两个Fragemnt中EditText的焦点问题
问题描述:
两个各自带有一个EditText的Fragment按顺序添加到同一个Activity上。使用自定义软键盘(该软键盘按相对位置直接显示在DecorView的Bottom上),在第二个Fragment中调用软键盘的隐藏键盘方法(通过清除RootView(DecorView)的焦点来实现清除子View(EditText)的焦点,来触发绑定在软键盘上的EditText所注册的View.OnFocusChangeListener
方法实现键盘隐藏)。
在源码中RootView清除焦点后会寻找一个View来重新获取焦点,这时问题(1)出现了,隐藏在后面(被第二个Fragment覆盖)的第一个Fragment的EditText获取了焦点,其所注册的View.OnFocusChangeListener
方法被触发,这时界面上显示出第一个Fragment的EditText所绑定的软键盘。扩展小问题为什么在清除焦点后需要重新找一个View来获取焦点呢?为什么是第一个Fragment的EditText获取到焦点呢?
问题(2)是该自定义键盘是显示在哪个View上的,或者是直接在Windows上显示?
问题(3)如果需要在点击按钮(如设置密码的确定按钮)后在加载视图弹出来之前隐藏键盘,那么需要找到另外一个控件来获取焦点;
问题(4)View的requestFocus()
方法先判断是否为可获取焦点,再判断是否在touch模式下可获取焦点,为何要进行二次判断?isFocusable
和isFocusableInTouchMode
的有什么区别?
问题解决或答疑:
问题(1):一个简单的解决方式是在第三个Fragment(本应用中恰好有第三个Fragment)上的某个控件(选第一个控件,减少查找时间)设置focusable
和focusableInTouchMode
为true
,并且在onCreateView()
中让该控件获取焦点。
扩展小问题:对于第一个小问题,这是View的绘制机制所决定的,在ViewRootImpl
绘制View的核心方法performTraversals()
中,会判断是否是在touch模式下,如果是在touch模式下,就会找到他的祖先View去调用requestFocus()
方法。这么做的原因我猜想是为了减少用户操作以提高用户体验,也即如果在界面上有EditText或者其他需要获取焦点的View,用户就不需要多一次点击操作来显示软键盘或者实现其他什么目的。
对于第二个小问题,requestFocus()
方法最终会调用handleFocusGainInternal
方法,在里面触发mParent.requestChildFocus
。如果是ViewGroup的话,它会判断是父类先获取焦点还是子类先获取焦点,如果父类没有阻挡子类获取焦点的话,会调用到onRequestFocusInDescendants
方法,在这个方法中,它会按顺序遍历ViewGroup用来存储子View的mChildren
数组,直到拿到可以获取焦点的View。而在FragmentTransaction.commit()
操作在添加View时,最终调用的是ViewGroup的addView()
方法,也即一个一个Fragment往后添加,所以在遍历mChildren
数组时会首先遍历第一个Fragment,所以第一个Fragment中的EditText获取到了焦点。
问题(2):自定义软键盘为了在任意界面都可以显示,所以使用getDecorView
来获取顶层View,然后按相对位置把软键盘View添加到该View底部。
问题(3):可以设置EditText的父控件的isFocusableInTouchMode
属性为true
,然后在点击按钮时让父控件主动获取焦点requestFocus()
。
问题(4):这要从android交互模式说起,第一部android手机同时支持touch模式、trackball模式、keyboard模式,发展到现在在手机上touch模式占了用户交互的绝大部分,部分手机还保留home键、菜单键和返回键。而且因为android系统需要保持在不同类型设备上强大的适应性,比如pos机(keyboard模式和touch模式)、android系统智能电视(keyboard模式)、android系统可视电话(keyboard模式、trackball模式),所以设计者对不同模式间的切换做了大量设计和完善工作。
设计者在08年12月发布的开发者博客中说到,在touch模式是没有焦点的,任何在其他模式下被选中或者说获得焦点而高亮的item在用户点击屏幕进入touch模式后都会变成未被选择的,当用户使用其他交互模式,比如trackball模式时,原本被选中的item会重新获得焦点。所以最开始的android新人开发者们很容易碰到的一个问题是在touch模式下使用 ListView.getSelectedItemPosition()
会返回 INVALID_POSITION
,正确的用法应该是使用click listeners或者choice模式,而使用click listeners对现在的新人开发者来说可能是很平常的操作。但他们还说在某些特别的情况下当用户使用touch模式时控件仍然可以获取焦点,这种模式就是为了使控件接收到文本输入,像EditText。
所以isFocusable
表示控件在任何模式下是否可以获得焦点,而在touch模式中还需要加上isFocusableInTouchMode
使得控件在此模式下能够获得焦点。
-完-
参考:
Android焦点分发和移动的原理
Android窗口机制(四)ViewRootImpl与View和WindowManager
Android Touch Mode
Fragment FragmentManager FragmentTransaction 深入理解