本篇依然讲的是焦点方面的问题,还是老样子,先看下出问题的现象,gif走起~

从动图上可以看到,进入二级页面,焦点向下移动,编辑框没有获取到焦点,后向上移动焦点,才获取到,是不是很神奇?
我们知道EditText和Button一样,是默认可获取焦点的,但是这里面没有获取到,这里面页面不是listview,第一个item是封装了EditText的控件,下面的其他item是单独另一种风格封装的控件。
思路:
1、EditText被父View焦点拦截了?
2、焦点搜索规则导致向下移动,EditText不是最佳的可以获取焦点的view?
首先,迅速排除掉了第一个可能性,因为代码中没有设置拦截子view focus焦点。
那么重点看下第二个思路,依然从源码处入手,焦点搜索查找由
frameworks/base/core/java/android/view/FocusFinder.java负责。

  //查找焦点,root是根view,指的是DecorView,focused指的是当前已经获取到焦点的view,direction是焦点移动方向public final View findNextFocus(ViewGroup root, View focused, int direction) {return findNextFocus(root, focused, null, direction);}

1、查找

    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {View next = null;//我这里,focused为nullif (focused != null) {next = findNextUserSpecifiedFocus(root, focused, direction);}if (next != null) {return next;}//初始化承载可以获取焦点的view的listArrayList<View> focusables = mTempList;try {//查找前先清除listfocusables.clear();//调用根view的获取可以focusable的view并且添加到list中去root.addFocusables(focusables, direction);if (!focusables.isEmpty()) {//获取到焦点view的list,继续查找next = findNextFocus(root, focused, focusedRect, direction, focusables);}} finally {focusables.clear();}return next;}

2、遍历查找根view下的所有可以获取焦点的view,并且放到list集合去。
ViewGroup.java addFocusables方法

@Overridepublic void addFocusables(ArrayList<View> views, int direction, int focusableMode) {final int focusableCount = views.size();//获取当前viewGroup焦点能力final int descendantFocusability = getDescendantFocusability();//如果不是阻塞子view获取焦点,那么开始遍历子viewif (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {if (shouldBlockFocusForTouchscreen()) {focusableMode |= FOCUSABLES_TOUCH_MODE;}final int count = mChildrenCount;final View[] children = mChildren;//开始遍历for (int i = 0; i < count; i++) {final View child = children[i];//如果子view可见。if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {child.addFocusables(views, direction, focusableMode);}}}//子view添加完了,开始添加父view自己了。if ((descendantFocusability != FOCUS_AFTER_DESCENDANTS// No focusable descendants|| (focusableCount == views.size())) &&(isFocusableInTouchMode() || !shouldBlockFocusForTouchscreen())) {super.addFocusables(views, direction, focusableMode);}}

好了,这里不继续往下贴源码了,结合项目bug来看,二级页面的EditText以及下面列表的item都是可以获取焦点的,所以都会被添加到这个list容器中,供FocusFinder用来在他们之间查找。
继续回到FocusFinder,看下其源码实现。

  private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,int direction, ArrayList<View> focusables) {//如果当前已经存在获取焦点的view,设置当前焦点区域背景Rect区域if (focused != null) {if (focusedRect == null) {focusedRect = mFocusedRect;}// fill in interesting rect from focusedfocused.getFocusedRect(focusedRect);//将背景Rect转换为坐标系的值root.offsetDescendantRectToMyCoords(focused, focusedRect);} else {if (focusedRect == null) {focusedRect = mFocusedRect;// make up a rect at top left or bottom right of rootswitch (direction) {case View.FOCUS_RIGHT:case View.FOCUS_DOWN:setFocusTopLeft(root, focusedRect);break;case View.FOCUS_FORWARD:if (root.isLayoutRtl()) {setFocusBottomRight(root, focusedRect);} else {setFocusTopLeft(root, focusedRect);}break;case View.FOCUS_LEFT:case View.FOCUS_UP:setFocusBottomRight(root, focusedRect);break;case View.FOCUS_BACKWARD:if (root.isLayoutRtl()) {setFocusTopLeft(root, focusedRect);} else {setFocusBottomRight(root, focusedRect);break;}}}}switch (direction) {case View.FOCUS_FORWARD:case View.FOCUS_BACKWARD:return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,direction);case View.FOCUS_UP:case View.FOCUS_DOWN:case View.FOCUS_LEFT:case View.FOCUS_RIGHT://上下左右方向移动焦点,走这个方法。return findNextFocusInAbsoluteDirection(focusables, root, focused,focusedRect, direction);default:throw new IllegalArgumentException("Unknown direction: " + direction);}}
    View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,Rect focusedRect, int direction) {//设置了最合适的候选者背景位置mBestCandidateRect.set(focusedRect);//根据不同的焦点移动方向,调整候选区域switch(direction) {case View.FOCUS_LEFT:mBestCandidateRect.offset(focusedRect.width() + 1, 0);break;case View.FOCUS_RIGHT:mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);break;case View.FOCUS_UP:mBestCandidateRect.offset(0, focusedRect.height() + 1);break;case View.FOCUS_DOWN:mBestCandidateRect.offset(0, -(focusedRect.height() + 1));}//初始化最靠近的viewView closest = null;int numFocusables = focusables.size();//遍历查找焦点view listfor (int i = 0; i < numFocusables; i++) {View focusable = focusables.get(i);//如果是当前已经获取焦点的view或者是根view,跳过if (focusable == focused || focusable == root) continue;//获取候选者view的坐标值focusable.getFocusedRect(mOtherRect);root.offsetDescendantRectToMyCoords(focusable, mOtherRect);//重点来了,选取最合适的下一个可以获取焦点的viewif (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {mBestCandidateRect.set(mOtherRect);closest = focusable;}}return closest;}

下面是最最关键的部分了,重点来研究下,焦点匹配查找算法。

// 一共有三个Rect,source是当前可以获取焦点的区域,rect1是候选者区域,
rect2是选择项区域boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) {// to be a better candidate, need to at least be a candidate in the first// place :)//如果候选view不满足条件,直接passif (!isCandidate(source, rect1, direction)) {return false;}//如果候选view满足条件,继续看rect2,如果不满足条件,就选rect1// we know that rect1 is a candidate.. if rect2 is not a candidate,// rect1 is betterif (!isCandidate(source, rect2, direction)) {return true;}// if rect1 is better by beam, it wins//如果rect1,rect2都是候选者,都是合适的,继续在beamBeats维度上进行比较。if (beamBeats(direction, source, rect1, rect2)) {return true;}// if rect2 is better, then rect1 cant' be :)//在beamBeats 比较中,rect2不满足,选rect1if (beamBeats(direction, source, rect2, rect1)) {return false;}//好了,如果rect1,rect2都满足,继续这个比较// otherwise, do fudge-tastic comparison of the major and minor axisreturn (getWeightedDistanceFor(majorAxisDistance(direction, source, rect1),minorAxisDistance(direction, source, rect1))< getWeightedDistanceFor(majorAxisDistance(direction, source, rect2),minorAxisDistance(direction, source, rect2)));}

这段代码,说实话,看起来还是有点费劲的,总结一下。

1、一个source区域,也就是被比较的位置范围,rect1,候选者view的位置范围,rect2 当前最合适的区域范围。

2、一共有三个比较维度,isCandidate,beamBeats,还有getWeightedDistanceFor比较,我们一个个看其实怎么比较的。

isCandidate 比较:

 boolean isCandidate(Rect srcRect, Rect destRect, int direction) {switch (direction) {case View.FOCUS_LEFT:return (srcRect.right > destRect.right || srcRect.left >= destRect.right) && srcRect.left > destRect.left;case View.FOCUS_RIGHT:return (srcRect.left < destRect.left || srcRect.right <= destRect.left)&& srcRect.right < destRect.right;case View.FOCUS_UP:return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)&& srcRect.top > destRect.top;case View.FOCUS_DOWN:return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)&& srcRect.bottom < destRect.bottom;}throw new IllegalArgumentException("direction must be one of "+ "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");}

这段代码什么意思呢?我画个示意图吧,方便你们理解。
以向下移动举例:

如果满足目标区域完全在源区域下方或者目标区域和源区域仅有图示的部分重叠,那么dest目标view是满足条件的。

beamBeats 主要是比较水平方向的区域(以上下焦点为例)

 boolean beamsOverlap(int direction, Rect rect1, Rect rect2) {switch (direction) {case View.FOCUS_LEFT:case View.FOCUS_RIGHT:return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);case View.FOCUS_UP:case View.FOCUS_DOWN:return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);}throw new IllegalArgumentException("direction must be one of "+ "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");}

用图来表示:

getWeightedDistanceFor 比较维度。

这个维度在水平和垂直方向上做了一个权重比较。

 return (getWeightedDistanceFor(majorAxisDistance(direction, source, rect1),minorAxisDistance(direction, source, rect1))< getWeightedDistanceFor(majorAxisDistance(direction, source, rect2),minorAxisDistance(direction, source, rect2)));int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {return 13 * majorAxisDistance * majorAxisDistance+ minorAxisDistance * minorAxisDistance;}static int majorAxisDistanceToFarEdgeRaw(int direction, Rect source, Rect dest) {switch (direction) {case View.FOCUS_LEFT:return source.left - dest.left;case View.FOCUS_RIGHT:return dest.right - source.right;case View.FOCUS_UP:return source.top - dest.top;case View.FOCUS_DOWN:return dest.bottom - source.bottom;}throw new IllegalArgumentException("direction must be one of "+ "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");}static int minorAxisDistance(int direction, Rect source, Rect dest) {switch (direction) {case View.FOCUS_LEFT:case View.FOCUS_RIGHT:// the distance between the center verticalsreturn Math.abs(((source.top + source.height() / 2) -((dest.top + dest.height() / 2))));case View.FOCUS_UP:case View.FOCUS_DOWN:// the distance between the center horizontalsreturn Math.abs(((source.left + source.width() / 2) -((dest.left + dest.width() / 2))));}throw new IllegalArgumentException("direction must be one of "+ "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");}

这又是一段很难理解的算法,说实话,我很难用文字描述出来,哪个比较的rect的13majorAxisDistancemajorAxisDistance+ minorAxisDistance * minorAxisDistance的数字越大,那么他越合适。以向下移动焦点为例,majorAxisDistance指的是dest的bottom-src的区域,也就是高度差,高度差和 minorAxisDistance水平差两个因素,高度差的权重与水平差的权重比13:1。

经过这段算法的仔细分析,造成我bug的问题就出现在最后一个比较维度,因为代码设置了 EditText的 gravity=right,导致Edittext的绘制区域背景靠右,在比较上面落后于“呼叫速率”那一个控件,找到原因后,就知道如何改了,将EditText gravity=left就可以了,或者让EditText的父view可以获取焦点,就可以了。

项目疑难杂症记录(三):EditText获取不到焦点了?相关推荐

  1. 项目疑难杂症记录(一):fragment单例导致的界面异常

    前言:之前项目中也会遇到一些头疼的问题或者难解的bug,有些可能花费不少时间精力解决了,但是没有记录,打算从本篇博客开始,记录下项目中遇到的我认为的疑难杂症,算是对自己学习的总结,如果凑巧你也看到了, ...

  2. 项目疑难杂症记录(五):fragment生命周期都回调了,却不见其页面展示

    继续记录我的疑难bug解决过程,这次要说的bug相比前几篇来说,更难定位,因为影响较大,直接导致不解决这个bug,根本就没有办法出版本,两三个同事定位了半天也没有结果,最后我自告奋勇的暂时放下手中的工 ...

  3. Leo仿【DOTA视频站】项目实践【三】---- 获取DOTA2视频已经XListView实现上拉加载更多、下拉刷新

    在前一篇文章<优酷SDK学习>中使用优酷API根据视频种类进行搜索,可是优酷设置的视频种类中只有游戏,没有我想要搜索的DOTA2视频.于是为了弄懂怎么去搜索DOTA2视频,专门在优酷网页上 ...

  4. 项目疑难杂症记录(四):Activity被重新创建的原因分析

    在项目中遇到一个奇怪的Bug,插上带有升级包固件的U盘,选择升级框中的放弃按钮,Activity被onDestroy,随后又重新onCreate,相应的图片和日志如下: [一] 现象和日志 1.升级框 ...

  5. 项目疑难杂症记录(二):焦点移动不了

    本篇讲焦点移动不了的问题,先下下图效果. 进入"添加网络摄像机"页面后,遥控器按下往右的按键,焦点只落在第一个框上面,再也移动不了,页面拍的不是很清楚,需要仔细看下. 正常焦点移动 ...

  6. android editview获取焦点,Android EditText 获取不到焦点

    开发中遇到一个问题就是输入框EditText点击的时候没有弹出软键盘也没有任何反映,同级视图,其他的EditText有反映,唯有这一个没反应,搜索了下EditText相关的问题但都不是想要的信息 如上 ...

  7. EditText 获取不到焦点

    1.手动写入代码,设置光标,获取焦点 editText.setFocusable(true); editText.setFocusableInTouchMode(true); editText.set ...

  8. Hadoop学习笔记—20.网站日志分析项目案例(三)统计分析

    网站日志分析项目案例(一)项目介绍:http://www.cnblogs.com/edisonchou/p/4449082.html 网站日志分析项目案例(二)数据清洗:http://www.cnbl ...

  9. vue(vue-cli+vue-router)+babel+webpack项目搭建入门(三)

    vue(vue-cli+vue-router)+babel+webpack项目搭建入门<三> 本系列文章将介绍基于vue+webpack的前端项目的构建过程.文章分为四章内容,第一章介绍开 ...

最新文章

  1. php 错误传递,php-调用时通过引用传递错误,无法修复代码
  2. VS2019 C#安装那些插件_【完整版】针对零基础小白的VS2019安装攻略
  3. qunee for html5 api,Qunee for HTML5
  4. coroutine协程详解
  5. html检查输入为空,html input输入验证不为空
  6. ARM中断分析之二:裸机下面的中断处理
  7. PHP、 Ruby、Python、Java、C++、C、Objective C——编程语言之禅
  8. mybatis使用和分析
  9. https方式nginx 代理tomcat访问不带www的域名301重定向跳转到www的域名帮助seo集中权重...
  10. python设计模式pdf_精通python设计模式豆瓣-精通python设计模式第二版电子书pdf下载-精品下载...
  11. java byte 相等比较_Java字节码跟真正汇编的比较
  12. 问卷调查微信小程序源码
  13. 数字信号处理课程设计---带通滤波器的设计及其matlab实,数字信号处理课程设计---带通滤波器的设计及其MATLAB实现...
  14. baidu经纬度坐标与google经纬度坐标转换
  15. 数据库时间慢了14个小时,Mybatis说,这个锅我不背~
  16. 阿里百度腾讯等34家企业获年度互联网经济大奖
  17. 深度 | AI芯片之智能边缘计算的崛起——实时语言翻译、图像识别、AI视频监控、无人车这些都需要终端具有较强的计算能力,从而AI芯片发展起来是必然,同时5G网络也是必然...
  18. 中国人民银行面试题目(经典题目2)
  19. 微信小程序开发和APP开发有哪些区别
  20. 软考-嵌入式系统设计师:[网络安全:笔记(六)]

热门文章

  1. 关于三甲医院转行IT 医生家属说两句
  2. 绿屏后再现“粉屏”门!大量网友投诉iPhone 13粉屏问题 客服:非硬件问题
  3. 小米12系列首发!高通骁龙898有望11月30日亮相
  4. 中国三大运营商发声!要求纽交所复议退市决定
  5. 华为P50 Pro/Pro+更多细节曝光:6.7寸120Hz单孔屏 比前作更轻薄
  6. 三星首款5nm A78旗舰芯Exynos 1080即将亮相
  7. 转转、e代驾发布联合报告:代驾司机买二手手机看重验机、质保
  8. 高德联手饿了么:外卖小哥跑出偏远地区活地图
  9. 中国移动国际英国数据中心正式启动
  10. 第四代双模5G旗舰:vivo X30系列为啥「超有梗」?