2015-10-11 19:36:25 jjwwmlp456 阅读数 2782

android:descendantFocusability 

属性的值有三种:

        beforeDescendants:ViewGroup 在后代前获取到焦点

        afterDescendants:ViewGroup 在后代之后获取焦点   //默认

        blocksDescendants:ViewGroup 只是自身获取焦点

2018-11-21 17:12:16 LikeBoke 阅读数 43

开发经常用到,在此记录一下:

给父布局设置获取焦点属性
android:focusable="true"
android:focusableInTouchMode="true"

 

2015-12-11 10:42:24 aaawqqq 阅读数 45215

Android Edittext 显示光标 获取焦点 监听焦点   



Edittext java 代码控制获取焦点  
            EditText mEditText = (EditText) findViewById(R.id.et);
            mEditText.setFocusable(true);
            mEditText.setFocusableInTouchMode(true);

显示光标
mEditText.requestFocus();//获取焦点 光标出现

失去焦点
mEditText.clearFocus();
监听EditText焦点变化   当获取焦点后 hasFocus 为true
mEditText.setOnFocusChangeListener(new android.view.View.OnFocusChangeListener() {

                @Override
                public void onFocusChange(View v, boolean hasFocus) {

                    if (hasFocus) {

                        // 获得焦点

                    } else {

                        // 失去焦点

                    }

                }


            });


使用XML配置文件控制光标的代码
cursorVisible 中
true为显示  
false为隐藏光标
android:cursorVisible="true"
android:cursorVisible="false"

EditText不自动获取焦点 在EditText的父级控件上设置
                    android:focusable="true"
                    android:focusableInTouchMode="true"

EditText 设置光标颜色
                android:textCursorDrawable="#ff2244"
如果想设置光标颜色和字体一样 设置@null 即可


//  ┏┓   ┏┓
//┏┛┻━━━┛┻┓
//┃       ┃
//┃   ━   ┃
//┃ ┳┛ ┗┳ ┃
//┃       ┃
//┃   ┻   ┃
//┃       ┃
//┗━┓   ┏━┛
//    ┃   ┃   神兽保佑
//    ┃   ┃   代码无BUG!
//    ┃   ┗━━━┓
//    ┃       ┣┓
//    ┃       ┏┛
//    ┗┓┓┏━┳┓┏┛
//      ┃┫┫ ┃┫┫
//      ┗┻┛ ┗┻┛

希望对大家有用

2014-08-07 16:32:28 wuxifu001 阅读数 1335

listview的多个edittext如何分别获得焦点呢?

//listview后获取焦点,优先级低于item

listView.setDescendantFocusability(ListView.FOCUS_AFTER_DESCENDANTS);

我的思路就是在edittext的头部包层button,监听button的点击事件,来获取焦点

 <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal"
        android:paddingTop="10dp" >


        <TextView
            android:id="@+id/tv_faPiaoKaili"
            style="@style/left_title"
            android:text="@string/product_fapiaoKaili" />


        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >


            <RadioGroup
                android:id="@+id/radioGroup1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="horizontal" >


                <RadioButton
                    android:id="@+id/radio0"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/product_fapiao3" />


                <RadioButton
                    android:id="@+id/radio1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:checked="true"
                    android:text="@string/product_fapiao2" />
            </RadioGroup>


            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content" >


                <EditText
                    android:id="@+id/et_code"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@drawable/aboutus_content_bg003"
                    android:ems="10"
                    android:hint="@string/product_fapiaoCode"
                    android:inputType="phone"
                    android:padding="8dp" />


                <Button
                    android:id="@+id/bt_code"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@android:color/transparent"
                    android:focusable="false" 
                    />
            </FrameLayout>



            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content" >


                <EditText
                    android:id="@+id/et_header"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="4dp"
                    android:background="@drawable/aboutus_content_bg003"
                    android:ems="10"
                    android:hint="@string/product_fapiaoHeader"
                    android:inputType="phone"
                    android:padding="8dp" />


                <Button
                    android:id="@+id/bt_header"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@android:color/transparent"
                    android:focusable="false" 
                    />
            </FrameLayout>

        </LinearLayout>
    </LinearLayout>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal"
        android:paddingTop="10dp" >


        <TextView
            android:id="@+id/tv_payMethod"
            style="@style/left_title"
            android:text="@string/product_payStyle" />


        <Spinner
            android:id="@+id/sp_payMethod"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@drawable/aboutus_content_bg003" />
    </LinearLayout>


    <TextView
        android:id="@+id/tv_remark"
        style="@style/left_title"
        android:layout_marginTop="10dp"
        android:text="@string/product_remark" />


    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >


        <EditText
            android:id="@+id/et_remark"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/aboutus_content_bg003"
            android:ems="115"
            android:gravity="left"
            android:minHeight="80dp"
            android:padding="5dp" />


        <Button
            android:id="@+id/bt_remark"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/transparent"
            android:focusable="false"
            />
    </FrameLayout>

//代码

viewHolder004.bt_code.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {

//其它两个edittext先设置不能获取焦点
viewHolder004.et_header.setFocusable(false);
viewHolder004.et_remark.setFocusable(false);

showSoftInput(viewHolder004.et_code);
}
});
viewHolder004.bt_header.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
viewHolder004.et_code.setFocusable(false);
viewHolder004.et_remark.setFocusable(false);
showSoftInput(viewHolder004.et_header);
}
});
viewHolder004.bt_remark.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
viewHolder004.et_header.setFocusable(false);
viewHolder004.et_code.setFocusable(false);
                  showSoftInput(viewHolder004.et_remark);
}
});

/**
* @param editText  系统键盘的弹出
*/
private void showSoftInput(EditText editText) {

//3个一起设置才有效,不然可能无效
editText.setFocusable(true);
editText.setFocusableInTouchMode(true);
editText.requestFocus();

InputMethodManager inputMethodManager=(InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
//系统键盘显示后=>隐藏的切换
        //inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, //InputMethodManager.HIDE_NOT_ALWAYS);
//没用
//inputMethodManager.showSoftInputFromInputMethod(viewHolder004.et_code.getWindowToken(), //InputMethodManager.SHOW_IMPLICIT);
//显示系统键盘
inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
}

/**隐藏软键盘
* @param context
* @param et_tel
*/
public static void hiddenSoftInput(Context context, EditText et_tel) {
// 隐藏键盘
InputMethodManager imm = (InputMethodManager) context
.getApplicationContext().getSystemService(
Context.INPUT_METHOD_SERVICE);


// 如果输入法在窗口上已经显示,则隐藏,反之则显示
// imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
// 强制隐藏键盘
if (et_tel != null) {
IBinder applicationWindowToken = et_tel.getApplicationWindowToken();
if(applicationWindowToken!=null){
imm.hideSoftInputFromWindow(applicationWindowToken, 0);
}
}
}
/**
* @param context   隐藏软键盘
*/
public static void hiddenSoftInput(Context context)
{
if(context  instanceof  Activity){
// 隐藏键盘
InputMethodManager imm = (InputMethodManager) context
.getApplicationContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
View currentFocus = ((Activity) context).getCurrentFocus();
if(currentFocus!=null){
IBinder applicationWindowToken = currentFocus.getApplicationWindowToken();
if(applicationWindowToken!=null){
imm.hideSoftInputFromWindow(applicationWindowToken, 0);
}
}
}
}



2017-04-05 20:45:50 zhcswlp0625 阅读数 6833

如果您对TouchEvent事件分发机制不太了解的,可以参考我的这篇文章——安卓TounchEvent事件分发机制。

问题:TV端焦点满天飞,如何解决和处理?

记得初入TV开发,以为很简单。TV的这些界面与布局太简单了,分分钟就可以把页面搭建出来,处理好,然后就没有然后了。。。。

下面我们就从源码来带大家进行安卓TV焦点事件的传递

这里先给出Android系统View的绘制流程:

依次执行View类里面的如下三个方法:

  • measure(int ,int) :测量View的大小
  • layout(int ,int ,int ,int) :设置子View的位置
  • draw(Canvas) :绘制View内容到Canvas画布上

ViewRootImpl的主要作用如下(此处不多讲,如有意图,看源码):

  • A:链接WindowManager和DecorView的纽带,更广一点可以说是Window和View之间的纽带。

  • B:完成View的绘制过程,包括measure、layout、draw过程。

  • C:向DecorView分发收到的用户发起的event事件,如按键触屏等事件。

ViewRootImpl不再多余叙述,进入正题:

Android焦点分发的主要方法以及拦截方法的讲解。

在RootViewImpl中的函数通道是各种策略(InputStage)的组合,各策略负责的任务不同,如SyntheticInputStage、ViewPostImeInputStage、NativePostImeInputStage等等,这些策略以链表结构结构起来,当一个策略者没有消费事件时,就传递个下一个策略者。其中触摸和按键事件由ViewPostImeInputStage处理。

 @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);//如果是按键事件走此处,处理按键和焦点问题了
            } else {
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    return processPointerEvent(q);//如果是触摸事件走此处
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    return processTrackballEvent(q);
                } else {
                    return processGenericMotionEvent(q);
                }
            }
        }

processKeyEvent(QueuedInputEvent q)源码如下:

   @Override
        protected void onDeliverToNext(QueuedInputEvent q) {
            if (mUnbufferedInputDispatch
                    && q.mEvent instanceof MotionEvent
                    && ((MotionEvent)q.mEvent).isTouchEvent()
                    && isTerminalInputEvent(q.mEvent)) {
                mUnbufferedInputDispatch = false;
                scheduleConsumeBatchedInput();
            }
            super.onDeliverToNext(q);
        }

        private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;

            // Deliver the key to the view hierarchy.
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }

            if (shouldDropInputEvent(q)) {
                return FINISH_NOT_HANDLED;
            }

            // If the Control modifier is held, try to interpret the key as a shortcut.
            if (event.getAction() == KeyEvent.ACTION_DOWN
                    && event.isCtrlPressed()
                    && event.getRepeatCount() == 0
                    && !KeyEvent.isModifierKey(event.getKeyCode())) {
                if (mView.dispatchKeyShortcutEvent(event)) {
                    return FINISH_HANDLED;
                }
                if (shouldDropInputEvent(q)) {
                    return FINISH_NOT_HANDLED;
                }
            }

            // Apply the fallback event policy.
            if (mFallbackEventHandler.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }
            if (shouldDropInputEvent(q)) {
                return FINISH_NOT_HANDLED;
            }

            // Handle automatic focus changes.
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                int direction = 0;
                switch (event.getKeyCode()) {
                    case KeyEvent.KEYCODE_DPAD_LEFT:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_LEFT;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_RIGHT:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_RIGHT;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_UP:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_UP;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_DOWN:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_DOWN;
                        }
                        break;
                    case KeyEvent.KEYCODE_TAB:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_FORWARD;
                        } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                            direction = View.FOCUS_BACKWARD;
                        }
                        break;
                }
                if (direction != 0) {
                    View focused = mView.findFocus();
                    if (focused != null) {
                        View v = focused.focusSearch(direction);
                        if (v != null && v != focused) {
                            // do the math the get the interesting rect
                            // of previous focused into the coord system of
                            // newly focused view
                            focused.getFocusedRect(mTempRect);
                            if (mView instanceof ViewGroup) {
                                ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                                        focused, mTempRect);
                                ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                                        v, mTempRect);
                            }
                            if (v.requestFocus(direction, mTempRect)) {
                                playSoundEffect(SoundEffectConstants
                                        .getContantForFocusDirection(direction));
                                return FINISH_HANDLED;
                            }
                        }

                        // Give the focused view a last chance to handle the dpad key.
                        if (mView.dispatchUnhandledMove(focused, direction)) {
                            return FINISH_HANDLED;
                        }
                    } else {
                        // find the best view to give focus to in this non-touch-mode with no-focus
                        View v = focusSearch(null, direction);
                        if (v != null && v.requestFocus(direction)) {
                            return FINISH_HANDLED;
                        }
                    }
                }
            }
            return FORWARD;
        }

进入源码讲解:

(1) 首先由dispatchKeyEvent进行焦点的分发

如果dispatchKeyEvent方法返回true,那么下面的焦点查找步骤就不会继续了。

dispatchKeyEvent方法返回true代表事件(包括焦点和按键)被消费了。

dispatchKeyEvent(event)如果不了解,看我上一篇文章安卓TounchEvent事件分发机制。

mView的dispatchKeyEvent方法,
mView是是Activity的顶层容器DecorView,它是一FrameLayout

所以这里的dispatchKeyEvent方法应该执行的是ViewGroup的dispatchKeyEvent()方法,而不是View的dispatchKeyEvent方法。

 @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 1);
        }

        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
            if (super.dispatchKeyEvent(event)) {
                return true;
            }
        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
                == PFLAG_HAS_BOUNDS) {
            if (mFocused.dispatchKeyEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
        }
        return false;
    }

ViewGroup的dispatchKeyEvent简略执行流程

首先ViewGroup会执行父类的dispatchKeyEvent方法,如果返回true那么父类的dispatchKeyEvent方法就会返回true,也就代表父类消费了该焦点事件,那么焦点事件自然就不会往下进行分发

然后ViewGroup会判断mFocused这个view是否为空如果为空就会****return false,焦点继续往下传递;如果不为空,那就会return mFocused的dispatchKeyEvent方法返回的结果。这个mFocused是ViewGroup中当前获取焦点的子View,这个可以从requestChildFocus方法中得到答案。

requestChildFocus()的源码如下:

   @Override
    public void requestChildFocus(View child, View focused) {
        if (DBG) {
            System.out.println(this + " requestChildFocus()");
        }
        if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
            return;
        }

        // Unfocus us, if necessary
        super.unFocus(focused);

        // We had a previous notion of who had focus. Clear it.
        if (mFocused != child) {
            if (mFocused != null) {
                mFocused.unFocus(focused);
            }

            mFocused = child;
        }
        if (mParent != null) {
            mParent.requestChildFocus(this, focused);
        }
    }

居然有这个彩蛋?

View的dispatchKeyEvent简略执行流程

public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 0);
        }

        // Give any attached key listener a first crack at the event.
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
            return true;
        }

        if (event.dispatch(this, mAttachInfo != null
                ? mAttachInfo.mKeyDispatchState : null, this)) {
            return true;
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

要修改ViewGroup焦点事件的分发:

  • 重写view的dispatchKeyEvent方法
  • 给某个子view设置onKeyListener监听

焦点没有被dispatchKeyEvent拦截的情况下的继续代码中的处理过程,还是进入ViewRootImpl源码

            // Handle automatic focus changes.
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                int direction = 0;
                switch (event.getKeyCode()) {
                    case KeyEvent.KEYCODE_DPAD_LEFT:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_LEFT;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_RIGHT:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_RIGHT;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_UP:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_UP;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_DOWN:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_DOWN;
                        }
                        break;
                    case KeyEvent.KEYCODE_TAB:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_FORWARD;
                        } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                            direction = View.FOCUS_BACKWARD;
                        }
                        break;
                }
                if (direction != 0) {
                    View focused = mView.findFocus();
                    if (focused != null) {
                        View v = focused.focusSearch(direction);
                        if (v != null && v != focused) {
                            // do the math the get the interesting rect
                            // of previous focused into the coord system of
                            // newly focused view
                            focused.getFocusedRect(mTempRect);
                            if (mView instanceof ViewGroup) {
                                ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                                        focused, mTempRect);
                                ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                                        v, mTempRect);
                            }
                            if (v.requestFocus(direction, mTempRect)) {
                                playSoundEffect(SoundEffectConstants
                                        .getContantForFocusDirection(direction));
                                return FINISH_HANDLED;
                            }
                        }

                        // Give the focused view a last chance to handle the dpad key.
                        if (mView.dispatchUnhandledMove(focused, direction)) {
                            return FINISH_HANDLED;
                        }
                    } else {
                        // find the best view to give focus to in this non-touch-mode with no-focus
                        View v = focusSearch(null, direction);
                        if (v != null && v.requestFocus(direction)) {
                            return FINISH_HANDLED;
                        }
                    }
                }
            }

dispatchKeyEvent方法返回false后,先得到按键的方向direction一个int值。direction值是后面来进行焦点查找的。

接着会调用DecorView的findFocus()方法一层一层往下查找已经获取焦点的子View。

DecorView则是PhoneWindow类的一个内部类,继承于FrameLayout,由此可知它是一个ViewGroup。

那么,DecroView到底充当了什么样的角色呢?

其实,DecorView是整个ViewTree的最顶层View,它是一个FrameLayout布局,代表了整个应用的界面。在该布局下面,有标题view内容view这两个子元素。

 @Override
    public View findFocus() {
        if (DBG) {
            System.out.println("Find focus in " + this + ": flags="
                    + isFocused() + ", child=" + mFocused);
        }

        if (isFocused()) {
            return this;
        }

        if (mFocused != null) {
            return mFocused.findFocus();
        }
        return null;
    }

View的findFocus方法

 /**
     * Find the view in the hierarchy rooted at this view that currently has
     * focus.
     *
     * @return The view that currently has focus, or null if no focused view can
     *         be found.
     */
    public View findFocus() {
        return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;
    }

View的hasFocus()方法和isFocused()方法对比

Stackoverflow解释来了:

hasFocus() is different from isFocused(). hasFocus() == true means that the View or one of its descendants is focused. If you look closely, there’s a chain of hasFocused Views till you reach the View that isFocused.

 /**
     * Returns true if this view has focus itself, or is the ancestor of the
     * view that has focus.
     *
     * @return True if this view has or contains focus, false otherwise.
     */
    @ViewDebug.ExportedProperty(category = "focus")
    public boolean hasFocus() {
        return (mPrivateFlags & PFLAG_FOCUSED) != 0;
    }

  /**
     * Returns true if this view has focus
     *
     * @return True if this view has focus, false otherwise.
     */
    @ViewDebug.ExportedProperty(category = "focus")
    public boolean isFocused() {
        return (mPrivateFlags & PFLAG_FOCUSED) != 0;
    }

接着,如果mView.findFocus()方法返回的mFocused不为空,说明找到了当前获取焦点的view(mFocused),接着focusSearch会把direction(遥控器按键按下的方向)作为参数,找到特定方向下一个将要获取焦点的view,最后如果该view不为空,那么就让该view获取焦点。

我们来看一下focusSearch方法的源码以及具体实现。

 @Override
    public View focusSearch(View focused, int direction) {
        if (isRootNamespace()) {
            // root namespace means we should consider ourselves the top of the
            // tree for focus searching; otherwise we could be focus searching
            // into other tabs.  see LocalActivityManager and TabHost for more info
            return FocusFinder.getInstance().findNextFocus(this, focused, direction);
        } else if (mParent != null) {
            return mParent.focusSearch(focused, direction);
        }
        return null;
    }

focusSearch其实是一层一层地网上调用父View的focusSearch方法,直到当前view是根布局(isRootNamespace()方法),通过注释可以知道focusSearch最终会调用DecorView的focusSearch方法。而DecorView的focusSearch方法找到的焦点view是通过FocusFinder来找到的。

FocusFinder是什么?

根据给定的按键方向,通过当前的获取焦点的View,查找下一个获取焦点的view这样算法的类。焦点没有被拦截的情况下,Android焦点的查找最终都是通过FocusFinder类来实现的。

FocusFinder是如何通过findNextFocus方法寻找焦点的?

 public final View findNextFocus(ViewGroup root, View focused, int direction) {
        return findNextFocus(root, focused, null, direction);
    }

     private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
        View next = null;
        if (focused != null) {
            next = findNextUserSpecifiedFocus(root, focused, direction);
        }
        if (next != null) {
            return next;
        }
        ArrayList<View> focusables = mTempList;
        try {
            focusables.clear();
            root.addFocusables(focusables, direction);
            if (!focusables.isEmpty()) {
                next = findNextFocus(root, focused, focusedRect, direction, focusables);
            }
        } finally {
            focusables.clear();
        }
        return next;
    }

private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
            int direction, ArrayList<View> focusables) {
        if (focused != null) {
            if (focusedRect == null) {
                focusedRect = mFocusedRect;
            }
            // fill in interesting rect from focused
            focused.getFocusedRect(focusedRect);
            root.offsetDescendantRectToMyCoords(focused, focusedRect);
        } else {
            if (focusedRect == null) {
                focusedRect = mFocusedRect;
                // make up a rect at top left or bottom right of root
                switch (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;
                    }
                }
            }
        }


    private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
        // check for user specified next focus
        View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
        if (userSetNextFocus != null && userSetNextFocus.isFocusable()
                && (!userSetNextFocus.isInTouchMode()
                        || userSetNextFocus.isFocusableInTouchMode())) {
            return userSetNextFocus;
        }
        return null;
    }

FocusFinder类通过findNextFocus来找焦点的。一层一层往寻找,后面会执行findNextUserSpecifiedFocus()方法,这个方法会执行focused(即当前获取焦点的View)的findUserSetNextFocus方法,如果该方法返回的View不为空,
且isFocusable = true && isInTouchMode() = true的话。

FocusFinder找到的焦点就是findNextUserSpecifiedFocus()返回的View。

findNextFocus会优先根据XML里设置的下一个将获取焦点的View的ID值来寻找将要获取焦点的View。

 View findUserSetNextFocus(View root, @FocusDirection int direction) {
        switch (direction) {
            case FOCUS_LEFT:
                if (mNextFocusLeftId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusLeftId);
            case FOCUS_RIGHT:
                if (mNextFocusRightId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusRightId);
            case FOCUS_UP:
                if (mNextFocusUpId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusUpId);
            case FOCUS_DOWN:
                if (mNextFocusDownId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusDownId);
            case FOCUS_FORWARD:
                if (mNextFocusForwardId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusForwardId);
            case FOCUS_BACKWARD: {
                if (mID == View.NO_ID) return null;
                final int id = mID;
                return root.findViewByPredicateInsideOut(this, new Predicate<View>() {
                    @Override
                    public boolean apply(View t) {
                        return t.mNextFocusForwardId == id;
                    }
                });
            }
        }
        return null;
    }

焦点事件分发步骤:

  • DecorView会调用dispatchKey一层一层进行焦点的分发,如果dispatchKeyEvent方法返回true的话,那么焦点或者按键事件就不会往下分发了。

  • 如果你想拦截某个子View,对其设置OnKeyListener进行焦点的拦截。

  • 如果焦点没有被拦截的话,那么焦点就会交给系统来处理,还是会继续分发,直到找到那个获取焦点的View

  • Android底层先会记录按键的方向,后面DecorView会一层一层往下调用findFocus方法找到当前获取焦点的View

  • 后面系统又会根据按键的方向,执行focusSearch方法来寻找下一个将要获取焦点的View

  • focusSearch内部其实是通过FocusFinder来查找焦点的。FocusFinder会优先通过View在XML布局设置的下一个焦点的ID来查找焦点。

  • 最终如果找到将要获取焦点的View,就让其requestFocus。如果请求无效,将其放在onWindowFocusChanged()这个方法中去请求。这是在Activity寻找到焦点的时候。

我的前一篇文章,主要是介绍了TouchEvent事件分发机制,省略了焦点分发传递机制的代码,这篇文章与此相反。如果将两个结合起来,太繁杂,冗长了。分开反而有利于您的理解。至此,事件分发机制,你也了解的差不多了,給个粉吧!

Android之音频焦点

阅读数 752

android 中 焦点控制

阅读数 1132

android 焦点控制

阅读数 10179

没有更多推荐了,返回首页