Android-SDK源码阅读系列-02-View.java

View 作为 Android UI 控件的 基础控件里面包含了重要的渲染逻辑 点击事件传递逻辑等,因为是基础控件,代码依赖比较少阅读起来也比较简单。
image-20190706132734896

1. findViewById的真实实现逻辑

protected <T extends View> T findViewTraversal(@IdRes int id) {
  if (id == mID) {
    return (T) this;
  }
  return null;
}

@Nullable
public final <T extends View> T findViewById(@IdRes int id) {
  if (id == NO_ID) {
    return null;
  }
  return findViewTraversal(id);
}

@NonNull
public final <T extends View> T requireViewById(@IdRes int id) {
  T view = findViewById(id);
  if (view == null) {
    throw new IllegalArgumentException("ID does not reference a View inside this View");
  }
  return view;
}

总结:

View 类中 findViewById() 方法直接调用了 findViewTraversal() 方法,在此方法里匹配入参 id 值,一旦匹配直接返回此 View 示例自己,至此完成了 findViewById() 方法。

2. 点击事件的实现逻辑

image-20190706150009431

image-20190706150320073

public void setOnClickListener(@Nullable OnClickListener l) {
  if (!isClickable()) {
    setClickable(true);
  }
  getListenerInfo().mOnClickListener = l;
}

public interface OnClickListener {
  void onClick(View v);
}

public boolean onTouchEvent(MotionEvent event) {
  final float x = event.getX();
  final float y = event.getY();
  final int viewFlags = mViewFlags;
  final int action = event.getAction();
  final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                             || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
  if ((viewFlags & ENABLED_MASK) == DISABLED) {
    if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
      setPressed(false);
    }
    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
    return clickable;
  }
  // 整个View类的点击时间传递在这5行代码里清晰可见。
  if (mTouchDelegate != null) {
    if (mTouchDelegate.onTouchEvent(event)) {
      return true;
    }
  }
  if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
    switch (action) {
      case MotionEvent.ACTION_UP:
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        if ((viewFlags & TOOLTIP) == TOOLTIP) {
          handleTooltipUp();
        }
        if (!clickable) {
          removeTapCallback();
          removeLongPressCallback();
          mInContextButtonPress = false;
          mHasPerformedLongPress = false;
          mIgnoreNextUpEvent = false;
          break;
        }
        boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
        if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
          boolean focusTaken = false;
          if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
            focusTaken = requestFocus();
          }
          if (prepressed) {
            setPressed(true, x, y);
          }
          if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
            removeLongPressCallback();
            if (!focusTaken) {
              if (mPerformClick == null) {
                mPerformClick = new PerformClick();
              }
              if (!post(mPerformClick)) {
                performClickInternal(); // 重点代码
              }
            }
          }
          if (mUnsetPressedState == null) {
            mUnsetPressedState = new UnsetPressedState();
          }
          if (prepressed) {
            postDelayed(mUnsetPressedState,
                        ViewConfiguration.getPressedStateDuration());
          } else if (!post(mUnsetPressedState)) {
            mUnsetPressedState.run();
          }
          removeTapCallback();
        }
        mIgnoreNextUpEvent = false;
        break;
      case MotionEvent.ACTION_DOWN:
        if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
          mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
        }
        mHasPerformedLongPress = false;
        if (!clickable) {
          checkForLongClick(0, x, y);
          break;
        }
        if (performButtonActionOnTouchDown(event)) {
          break;
        }
        boolean isInScrollingContainer = isInScrollingContainer();
        if (isInScrollingContainer) {
          mPrivateFlags |= PFLAG_PREPRESSED;
          if (mPendingCheckForTap == null) {
            mPendingCheckForTap = new CheckForTap();
          }
          mPendingCheckForTap.x = event.getX();
          mPendingCheckForTap.y = event.getY();
          postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
        } else {
          setPressed(true, x, y);
          checkForLongClick(0, x, y);
        }
        break;
      case MotionEvent.ACTION_CANCEL:
        if (clickable) {
          setPressed(false);
        }
        removeTapCallback();
        removeLongPressCallback();
        mInContextButtonPress = false;
        mHasPerformedLongPress = false;
        mIgnoreNextUpEvent = false;
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        break;
      case MotionEvent.ACTION_MOVE:
        if (clickable) {
          drawableHotspotChanged(x, y);
        }
        if (!pointInView(x, y, mTouchSlop)) {
          removeTapCallback();
          removeLongPressCallback();
          if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
          }
          mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        }
        break;
    }
    return true;
  }
  return false;
}


private boolean performClickInternal() {
  notifyAutofillManagerOnClick();
  return performClick();
}

public boolean performClick() {
  notifyAutofillManagerOnClick();
  final boolean result;
  final ListenerInfo li = mListenerInfo;
  if (li != null && li.mOnClickListener != null) {
    playSoundEffect(SoundEffectConstants.CLICK);
    li.mOnClickListener.onClick(this);
    result = true;
  } else {
    result = false;
  }
  sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  notifyEnterOrExitForAutoFillIfNeeded(true);
  return result;
}

image-20190706133638397

总结:

View.java 通过 ListenerInfo 保存了几乎所有的监听者,其中就有内部接口 OnClickListener定义的 OnClickListener,通过 setOnClickListener 设置了监听者。在 OnTouchEvent 方法里 performClickInternal() 被调用最后调用到 performClick() 至此完成了 onClick() 方法的回调。

3. findViewById() 的延伸发现

有新的发现:

image-20190706141614730


   
  
   final <T extends View> T findViewByAccessibilityId(int accessibilityId) {
        if (accessibilityId < 0) {
            return null;
        }
        T view = findViewByAccessibilityIdTraversal(accessibilityId);
        if (view != null) {
            return view.includeForAccessibility() ? view : null;
        }
        return null;
    }

    public <T extends View> T findViewByAccessibilityIdTraversal(int accessibilityId) {
        if (getAccessibilityViewId() == accessibilityId) {
            return (T) this;
        }
        return null;
    }


   public <T extends View> T findViewByAutofillIdTraversal(int autofillId) {
        if (getAutofillViewId() == autofillId) {
            return (T) this;
        }
        return null;
    }
    
   public final <T extends View> T findViewByPredicate(Predicate<View> predicate) {
        return findViewByPredicateTraversal(predicate, null);
    }

   protected <T extends View> T findViewByPredicateTraversal(Predicate<View> predicate,
            View childToSkip) {
        if (predicate.test(this)) {
            return (T) this;
        }
        return null;
    }


    public final <T extends View> T findViewByPredicateInsideOut(
            View start, Predicate<View> 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 的方法就这里几个,通过 id, Pridicate, accessbilityId, autoFillId。

猜想 Pridicate是测试用的,accessibility适用于辅助功能方面,autoFillId应该是系统自动分配的id。

4. View 类点击事件传递的关键代码被发现

image-20190706151308578

接下来我们看一下 mTouchDelegate 是什么东西,猜想是点击事件代理者。

public void setTouchDelegate(TouchDelegate delegate) {
  mTouchDelegate = delegate;
}

public TouchDelegate getTouchDelegate() {
  return mTouchDelegate;
}

在Android源码里给的注释如下:

image-20190706151556904

  1. postDelay() 方法

我们先来看看 postDelayed() 方法的全部内容.

[外链图片转存失败(img-yPlqTXIz-1562586125428)(/Users/panda8z/Library/Application Support/typora-user-images/image-20190708082319376.png)]

这里 getRunQueue() 方法 new 了一个新的 HandlerActionQueue, 这个类和 View.java 在同一目录下所以不用导包.

[外链图片转存失败(img-Rpe0raAi-1562586125430)(/Users/panda8z/Library/Application Support/typora-user-images/image-20190708082211775.png)]

那我们现在看一下 HandlerActionQueue中的 postDelayed() 方法做了什么事情吧.


package android.view;

import android.os.Handler;

import com.android.internal.util.GrowingArrayUtils;

public class HandlerActionQueue {
    private HandlerAction[] mActions;
    private int mCount;

    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

    public void removeCallbacks(Runnable action) {
        synchronized (this) {
            final int count = mCount;
            int j = 0;

            final HandlerAction[] actions = mActions;
            for (int i = 0; i < count; i++) {
                if (actions[i].matches(action)) {
                    // Remove this action by overwriting it within
                    // this loop or nulling it out later.
                    continue;
                }

                if (j != i) {
                    // At least one previous entry was removed, so
                    // this one needs to move to the "new" list.
                    actions[j] = actions[i];
                }

                j++;
            }

            // The "new" list only has j entries.
            mCount = j;

            // Null out any remaining entries.
            for (; j < count; j++) {
                actions[j] = null;
            }
        }
    }

    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }

    public int size() {
        return mCount;
    }

    public Runnable getRunnable(int index) {
        if (index >= mCount) {
            throw new IndexOutOfBoundsException();
        }
        return mActions[index].action;
    }

    public long getDelay(int index) {
        if (index >= mCount) {
            throw new IndexOutOfBoundsException();
        }
        return mActions[index].delay;
    }

  //HandlerAction内部类用来封装 Runnable的action和delay的时间
    private static class HandlerAction {
        final Runnable action;
        final long delay;

        public HandlerAction(Runnable action, long delay) {
            this.action = action;
            this.delay = delay;
        }

        public boolean matches(Runnable otherAction) {
            return otherAction == null && action == null
                    || action != null && action.equals(otherAction);
        }
    }
}

[外链图片转存失败(img-JMgXzdkt-1562586125432)(/Users/panda8z/Library/Application Support/typora-user-images/image-20190708081816982.png)]

发布了100 篇原创文章 · 获赞 2 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/panda_8/article/details/95090448