android 焦点分发,Android焦点分发策略

[TOC]

焦点分发基础知识

获取焦点的前提

View#isFocusable返回true, 如果在触摸模式, 则View#isFocusableInTouchMode也要返回true

控件必须可见

控件相关的父控件, 包括祖父控件等, ViewGroup#getDescendantFocusability()不能为ViewGroup#FOCUS_BLOCK_DESCENDANTS

焦点相关api

requestFocus

主动请求焦点

clearFocus

主动清除焦点

focusSearch

递归搜索需要焦点的View

ViewGroup.setDescendantFocusability

三个参数含义:

FOCUS_BEFORE_DESCENDANTS

ViewGroup本身相对焦点进行处理,如果沒有处理则分发给child View进行处理

FOCUS_AFTER_DESCENDANTS

先分发给Child View进行处理,如果所有的Child View都沒有处理,則自己再处理

FOCUS_BLOCK_DESCENDANTS

ViewGroup本身进行处理,不管是否处理成功,都不会分发给ChildView进行处理

onRequestFocusInDescendants

可以传入方向来改变遍历的顺序, 默认是从0递增,遍历子控件,调用子控件的View#requestFocus来尝试把焦点给可见的子控件, 某个子控件成功获取到焦点后, 停止遍历

注: 重写该方法可以改变ViewGroup分发焦点给子控件的行为, 例如遍历顺

requestChildFocus

当子控件主动放弃焦点的时候,回调这个方法通知父控件.

clearChildFocus

当子控件获取了焦点后, 回调这个方法通知父控件

focusableViewAvailable

通知父控件, 子控件的状态发生改变, 从不能获取焦点, 变成可能可以获取焦点.

hasFocus

当前视图是否是焦点视图或子视图里面有焦点视图

isFocused()

当前视图是否是焦点视图

findFocus

查找焦点控件

getFocusedChild

获取子控件的焦点view

焦点分发源码分析

在Android中任何事件都会经由ViewRootImpl$ViewPostImeInputStage的onProcess处理.在onProcess方法中会判断不同事件类型做不同的处理.这里我们只分析触发焦点转移的KeyEvent.事件分发机制参考

当我们触发按键,最终会由底层把事件传入onProcess,在onProcess中如果是KeyEvent类型就进入processKeyEvent方法中进行处理.

processKeyEvent方法源码如下:

private int processKeyEvent(QueuedInputEvent q) {

final KeyEvent event = (KeyEvent)q.mEvent;

if (event.getAction() != KeyEvent.ACTION_UP) {

// If delivering a new key event, make sure the window is

// now allowed to start updating.

handleDispatchDoneAnimating();

}

//1.向View树分发KeyEvent事件

// Deliver the key to the view hierarchy.

if (mView.dispatchKeyEvent(event)) {

return FINISH_HANDLED;

}

//........................此处省略部分代码

//2.处理焦点的转移

// Handle automatic focus changes.

//........................此处省略部分代码

if (direction != 0) {

//step1 找到当前View树中的焦点View

View focused = mView.findFocus();

if (focused != null) {

//step2 当前焦点View根据方向搜索下一个需要焦点的View并主动请求焦点

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

//step3 ViewRootImpl根据方向搜索下一个需要焦点的View并主动请求焦点

View v = focusSearch(null, direction);

if (v != null && v.requestFocus(direction)) {

return FINISH_HANDLED;

}

}

}

}

这个方法中主要做了两件事,1.向View树分发KeyEvent事件 2.处理焦点的转移,下面主要分析处理焦点的转移这条逻辑.

1.向View树分发KeyEvent事件

// Deliver the key to the view hierarchy.

if (mView.dispatchKeyEvent(event)) {

return FINISH_HANDLED;

}

这就是我们熟悉View层的事件分发,同理还有

dispatchHoverEvent

dispatchTouchEvent

2.处理焦点的转移

step1 : 当我们的按键有一个确定的方向,首先找到当前View树中的焦点view.

if (direction != 0) {

View focused = mView.findFocus();

}

step2 : 如果找到了当前焦点View,则调用当前焦点View的焦点搜索方法,并返回下一个需要焦点的View,最后需要焦点的View主动请求焦点

View v = focused.focusSearch(direction);

if (v.requestFocus(direction, mTempRect)) {

playSoundEffect(SoundEffectConstants

.getContantForFocusDirection(direction));

return FINISH_HANDLED;

}

最终View#focusSearch直接调用的是ViewPaent#focusSearch

ViewParent是一个接口,最后在ViewGroup中实现,后文分析ViewGroup#focusSearch,View的focusSearch方法如下:

public View focusSearch(@FocusRealDirection int direction) {

if (mParent != null) {

return mParent.focusSearch(this, direction);

} else {

return null;

}

}

step3 : 如果未找到当前焦点View,则调用ViewRootImpl的焦点搜索方法,并返回下一个需要焦点的View,最后需要焦点的View主动请求焦点

View v = focusSearch(null, direction);

if (v != null && v.requestFocus(direction)) {

return FINISH_HANDLED;

}

本质上ViewRootImpl#focusSearch方法重写的是ViewPaent#focusSearch,此方法内部直接调用焦点帮助类,返回一个需要焦点的View.方法如下:

@Override

public View focusSearch(View focused, int direction) {

checkThread();

if (!(mView instanceof ViewGroup)) {

return null;

}

return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction);

}

至此,焦点分发的逻辑就进入了View层,下面分析View层的逻辑.

ViewGroup#focusSearch

因为View的focusSearch直接调用的是ViewParent的focusSearch,所以我们只分析ViewGroup的focusSearch

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;

}

ViewGroup的focusSearch方法非常简单,如果是root View就调用焦点帮助类返回一个需要焦点的View,反之递归调用ViewGroup的focusSearch,直到找到一个需要焦点的View.

requestFocus

了解完focusSearch我们继续分析requestFocus,requestFocus在View和ViewGrope有各自不同的实现.

ViewGroup#requestFocus

@Override

public boolean requestFocus(int direction, Rect previouslyFocusedRect) {

int descendantFocusability = getDescendantFocusability();

switch (descendantFocusability) {

case FOCUS_BLOCK_DESCENDANTS:

return super.requestFocus(direction, previouslyFocusedRect);

case FOCUS_BEFORE_DESCENDANTS: {

final boolean took = super.requestFocus(direction, previouslyFocusedRect);

return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);

}

case FOCUS_AFTER_DESCENDANTS: {

final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);

return took ? took : super.requestFocus(direction, previouslyFocusedRect);

}

}

}

ViewGroup的requestFocus会根据三种分发策略决定是自己先请求还是child先请求,最终都会调用View的requestFocus

View#requestFocus

View的requestFocus会调用到requestFocusNoSearch

在requestFocusNoSearch中做一些位运算后最终调用handleFocusGainInternal

void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {

if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {

mPrivateFlags |= PFLAG_FOCUSED;

View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;

if (mParent != null) {

mParent.requestChildFocus(this, this);

}

if (mAttachInfo != null) {

mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);

}

onFocusChanged(true, direction, previouslyFocusedRect);

refreshDrawableState();

}

}

handleFocusGainInternal中通知父容器自己请求了焦点,回调onFocusChanged,并刷新View

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值