Android 事件传递与焦点处理(tv)

1、概述

上节介绍了android tv app 与android mobile app 的一些表现形式的不同。在实际编程中需要很多的焦点处理,而焦点处理有经常是在事件传递函数内处理的。所以本节做个android 事件传递与焦点处理的小结。另既然描述到android事件传递不可避免就涉及到了android手势拦截。这也是对原有知识认识的一个补充,因为之前涉及到安卓事件传递就是为了做手势拦截,以至于当看到代码在手势分发函数里处理tv的焦点,与界面移动填充时。一时有点迷糊,为什么是写在dispathKeyEvent里不是写在onInterceptTouchEvent里。原因就是因为这是在事件传递流程里处理焦点,而不是在事件传递流程里拦截事件。

 

2、Android简单的事件传递流程

提到android事件传递流程肯定会涉及到几个以下几个函数

函数

说明

备注

dispathTouchEvent (MotionEvent ev)

事件分发(手势分发)

触摸屏-手机

dispatchKeyEvent(KeyEvent event)

事件分发

遥控- tv

onInterceptTouchEvent

事件拦截(手势拦截)

返回true: 事件被拦截,事件传递给自己的

onTouchEvent

返回false:事件继续传递

ViewGroup才有

onTouchEvent

事件处理

返回true:事件被消费终止传递

返回false:继续传递

 

                                                        表1

Android事件传递有两种事件,一种是触摸屏的触摸事件,一种就是按键事件,键盘模式和遥控就用这种,不在熬述。dispathTouchEvent 和 dispatchKeyEvent 走的流程是一样的。

事件传递流程细节与源码的分析,网上有很多详细的描述,有些说的也是云里雾里,结合实际项目中的使用并参考:

PRE_andevcon_mastering-the-android-touch-system

事件传递从Activity的事件分发函数开始dispath… 如果没有事件消费最终在回到activty的ontouchEvent在子view被消费了,那就不会传到这个函数。

 

简单的Down事件的传递如下图和表1结合看:(事件未被消费)


                                                                                         图1

 

                                                                                        图2

       关于拦截:对于拦截down事件后move/up 事件的传递比较复杂,实际使用中只要知道哪个view 是没有down事件的也就没有move/up 而且在实际项目中自己拦截的是move事件。onInterceptTouchEvent

public boolean onInterceptTouchEvent(MotionEvent ev)

事件拦截函数onInterceptTouchEvent是ViewGroup才有的函数。事件拦截(手势拦截)也是在自定义ViewGroup时才要做的。实际情况遇到的是,对于move事件在一些边界使用条件的时候要决定是让ViewGroup自己处理还是childView来处理。其中通过返回值确定是否拦截,false继续传递,true拦截给自己,流程见图1,图2。

 

 

3、dispathKeyEvent 与 dispathTouchEvent

事件分发函数,也是事件流程的调度函数。基类的dispath…函数决定了事件传递的方式,里面有涉及到调用onInterceptTouchEvent,和onTouchEvent函数。

做自定ViewGroup事件拦截是不需要重写dispath…的只要重写onInterceptTouchEvent这个函数就可以。所以之前也比较少见重写dispath…

但是在tv app里好多地方是重写了该函数。开始的时候有点不明白为什么写在这里,后来看到每个dispathKeyEvent 函数都有调用super.dispathKeyEvent就明白了,重写dispathKeyEvent并没有影响事件的分发。只是相当于一个钩子挂在事件传递流程上来处理:点击按键手势时理焦点效果与界面效果,还有就是重写dispathKeyEvent可以很方便的获取到当前遥控的控件按钮。而在onInterceptTouchEvent就不行。所以遥控设备大量从写dispathKeyEvent方法还是有道理的

 

4、焦点处理

手机app开发时焦点处理比较少,有就是editText有时候需要获取下焦点,或者移除下,其实也比较少处理。Tv里就比较多要处理,这里罗列下几个焦点处理函数

Activity

 

View currentView = getCurrentFocus();

 

ViewGroup

 

View currentView = findFocus();

 

View

 

currentView.focusSearch(direction);

找到指定方向最近的一个可获取焦点的view 如果没有返回null

setClickedAble

 

setFocusAble

 


添加几个函数:


((ViewGroup) parent).getFocusedChild() 获取子布局里有焦点的view 没有返回null。就不用自己去遍历布局了
public boolean hasFocus() 判断view 是否有焦点 或者view的子view是否有焦点
 Returns true if this view has focus iteself, or is the ancestor of the
* view that has focus.

下面的明白的例子说明了几个函数的具体意思
if (parent.hasFocus()) {
    if (parent.isFocused()) {
        return parent;
    } else {
        return getFocusView(((ViewGroup) parent).getFocusedChild());
    }
} 

/**
 * Called when this view wants to give up focus. If focus is cleared
 * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} is called.
 * <p>
 * <strong>Note:</strong> When a View clears focus the framework is trying
 * to give focus to the first focusable View from the top. Hence, if this
 * View is the first from the top that can take focus, then all callbacks
 * related to clearing focus will be invoked after wich the framework will
 * give focus to this view.
 * </p>
 */
public void clearFocus() {


另外 api里有 FocusFinder 工具类提供寻找focus

public View findNearestTouchable(ViewGroup root, int x, int y, int direction, int[] deltas) 
public final View findNextFocus(ViewGroup root, View focused, int direction) {
public View findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction) {

 

5、实际中遇到

一个需求:tabhost 点击向上跳到set按钮,setting按钮点击向下要回到上次的那个tabhost的select tab。如果不处理是跑到最近的一个tab

思路一:在setting按钮的focus监听器里指定出焦点向下跑时跑到指定的id。

问题:tabhost对应的每个tab的id实际中都是一样的,所以该思路废弃


思路二:在setting的focus监听器中指定当焦点丢失时,原来的tab亮起来

问题:无法再该监听器中得到是上下左右那种焦点的丢失或者得到,该思路废弃


思路三:setting按钮属于titleBar里的一个view,那么在titleBar这个继承自layout的容器里重写dispathTouchEvent,该函数里判断是findfocus是在setting按钮,并且操作是向下则把tabhost当前select的tab focus起来,并且拦截了该次的操作不继续向下传递(继续传递则默认最近的view会focus)

代码:

@Override
public boolean dispatchKeyEvent(KeyEvent event) {

    if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
        if (mSearchView.equals(findFocus())){
            if (tabHost != null && tabHost.getCurrentTabView() != null){

                tabHost.getCurrentTabView().requestFocus();

                //return true 让焦点跑到上次的地方,并且终止在该容器中手势的继续传递
                return true;
            }
        }

    }

    return super.dispatchKeyEvent(event);
}

6、有时候要找焦点。找焦点得先找子布局

View view = mGridLayout.get(0).getAdapter().getView(0, null, null);
if (view != null){
if (view instanceof FrameLayout){
FrameLayout l = (FrameLayout) view;
//最外层的layout
LoggerUtil.d("MainFragment", "view = " + view);
int childCount = l.getChildCount();
//遍历下面所有的子控件,判断是否是layout
for(int i = 0; i < childCount; i++){
LoggerUtil.d("MainFragment", "view l = " + l.getChildAt(i));
if (l.getChildAt(i) instanceof VideoView){
LoggerUtil.d("MainFragment", "view lll = " + l.getChildAt(i));
l.getChildAt(i).requestFocus();
}
}
}
}


 
FOCUS_BEFORE_DESCENDANTS  ViewGroup本身先对焦点进行处理,如果没有处理则分发给child View进行处理
FOCUS_AFTER_DESCENDANTS   先分发给Child View进行处理,如果所有的Child View都没有处理,则自己再处理
FOCUS_BLOCK_DESCENDANTS   ViewGroup本身进行处理,不管是否处理成功,都不会分发给ChildView进行处理



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值