Androidベース-フォーカス配信ソース解析
20501 ワード
勝手なことを言う
現段階の携帯電話はほとんどタッチパネルで、ボタンは戻り、メニュー、ホームページキーしかありません(iosにはホームページキーが1つしかありません).したがって、コントロールのフォーカスの制御と配布はほとんど行われず、EditTextに対して特別な操作をする以外はフォーカスの問題を考慮しません.しかし、私たちはやはり理解しなければなりません.そうしないと、焦点の問題に遭遇すると、二丈和尚は頭がつかめません.では、これから私と一緒に勉強しましょう.
コントロールにフォーカスを設定する方法
Androidの一般的なコントロールではデフォルトではフォーカスを取得できませんが、いくつかの特殊なコントロールGoogleのお父さんはフォーカス制御を設定しています(
このコントロールが初めてクリックされたときに
解決策は遠くなったが、自分でGoogleを見たり、
フォーカス配布
リモコンのボタンを押すと、まずこの方法に入ります.
ソースコードは長いですが、2つの点に注意すればいいです.
first
私たちのviewの
ビューの焦点は彼自身であり、彼は親の を伝えない.当mFocused!=Nullは、mFocusedの です.
second
この方法は,次の焦点配布を処理することである.
しきべつキー
上の半分のコードは主にボタンを処理した方向です.
フォーカスを取得したビューの取得
方向キーを押すと、まずフォーカスを取得したView
Viewの自分がフォーカスを取得したら自分に戻ります.そうしないとnullに戻ります.
ViewGroupのまず、自分がフォーカス状態を取得すると、自分に戻る. 当mFocused!=Nullは、フォーカスを取得したビューを取得するために、フォーカスを取得するサブビューの
findFocus() == Null
ViewGroupの
mDefaultFocusは、デフォルトのフォーカスを取得するViewです.彼が空ではなく、見えると
Viewの
デフォルトでは、DecorViewであるため、次のフォーカスを要求します.だから基本的にページの一番上のViewです.
次に取得したフォーカスのビューを方向に基づいて検索
現在フォーカスを取得しているViewが空でない場合、次のフォーカスを取得すべきViewがどの
Viewの
親コントロールが空でない場合、親レイアウトの
ViewGroupの
親レイアウトの
まず、有効なルートレイアウトのView Groupを取得します.focusedは空ではないに違いないので、入ります
ここで、ユーザが設定した次のフォーカスのビューを取得するために、取得済みフォーカスViewの
ここでmNextFocusLeftId、mNextFocusRightId、mNextFocusUpId、mNextFocusDownId、mNextFocusForwardIdは、ユーザがxmlに設定した
ここでは、ルートViewの
ここでは、次のフォーカスを取得すべきViewをルートViewまで検索します.
Viewの
ViewGroupの
MatchIdPredicate
最後の一歩
次のフォーカスが検出されたViewは空ではなく、フォーカスが取得されていないViewは
結論を出すボタンのブロック操作を行う必要がある場合は、 ボタンを押すと、焦点がどこに行ったのか、焦点が「失われた」と感じたりすることがあります.すでに覆われているViewに焦点を当てられた可能性があります.だから、すでに覆われているViewをGONEに設定して、焦点が暴走しないようにしなければなりません.
小細工
この文章が好きならいいねを押すを覚えておいてくださいね~
現段階の携帯電話はほとんどタッチパネルで、ボタンは戻り、メニュー、ホームページキーしかありません(iosにはホームページキーが1つしかありません).したがって、コントロールのフォーカスの制御と配布はほとんど行われず、EditTextに対して特別な操作をする以外はフォーカスの問題を考慮しません.しかし、私たちはやはり理解しなければなりません.そうしないと、焦点の問題に遭遇すると、二丈和尚は頭がつかめません.では、これから私と一緒に勉強しましょう.
コントロールにフォーカスを設定する方法
Androidの一般的なコントロールではデフォルトではフォーカスを取得できませんが、いくつかの特殊なコントロールGoogleのお父さんはフォーカス制御を設定しています(
Button
、EditText
...一般的なコントロールでフォーカスを取得するにはandroid:focusable="true"
or view.setFocusable(true)
が必要です.タッチしたい場合は、フォーカスandroid:focusableInTouchMode="true"
or view.setFocusableInTouchMode(true)
も取得します.このプロパティが同時に設定されている場合、最初のクリックでクリックイベントがトリガーされず、2番目のクリックでクリックイベントがトリガーされる理由に注意してください. // take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
このコントロールが初めてクリックされたときに
isFocusable() && isFocusableInTouchMode() && !isFocused()
が条件を満たすfocusTaken
がtrue
に設定されているので入らないことがわかります if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
解決策は遠くなったが、自分でGoogleを見たり、
Button
のソースコードを見たりして、彼らがどのように解決したのかを見てみましょう.フォーカス配布
リモコンのボタンを押すと、まずこの方法に入ります.
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;
}
int groupNavigationDirection = 0;
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {
groupNavigationDirection = View.FOCUS_FORWARD;
} else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),
KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {
groupNavigationDirection = View.FOCUS_BACKWARD;
}
}
// If a modifier is held, try to interpret the key as a shortcut.
if (event.getAction() == KeyEvent.ACTION_DOWN
&& !KeyEvent.metaStateHasNoModifiers(event.getMetaState())
&& event.getRepeatCount() == 0
&& !KeyEvent.isModifierKey(event.getKeyCode())
&& groupNavigationDirection == 0) {
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) {
if (groupNavigationDirection != 0) {
if (performKeyboardGroupNavigation(groupNavigationDirection)) {
return FINISH_HANDLED;
}
} else {
if (performFocusNavigation(event)) {
return FINISH_HANDLED;
}
}
}
return FORWARD;
}
ソースコードは長いですが、2つの点に注意すればいいです.
first
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
私たちのviewの
dispatchKeyEvent(event)
メソッドがtrueに戻ると、今回のボタンイベントが消費されます.では、まずViewのdispatchKeyEvent(event)
の方法を見てみましょう.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;
}
ビューの
OnKeyListener.onKey(this, event.getKeyCode(), event)
がtrueを返すとdispatchKeyEvent(event)
がtrueを返し、今回のキーイベントを消費することがわかります.次に、ViewGroupのdispatchKeyEvent(event)
の方法を見てみましょう.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;
}
dispatchKeyEvent(event)
を呼び出す.親が現在のボタンを消費したイベントをブロックしたとき、すぐに消費してdispatchKeyEvent(event)
を呼び出します.では、mFocusedはどこの聖神なのかと聞かれます.mFocusedは現在のView Groupで得られている焦点の子View second
if (performFocusNavigation(event)) {
return FINISH_HANDLED;
}
この方法は,次の焦点配布を処理することである.
private boolean performFocusNavigation(KeyEvent event) {
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 true;
}
}
// Give the focused view a last chance to handle the dpad key.
if (mView.dispatchUnhandledMove(focused, direction)) {
return true;
}
} else {
if (mView.restoreDefaultFocus()) {
return true;
}
}
}
return false;
}
しきべつキー
上の半分のコードは主にボタンを処理した方向です.
フォーカスを取得したビューの取得
方向キーを押すと、まずフォーカスを取得したView
View focused = mView.findFocus()
mViewを取得します.ここでは最上位のDecorViewです.Viewの
findFocus()
public View findFocus() {
return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;
}
ViewGroupの
findFocus()
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;
}
findFocus()
メソッドを呼び出す.findFocus() == Null
findFocus()
メソッドでViewを取得しなかった場合(ページにViewがフォーカスを取得していない場合).デフォルトのフォーカスmView.restoreDefaultFocus()
を取得します.ここでmViewはDecorViewなので、ViewGroupのrestoreDefaultFocus()
を呼び出します.ViewGroupの
restoreDefaultFocus()
public boolean restoreDefaultFocus() {
if (mDefaultFocus != null
&& getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
&& (mDefaultFocus.mViewFlags & VISIBILITY_MASK) == VISIBLE
&& mDefaultFocus.restoreDefaultFocus()) {
return true;
}
return super.restoreDefaultFocus();
}
mDefaultFocusは、デフォルトのフォーカスを取得するViewです.彼が空ではなく、見えると
restoreDefaultFocus()
を呼び出します.消費したら渡さない、そうでなければ親(View)のrestoreDefaultFocus()
を呼び出すViewの
restoreDefaultFocus()
public boolean restoreDefaultFocus() {
return requestFocus(View.FOCUS_DOWN);
}
デフォルトでは、DecorViewであるため、次のフォーカスを要求します.だから基本的にページの一番上のViewです.
次に取得したフォーカスのビューを方向に基づいて検索
現在フォーカスを取得しているViewが空でない場合、次のフォーカスを取得すべきViewがどの
View v = focused.focusSearch(direction)
であるかを方向別に検索します.Viewの
focusSearch(direction)
public View focusSearch(@FocusRealDirection int direction) {
if (mParent != null) {
return mParent.focusSearch(this, direction);
} else {
return null;
}
}
親コントロールが空でない場合、親レイアウトの
focusSearch(view, direction)
が呼び出され、次のフォーカスのViewが取得され、空の場合は空に戻ります.ViewGroupの
focusSearch(view, direction)
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, direction)
がルートレイアウトに呼び出され、FocusFinder.getInstance().findNextFocus(this, focused, direction)
が次の取得フォーカスのViewを取得するまで呼び出されます.private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
View next = null;
ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
if (focused != null) {
next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
}
if (next != null) {
return next;
}
ArrayList focusables = mTempList;
try {
focusables.clear();
effectiveRoot.addFocusables(focusables, direction);
if (!focusables.isEmpty()) {
next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
}
} finally {
focusables.clear();
}
return next;
}
まず、有効なルートレイアウトのView Groupを取得します.focusedは空ではないに違いないので、入ります
private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
// check for user specified next focus
View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
while (userSetNextFocus != null) {
if (userSetNextFocus.isFocusable()
&& userSetNextFocus.getVisibility() == View.VISIBLE
&& (!userSetNextFocus.isInTouchMode()
|| userSetNextFocus.isFocusableInTouchMode())) {
return userSetNextFocus;
}
userSetNextFocus = userSetNextFocus.findUserSetNextFocus(root, direction);
}
return null;
}
ここで、ユーザが設定した次のフォーカスのビューを取得するために、取得済みフォーカスViewの
findUserSetNextFocus(root, direction)
が呼び出される.このビューがフォーカスを取得することができ、表示され、モードがタッチされず、タッチモードであり、タッチ可能である場合、フォーカスを取得して次のフォーカスとしてのビューに戻る.そうでなければ、取得したばかりのViewのfindUserSetNextFocus(root, direction)
を呼び出して次の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() {
@Override
public boolean test(View t) {
return t.mNextFocusForwardId == id;
}
});
}
}
return null;
}
ここでmNextFocusLeftId、mNextFocusRightId、mNextFocusUpId、mNextFocusDownId、mNextFocusForwardIdは、ユーザがxmlに設定した
android:nextFocusLeft="" android:nextFocusRight="" android:nextFocusUp="" android:nextFocusDown="" android:nextFocusForward=""
のidである.private View findViewInsideOutShouldExist(View root, int id) {
if (mMatchIdPredicate == null) {
mMatchIdPredicate = new MatchIdPredicate();
}
mMatchIdPredicate.mId = id;
View result = root.findViewByPredicateInsideOut(this, mMatchIdPredicate);
if (result == null) {
Log.w(VIEW_LOG_TAG, "couldn't find view with id " + id);
}
return result;
}
ここでは、ルートViewの
findViewByPredicateInsideOut(this, mMatchIdPredicate)
を呼び出して次の取得フォーカスのViewを取得するpublic final T findViewByPredicateInsideOut(
View start, Predicate predicate) {
View childToSkip = null;
for (;;) {
T view = start.findViewByPredicateTraversal(predicate, childToSkip);
if (view != null || start == this) {
return view;
}
ViewParent parent = start.getParent();
if (parent == null || !(parent instanceof View)) {
return null;
}
childToSkip = start;
start = (View) parent;
}
}
ここでは、次のフォーカスを取得すべきViewをルートViewまで検索します.
Viewの
findViewByPredicateTraversal(predicate, childToSkip)
protected T findViewByPredicateTraversal(Predicate predicate,
View childToSkip) {
if (predicate.test(this)) {
return (T) this;
}
return null;
}
ViewGroupの
findViewByPredicateTraversal(predicate, childToSkip)
protected T findViewByPredicateTraversal(Predicate predicate,
View childToSkip) {
if (predicate.test(this)) {
return (T) this;
}
final View[] where = mChildren;
final int len = mChildrenCount;
for (int i = 0; i < len; i++) {
View v = where[i];
if (v != childToSkip && (v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewByPredicate(predicate);
if (v != null) {
return (T) v;
}
}
}
return null;
}
MatchIdPredicate
private static class MatchIdPredicate implements Predicate {
public int mId;
@Override
public boolean test(View view) {
return (view.mID == mId);
}
}
最後の一歩
次のフォーカスが検出されたViewは空ではなく、フォーカスが取得されていないViewは
requestFocus(direction, mTempRect)
で音声を再生します.結論を出す
dispatchKeyEvent(event)
を書き換えることができます.setOnKeyLinstener
も直接設置されています.小細工
ViewRootImpl
の4643-4645行のブレークポイントで、現在フォーカスを取得しているViewと、次にフォーカスを取得すべきViewを見ることができます. View focused = mView.findFocus();
if (focused != null) {
View v = focused.focusSearch(direction);
この文章が好きならいいねを押すを覚えておいてくださいね~