android焦點分析

  在tv端開發中,焦點處理是一個很是重要的技術。該篇主要是想整理相關知識。(本文檔依據sdk26進行分析)java

第一次尋焦

  在android的繪製流程中**ViewRootImpl#performTraversals()**起着關鍵的做用,而焦點狀態也會經過影響視圖的繪製。
  下面來看看android事如何進行第一次尋焦的android

private void performTraversals() {
    ......
    if (mFirst && sAlwaysAssignFocus) {
          // handle first focus request
          if (mView != null) {
              if (!mView.hasFocus()) {
                  mView.restoreDefaultFocus();
              }
          }
    }
    ......
}
複製代碼

  mView.restoreDefaultFocus()將會去查找當前試圖第一個可聚焦的View。將會執行requestFocus(int direction, Rect previouslyFocusedRect)。由於ViewGroup重寫了該方法,增長了是否攔截焦點處理的邏輯,下面咱們先來看看ViewGroup#requestFocus(int direction, Rect previouslyFocusedRect)ide

@Override
    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
        if (DBG) {
            System.out.println(this + " ViewGroup.requestFocus direction="
                    + direction);
        }
        int descendantFocusability = getDescendantFocusability();

        switch (descendantFocusability) {
            //攔截焦點,無論當前View是否被聚焦,子View必定獲取不到焦點。
            case FOCUS_BLOCK_DESCENDANTS:
                return super.requestFocus(direction, previouslyFocusedRect);
            //在子View以前判斷是否應被聚焦,若是爲false則會去判斷其子View
            case FOCUS_BEFORE_DESCENDANTS: {
                final boolean took = super.requestFocus(direction, previouslyFocusedRect);
                return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
            }
            // 在子View判斷焦點以後判斷
            case FOCUS_AFTER_DESCENDANTS: {
                final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
                return took ? took : super.requestFocus(direction, previouslyFocusedRect);
            }
            default:
                throw new IllegalStateException("descendant focusability must be "
                        + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
                        + "but is " + descendantFocusability);
        }
    }
複製代碼

  接着咱們來看看View#requestFocus(int direction, Rect previouslyFocusedRect)this

public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
   return requestFocusNoSearch(direction, previouslyFocusedRect);
}

private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
    // 判斷是否可被聚焦
    if ((mViewFlags & FOCUSABLE) != FOCUSABLE
            || (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
        return false;
    }

    // 判斷觸摸狀態下是否可被聚焦
    if (isInTouchMode() &&
        (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
           return false;
    }

    // 判斷父View是否須要攔截
    if (hasAncestorThatBlocksDescendantFocus()) {
        return false;
    }
    //執行聚焦操做
    handleFocusGainInternal(direction, previouslyFocusedRect);
    return true;
}

void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
    if (DBG) {
        System.out.println(this + " requestFocus()");
    }

    if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
        //設置聚焦標誌位
        mPrivateFlags |= PFLAG_FOCUSED;

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

        if (mParent != null) { 
            //通知父容器改變焦點View
            mParent.requestChildFocus(this, this);
            updateFocusedInCluster(oldFocus, direction);
        }

        if (mAttachInfo != null) {
            //全局監聽回調
            mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
        }
        //執行焦點變化與強制繪製
        onFocusChanged(true, direction, previouslyFocusedRect);
        refreshDrawableState();
    }
}
複製代碼

遙控器方向鍵後的尋焦邏輯

  首先會根據按鍵生成一個尋焦方向(能夠查看ViewRootImpl#ViewPostImeInputStage#processKeyEvent)spa

private int processKeyEvent(QueuedInputEvent q) {
            ......
            //上面是判斷是否處理當前按鍵,如dispatchKeyEvent返回true則不會執行下面的焦點邏輯

            // 根據當前事件生成一個尋焦方向。
            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,根據當前view查詢下一個方向的聚焦view
                    View focused = mView.findFocus();
                    if (focused != null) {
                    //因而可知,focusSearch 爲尋焦的主要方法,可從新該方法來修改焦點邏輯
                        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;
        }
複製代碼

下面可查看View#focusSearch與ViewGroup#focusSearch 的相關處理rest

View.java public View focusSearch(@FocusRealDirection int direction) {
        if (mParent != null) {
            return mParent.focusSearch(this, direction);
        } else {
            return null;
        }
    }
    
ViewGroup.java public View focusSearch(View focused, int direction) {
        if (isRootNamespace()) {
            // 經過FocusFinder來查找下一個聚焦view
            return FocusFinder.getInstance().findNextFocus(this, focused, direction);
        } else if (mParent != null) {
            return mParent.focusSearch(focused, direction);
        }
        return null;
    }

複製代碼

FocusFinder 是一個焦點處理的類,主要用於在一個方向上,經過當前view與聚焦方案來合理判斷下一個被聚焦viewcode

相關文章
相關標籤/搜索