美文网首页android技术开发
源码学习记录-从android.view.View开始

源码学习记录-从android.view.View开始

作者: Zyao89 | 来源:发表于2017-04-11 19:25 被阅读2187次

    View是安卓一切视图的基础,我觉得先从这个点开始学习,向下扩展会让我更容易理解。

    //包
    package android.view;
    //类
    android.view.View
    

    1、 实现接口 Drawable.Callback , 用来创建动画绘制时,用来实现调度和动画修改操作的,分为以下三个接口:

    //绘制自身
    void invalidateDrawable(@NonNull Drawable who);
    //计划执行下一次动画
    void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when);
    //取消执行前面计划
    void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what);
    

    这个接口是在 setBackground(Drawable background) 中注册监听的。其实现如下:

        public void setBackground(Drawable background) {
            setBackgroundDrawable(background);
        }
    
        @Deprecated
        public void setBackgroundDrawable(Drawable background) {
            ...
            boolean requestLayout = false;
    
            mBackgroundResource = 0;
    
            //如果已有背景,如果此View绑定到了Window上,则先使其不可见。
            if (mBackground != null) {
                if (isAttachedToWindow()) {
                    mBackground.setVisible(false, false);
                }
                //然后再清空回调监听
                mBackground.setCallback(null);
                //再清空原背景的所有绘制任务
                unscheduleDrawable(mBackground);
            }
    
            if (background != null) {
                ...
                //设置当前背景的布局方向            
                background.setLayoutDirection(getLayoutDirection());
                //这里获取背景的padding值并根据布局方向设置布局参数。
                if (background.getPadding(padding)) {
                    resetResolvedPaddingInternal();
                    switch (background.getLayoutDirection()) {
                        case LAYOUT_DIRECTION_RTL:
                            mUserPaddingLeftInitial = padding.right;
                            mUserPaddingRightInitial = padding.left;
                            internalSetPadding(padding.right, padding.top, padding.left, padding.bottom);
                            break;
                        case LAYOUT_DIRECTION_LTR:
                        default:
                            mUserPaddingLeftInitial = padding.left;
                            mUserPaddingRightInitial = padding.right;
                            internalSetPadding(padding.left, padding.top, padding.right, padding.bottom);
                    }
                    mLeftPaddingDefined = false;
                    mRightPaddingDefined = false;
                }
    
                // 判断当前背景大小是否与上一次绘制的背景大小相等,不想等则标记为要重新绘制。
                if (mBackground == null
                        || mBackground.getMinimumHeight() != background.getMinimumHeight()
                        || mBackground.getMinimumWidth() != background.getMinimumWidth()) {
                    requestLayout = true;
                }
    
                //赋值
                mBackground = background;
                //判断是否需要状态更新
                if (background.isStateful()) {
                    background.setState(getDrawableState());
                }
                //判断是否已添加到窗口中
                if (isAttachedToWindow()) {
                    //设置是否可见
                    background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
                }
    
                applyBackgroundTint();
    
                // 最后设置监听
                background.setCallback(this);
    
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                    mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                    requestLayout = true;
                }
            } else {
                //这里的分支是做移除背景操作
                mBackground = null;
                if ((mViewFlags & WILL_NOT_DRAW) != 0
                        && (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
                    mPrivateFlags |= PFLAG_SKIP_DRAW;
                }
    
               //这里会请求布局一次
                requestLayout = true;
            }
    
            //再次计算一次透明度
            computeOpaqueFlags();
    
            //是否需要重新布局
            if (requestLayout) {
                requestLayout();
            }
    
            //标记背景大小发生改变
            mBackgroundSizeChanged = true;
    
            //绘制
            invalidate(true);
            invalidateOutline();
        }
    

    2、 实现接口 KeyEvent.Callback , 实现所有按键监听回调,接口如下:

    boolean onKeyDown(int keyCode, KeyEvent event);
    boolean onKeyLongPress(int keyCode, KeyEvent event);
    boolean onKeyUp(int keyCode, KeyEvent event);
    boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
    
    

    具体实现方式如下:

        public boolean onKeyDown(int keyCode, KeyEvent event) {
            if (KeyEvent.isConfirmKey(keyCode)) {//检测
                if ((mViewFlags & ENABLED_MASK) == DISABLED) {//检测是否标记为禁用
                    return true;
                }
    //判断是否可点击,或可长点击
                if (((mViewFlags & CLICKABLE) == CLICKABLE
                        || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                        && (event.getRepeatCount() == 0)) {
                    // 计算出中心点
                    final float x = getWidth() / 2f;
                    final float y = getHeight() / 2f;
                    setPressed(true, x, y);//按压
                    checkForLongClick(0, x, y);//检测是否为长点击
                    return true;
                }
            }
            return false;
        }
    
        public boolean onKeyUp(int keyCode, KeyEvent event) {
            if (KeyEvent.isConfirmKey(keyCode)) {
                if ((mViewFlags & ENABLED_MASK) == DISABLED) {
                    return true;
                }
                if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
                    setPressed(false);//取消按压状态
    
                    if (!mHasPerformedLongPress) {
                        // 如果有长按,则将长按回调移除
                        removeLongPressCallback();
                        return performClick();
                    }
                }
            }
            return false;
        }
    

    3、 下面开始从View的构造函数开始正是入手,部分源码如下:

    
        public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            this(context);//处理context赋值等操作
    
            final TypedArray a = context.obtainStyledAttributes(
                    attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
           
                ···
                //初始化XML参数
                ···
    
            a.recycle();
    
            // Needs to be called after mViewFlags is set
            if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) {
                recomputePadding();
            }
    
            if (x != 0 || y != 0) {//是否需要滚动
                scrollTo(x, y);
            }
    
            if (transformSet) {//设置一些偏移,旋转放大缩小等参数
                setTranslationX(tx);
                setTranslationY(ty);
                setTranslationZ(tz);
                setElevation(elevation);
                setRotation(rotation);
                setRotationX(rotationX);
                setRotationY(rotationY);
                setScaleX(sx);
                setScaleY(sy);
            }
    
            if (!setScrollContainer && (viewFlagValues&SCROLLBARS_VERTICAL) != 0) {
                setScrollContainer(true);//设置内容是否可以滚动
            }
        }
    

    以下 performClick() 用来实现OnClickListener操作,并作出一系列关联操作:

    public boolean performClick() {
            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);
            return result;
        }
    

    callOnClick() 只调用实现监听,并不做一系列相关操作:

        public boolean callOnClick() {
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnClickListener != null) {
                li.mOnClickListener.onClick(this);
                return true;
            }
            return false;
        }
    

    4、 这里有个厉害的了 onTouchEvent(MotionEvent event) ,处理触摸事件,通常自定义View都会重写它,这里来按下源码。

        public boolean onTouchEvent(MotionEvent event) {
            final float x = event.getX();
            final float y = event.getY();
            final int viewFlags = mViewFlags;
            final int action = event.getAction();
    
            if ((viewFlags & ENABLED_MASK) == DISABLED) {
                if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                    setPressed(false);
                }
             
                return (((viewFlags & CLICKABLE) == CLICKABLE
                        || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                        || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
            }
            if (mTouchDelegate != null) {//代理分发
                if (mTouchDelegate.onTouchEvent(event)) {
                    return true;
                }
            }
            //判断是否可用点击、长按、还是上下文点击
            if (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                    (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
                switch (action) {
                    case MotionEvent.ACTION_UP:
                      boolean focusTaken = false;//焦点获取
                            if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                                focusTaken = requestFocus();
                            }
    
                        ···
                             if (!post(mPerformClick)) {//分发点击事件
                                     performClick();
                             }
    
                        ...
                        break;
    
                    case MotionEvent.ACTION_DOWN:
                        if (isInScrollingContainer) {//这里会先判断是否为内部滚动事件
                            ...
                        } else {
                            // 如果不是则,直接反馈按压状态,并检测是否为长按操作
                            setPressed(true, x, y);
                            checkForLongClick(0, x, y);
                        }
                        break;
    
                    case MotionEvent.ACTION_CANCEL:
                        setPressed(false);//解除按压状态
                        removeTapCallback();移除回调
                        removeLongPressCallback();移除长按回调
                        //重置状态
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
    
                    case MotionEvent.ACTION_MOVE:
                   // Be lenient about moving outside of buttons
    //这里有个mTouchSlop,是通过ViewConfiguration.get(context).getScaledTouchSlop();得到的。
                        if (!pointInView(x, y, mTouchSlop)) {
                            // Outside button
                            removeTapCallback();
                            if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                                // Remove any future long press/tap checks
                                removeLongPressCallback();
    
                                setPressed(false);
                            }
                        }
                        break;
                }
    
                return true;
            }
    
            return false;
        }
    
    

    5、 其实我觉得最牛的方法应该是 void setFlags(int flags, int mask) 虽然他是包类方法,但是这个方法的作用太强大了,所有的标志位设置,几乎都经过它, flags 表示应该设置的值,mask 表示改变的值的范围,源码部分分析如下:

        void setFlags(int flags, int mask) {
            final boolean accessibilityEnabled =
                    AccessibilityManager.getInstance(mContext).isEnabled();
            final boolean oldIncludeForAccessibility = accessibilityEnabled && includeForAccessibility();
    
            int old = mViewFlags;//先临时保存一份旧的
            mViewFlags = (mViewFlags & ~mask) | (flags & mask);
    
            int changed = mViewFlags ^ old;//判断旧的和新的是否发生改变
            if (changed == 0) {
                return;//如果没有改变则返回
            }
            int privateFlags = mPrivateFlags;
    
            /* Check if the FOCUSABLE bit has changed */
            if (((changed & FOCUSABLE_MASK) != 0) &&
                    ((privateFlags & PFLAG_HAS_BOUNDS) !=0)) {//判断是否改变了焦点
               ...
            }
    
            final int newVisibility = flags & VISIBILITY_MASK;
            if (newVisibility == VISIBLE) {//是否为VISIBLE
                if ((changed & VISIBILITY_MASK) != 0) {
                    //是否发生改变
                    mPrivateFlags |= PFLAG_DRAWN;
                    invalidate(true);//重新绘制
                    ...
                }
            }
    
            /* Check if the GONE bit has changed */
            if ((changed & GONE) != 0) {
                needGlobalAttributesUpdate(false);
                requestLayout();
    
                ...
            }
    
            /* Check if the VISIBLE bit has changed */
            if ((changed & INVISIBLE) != 0) {
                needGlobalAttributesUpdate(false);
                ...
            }
    
            if ((changed & VISIBILITY_MASK) != 0) {
                // If the view is invisible, cleanup its display list to free up resources
                if (newVisibility != VISIBLE && mAttachInfo != null) {
                    cleanupDraw();
                }
    
                if (mParent instanceof ViewGroup) {
                    ((ViewGroup) mParent).onChildVisibilityChanged(this,
                            (changed & VISIBILITY_MASK), newVisibility);
                    ((View) mParent).invalidate(true);
                } else if (mParent != null) {
                    mParent.invalidateChild(this, null);
                }
                ...
            }
    
            //后面是针对其它标记位进行判断
    
            if ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
                destroyDrawingCache();
            }
    
            if ((changed & DRAWING_CACHE_ENABLED) != 0) {
                destroyDrawingCache();
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
                invalidateParentCaches();
            }
    
            if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) {
                destroyDrawingCache();
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }
    
            if ((changed & DRAW_MASK) != 0) {
                if ((mViewFlags & WILL_NOT_DRAW) != 0) {
                    if (mBackground != null
                            || (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {
                        mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                    } else {
                        mPrivateFlags |= PFLAG_SKIP_DRAW;
                    }
                } else {
                    mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                }
                requestLayout();
                invalidate(true);
            }
    
            if ((changed & KEEP_SCREEN_ON) != 0) {
                if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
                    mParent.recomputeViewAttributes(this);
                }
            }
    
            if (accessibilityEnabled) {
                ...
                //通知
            }
        }
    

    6、 bringToFront() 将视图置顶,这个方法有时也很常用,我们来看下它的内部,调用父类方法,如下:

        public void bringChildToFront(View child) {
            final int index = indexOfChild(child);//查询孩子的脚标
            if (index >= 0) {//判断是否存在
                removeFromArray(index);//先移除,然后再添加进来
                addInArray(child, mChildrenCount);
                child.mParent = this;
                requestLayout();//重新布局
                invalidate();//重新绘制
            }
        }
    

    7、 dispatchAttachedToWindow(AttachInfo info, int visibility) 这个方法应该是当View绑定上窗口时,系统自动调用的。部分分析如下:

        void dispatchAttachedToWindow(AttachInfo info, int visibility) {
            mAttachInfo = info;
            if (mOverlay != null) {
                mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
            }
            mWindowAttachCount++;记录绑定次数
    
            ...
    
            // Transfer all pending runnables.
            if (mRunQueue != null) {对绑定前操作的所有任务队列,在这里进行统一处理。
                mRunQueue.executeActions(info.mHandler);
                mRunQueue = null;
            }
            performCollectViewAttributes(mAttachInfo, visibility);
            onAttachedToWindow();//调用此方法,View子类都可重写此方法。
    
            ListenerInfo li = mListenerInfo;
            final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                    li != null ? li.mOnAttachStateChangeListeners : null;
            if (listeners != null && listeners.size() > 0) {
                //通知监听
                for (OnAttachStateChangeListener listener : listeners) {
                    listener.onViewAttachedToWindow(this);
                }
            }
    
            int vis = info.mWindowVisibility;
            if (vis != GONE) {
                onWindowVisibilityChanged(vis);
                if (isShown()) {
                    // Calling onVisibilityAggregated directly here since the subtree will also
                    // receive dispatchAttachedToWindow and this same call
                    onVisibilityAggregated(vis == VISIBLE);
                }
            }
    
            // Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
            // As all views in the subtree will already receive dispatchAttachedToWindow
            // traversing the subtree again here is not desired.
            onVisibilityChanged(this, visibility);
    
            if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
                // If nobody has evaluated the drawable state yet, then do it now.
                refreshDrawableState();
            }
            needGlobalAttributesUpdate(false);
        }
    

    8、 相对绑定,肯定会有个解绑 dispatchDetachedFromWindow() ,如下:

        void dispatchDetachedFromWindow() {
            AttachInfo info = mAttachInfo;
            if (info != null) {
                int vis = info.mWindowVisibility;
                if (vis != GONE) {
                    onWindowVisibilityChanged(GONE);
                    if (isShown()) {
                        // Invoking onVisibilityAggregated directly here since the subtree
                        // will also receive detached from window
                        onVisibilityAggregated(false);
                    }
                }
            }
    
            onDetachedFromWindow();//对外继承使用
            onDetachedFromWindowInternal();//内部调用
    
            ...
    
            //其它通知
        }
    

    9、 实在是看不下去了,最后来讨论下 getViewTreeObserver(), 这是一个注册监听视图树的观察者(observer),在视图树种全局事件改变时得到通知。这个全局事件不仅还包括整个树的布局,从绘画过程开始,触摸模式的改变等。

    内部接口

        interface  ViewTreeObserver.OnGlobalFocusChangeListener         
      //当在一个视图树中的焦点状态发生改变时,所要调用的回调函数的接口类
     
      interface  ViewTreeObserver.OnGlobalLayoutListener
      //当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时,所要调用的回调函数的接口类
     
      interface  ViewTreeObserver.OnPreDrawListener
      //当一个视图树将要绘制时,所要调用的回调函数的接口类
     
      interface  ViewTreeObserver.OnScrollChangedListener
      //当一个视图树中的一些组件发生滚动时,所要调用的回调函数的接口类
     
      interface  ViewTreeObserver.OnTouchModeChangeListener
      //当一个视图树的触摸模式发生改变时,所要调用的回调函数的接口类
    

    可使用的公共方法

    /**注册一个回调函数,当在一个视图树中的焦点状态发生改变时调用这个回调函数。
      * 参数 listener    将要被添加的回调函数
      *异常 IllegalStateException       如果isAlive() 返回false
      */
     public void addOnGlobalFocusChangeListener (ViewTreeObserver.OnGlobalFocusChangeListener listener)
         
     
     /**注册一个回调函数,当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时调用这个回调函数。
      *参数 listener    将要被添加的回调函数
      *异常 IllegalStateException       如果isAlive() 返回false
      */
     public void addOnGlobalLayoutListener (ViewTreeObserver.OnGlobalLayoutListener listener)
      
     
      
     /**注册一个回调函数,当一个视图树将要绘制时调用这个回调函数。
      *参数 listener    将要被添加的回调函数
      *异常 IllegalStateException       如果isAlive() 返回false
      */
     public void addOnPreDrawListener (ViewTreeObserver.OnPreDrawListener listener)
     
        
     /**注册一个回调函数,当一个视图发生滚动时调用这个回调函数。
      *参数 listener    将要被添加的回调函数
      *异常 IllegalStateException       如果isAlive() 返回false
      */
     public void addOnScrollChangedListener (ViewTreeObserver.OnScrollChangedListener listener)  
     
     
     /**注册一个回调函数,当一个触摸模式发生改变时调用这个回调函数。
      *参数 listener    将要被添加的回调函数
      *异常 IllegalStateException       如果isAlive() 返回false
      */
     public void addOnTouchModeChangeListener (ViewTreeObserver.OnTouchModeChangeListener listener)
     
      
     //当整个布局发生改变时通知相应的注册监听器。如果你强制对视图布局或者在一个没有附加到一个窗口的视图的层次结构或者在GONE状态下,它可以被手动的调用
     public final void dispatchOnGlobalLayout ()
        
     /**当一个视图树将要绘制时通知相应的注册监听器。如果这个监听器返回true,则这个绘制将被取消并重新计划。如果你强制对视图布局或者在一个没有附加到一个窗口的视图的层次结构或者在一个GONE状态下,它可以被手动的调用
      *返回值  当前绘制能够取消并重新计划则返回true,否则返回false。
      */
     public final boolean dispatchOnPreDraw ()
     
     /**指示当前的ViewTreeObserver是否可用(alive)。当observer不可用时,任何方法的调用(除了这个方法)都将抛出一个异常。如果一个应用程序保持和ViewTreeObserver一个历时较长的引用,它应该总是需要在调用别的方法之前去检测这个方法的返回值。
      *返回值 但这个对象可用则返回true,否则返回false   
      */
     public boolean isAlive ()
        
         
     /**移除之前已经注册的全局布局回调函数。
      *参数 victim 将要被移除的回调函数
      *异常 IllegalStateException       如果isAlive() 返回false   
      */
     public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)
      
     /**移除之前已经注册的焦点改变回调函数。
      *参数 victim 将要被移除的回调函数
      *异常 IllegalStateException       如果isAlive() 返回false 
      */
     public void removeOnGlobalFocusChangeListener (ViewTreeObserver.OnGlobalFocusChangeListener victim)
      
     /**移除之前已经注册的预绘制回调函数。
      *参数 victim 将要被移除的回调函数
      *异常 IllegalStateException       如果isAlive() 返回false  
      */
     public void removeOnPreDrawListener (ViewTreeObserver.OnPreDrawListener victim)
      
     /**移除之前已经注册的滚动改变回调函数。
      *参数 victim 将要被移除的回调函数
      *异常 IllegalStateException       如果isAlive() 返回false 
      */
     public void removeOnScrollChangedListener (ViewTreeObserver.OnScrollChangedListener victim)
     
     /**移除之前已经注册的触摸模式改变回调函数
      *参数 victim 将要被移除的回调函数
      *异常  IllegalStateException       如果isAlive() 返回false
      */
     public void removeOnTouchModeChangeListener (ViewTreeObserver.OnTouchModeChangeListener victim)
    

    作者:Zyao89;转载请保留此行,谢谢;

    个人博客:http://zyao89.me

    相关文章

      网友评论

        本文标题:源码学习记录-从android.view.View开始

        本文链接:https://www.haomeiwen.com/subject/uqqpottx.html