android进阶(四)-----View的工作原理

79 阅读3分钟

前言:

好久没有发博客了,一直加班加到吐血,也是没谁了,最近也是互联网寒冬期,各大厂也都在裁员,提高自己才是正道啊。

一、ViewRoot和DecorView

ViewRoot对对应于ViewRootImpl类,他是连接WindowManager和DecorView的纽带,View的三大流程都是通过ViewRoot来完成的。

View的绘制流程是从ViewRoot的performTraversals方法开始的,经过measure、layout和draw三个过程才将View绘制出来。

二、理解MeasureSpec

MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize

SpecMode指的是测量模式

SpecMode三种测量模式

UNSPECIFIED(未指定模式):父容器不对View有任何限制,要多大给多大,一般用于系统内部,表示一种测量的状态

EXACTLY(精确值模式):父容器检测出View需要的精确大小,View的最终大小就是SpecSize所指定的值,他对应于LayoutParams中的match_parent和具体的数值

AT_MOST(最大值模式):父容器指定一个可用大小,View的大小不能大于这个值。对应于LayoutParams中的wrap_content

三、View的工作流程

1、View的工作流程主要是指measure、layout、draw三大流程

measure(测量):确定View的测量宽/高

layout(布局):layout确定View的最终宽/高和四个顶点的位置

draw(绘制):负责将View绘制在屏幕上

2、measure测量过程:View的measure过程和ViewGrop的measure过程

(1)View的measure过程:直接通过measure方法就完成了测量过程

protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
    super.onMeasure(widthMeasureSpec,heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 
    if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
        setMeasuredDimension(mWidth,mHeight)
    }else if (widthSpecMode == MeasureSpec.AT_MOST){
        setMeasuredDimension(mWidth,heightSpecSize)
    }else if (heightSpecMode == MeasureSpec.AT_MOST){
        setMeasuredDimension(widthSpecSize,mHeight)
    } 
    setMeasuredDimension(
        getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),
        getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec)
    )
}
public static int getDefaultSize(int size,int measureSpec){
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    switch(specMode){
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result;
}

(2)ViewGroup的measure过程:完成自己的measure过程,还需要遍历去调用所有子元素的measure方法,各子元素递归去执行这个过程。ViewGroup是一个抽象类,没有重写View的onMeasure方法,但是他提供了一个measureChildren的方法

protected void measureChildren(int widthMeasureSpec,int heightMeasureSpec){
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for(int i = 0; i < size ; i++){
        final View child = children[i];
        if((child.mViewFlags & VISIBILITY_MASK) != GONE){
            measureChild(child,widthMeasureSpec,heightMeasureSpec);
        }
    }
}
protected void measureChild(View child,int parentWidthMeasureSpec,int parentHeightMeasureSpec){
    final LayoutParams lp = child.getLayoutParams();
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidth - MeasureSpec,mPaddingLeft + mPaddingRight,lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeight - MeasureSpec,mPaddingTop + mPaddingBottom,lp.height);
    child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
}

3、layout过程:layout是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有子元素并调用layout方法,在layout方法中onLayout方法又会被调用。

protected void onLayout(boolean changed,int l,int t ,int r,int b){
    if(mOrientation == VERTICAL){
        layoutVertical(l,t,r,b);
    }else{
        layoutHorizontal(l,t,r,b);
    }
}
private void layoutVertical(int left,int top,int right,int bottom){
    final int count = getVirtualChildCount();
    for(int i= 0;i<count;i++){
        final View child = getVirtualChildAt(i);
        if(child == null){
            childTop += measureNullChild(i);
        }else if(child.getVisibility()!= GONE){
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeidht();
            final LinearLayout.LayoutParams lp = (LienarLayout.LayoutParams)child.getLayoutParams();
            if(hasDividerBeforeChildAt(i)){
                childTop += mDividerHeight;
            }
            childTop += lp.topMargin;setChildFrame(child,childLeft,childTop + getLocationOffset(child),childWidth,childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocation - Offset(child);
            i += getChildrenSkipCount(child,i);
        }
    }
}
private void setChildFrame(View child,int left,int top,int width,int height){
    child.layout(left,top,left+width,top+height)
}

4、draw过程

(1)View绘制过程步骤:

绘制背景、绘制自己、绘制children、绘制装饰

四:自定义View

1、自定义View注意事项

(1)让View支持wrap_content

(2)让View支持padding

(3)尽量不要在View中使用Handler

(4)View中如果有线程或动画,需要及时停止

(5)View带有滑动嵌套时,需要处理好滑动冲突

2、

继承View重写onDraw方法,代码实例

public class CircleView extends View{
    private int mColor = Color.RED;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    public CircleView(Context context){
        super(context);init()
    }
    public CircleView(Context context,AttributeSet attrs){
        this(context,attrs,0);
    }
    public CircleView(Context context,AttributeSet attrs,int defStyleAttr){
        super(context,attrs,defStyleAttr);
        TypedArray a =context.obtainStyledAttributes(attrs,R.styleable.CircleView);
        mColor = a.getColor(R.styleable.CircleView_circle_color,Color.RED);
        a.recycle();
        init();
    }
    private void init(){
        mPaint.setColor(mColor);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if(widthSpecMode == MeasureSpec.AT_MOST && heigitSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(200,200);
        }else if(widthSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(200,heightSpecSize);
        }else if(heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize,200);
        }
}    
    @Override
    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        int width = getWidth()-paddingLeft-paddingRight;
        int height = getHeight()-paddingTop - paddingBottom;
        int radius = Math.min(width,height)/2;
        canvas.drawCircle(paddingLeft+width/2,paddingTop+height/2,radius,mPaint);
    }
}

继承ViewGroup派生特殊的Layout:这种方式用于实现自定义的布局,需要处理ViewGroup的测量、布局,并同时处理子元素的测量和布局过程

代码实例:

public class HorizontalScrollViewEx extends ViewGroup{
    private int mChildrenSize;
    private int mChildWidth;
    private int mChildIndex;//分别记录上次滑动的坐标
    private int mLastX = 0;
    private int mLastY = 0;//分别记录上次滑动的坐标
    private int mLastXIntercept = 0;
    private int mLastYIntercept = 0;
    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;

    public HorizontalScrollViewEx(Context context){
        super(context);
        init();
    }
    public HorizontalScrollViewEx(Context context,AttributeSet attrs){
        super(context,attrs);
        init();
    }
    public HorizontalScrollViewEx(Context context,AttributeSet attrs,int defStyle){
        super(context,attrs,defStyle);
        init();
    }
    private void init(){
        if(mScroller == null){
            mScroller = new Scroller(getContext());
            mVelocityTracker = VelocityTracker.obtain();
        }
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event){
        boolean intercepted = false;
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch(event.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                if(!mScroller.isFinished()){
                    mScroller.abortAnimation();
                    intercepted = true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastXIntercept;
                int deltaY = y - mLastYIntercept;
                if(Math.abs(deltaX) > Math.abs(deltaY)){
                    intercepted = true;
                }else{
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
        }
        mLastX = x;
        mLastY = y;
        mLastXIntercept = x;
        mLastYIntercept = y;
        return intercepted;
    }
    @Override
    public boolean onTouchEvent(MotionEvent event){
        mVelocityTracker.addMovement(event);
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch(event.getAction()){
            case MotionEvent.ACTION_DOWN:
                if(!mScroller.isFinished()){
                    mScroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                scrollBy(-deltaX,0);
                break;
            case MotionEvent.ACTION_UP:
                int scrollX = getScrollX();
                mVelocityTracker.computeCurrentVelocity(1000);
                float xVelocity = mVelocityTracker.getXVelocity();
                if(Math.abs(xVelocity) >= 50){
                    mChildIndex = xVelocity >0 ?mChildIndex - 1 : mChildindex + 1;
                }else{
                    mChildIndex = (scrollX + mChildWidth / 2)/mChildWidth;
                }
                mChildIndex = Math.max(0,Math.min(mChildIndex,mChildrenSize - 1));
                int dx = mChildIndex * mChildWidth - scrollX;smoothScrollBy(dx,0);
                mVelocityTracker.clear();
                break;
        }
        mLastX = x;
        mLastY = y;
        return true;
    }
    @Override
    protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);
        int measuredWidth = 0;
        int measuredHeight = 0;
        final int childCount = getChildCount();
        measureChildren(widthMeasureSpec,heightMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        if(childCount ==0){
            setMeasuredDimension(0,0);
        } else if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            final View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth()*childCount;
            measuredHeight = childView.getMeasuredHeight();
            setMeasuredDimension(measuredWidth,measuredHeight);
        }else if(heightSpecMode == MeasureSpec.AT_MOST){
            final View childView = getChildAt(0);
            measuredHeight = childView.getMeasuredHeight();
            setMeasuredDimension(widthSpaceSize,childView.getMeasuredHeight());
        }else if(widthSpecMode == MeasureSpec.AT_MOST){
            final View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth()*childCount;
            setMeasuredDimension(measuredWidth,heightSpaceSize);
        }
    }
    @Override
    protected void onLayout(boolean changed,int l,int t,int r,int b){
        int childLeft = 0;
        final int childCount = getChildCount();
        mChildrenSize = childCount;
        for(int i=0;i<childCount;i++){
            final View childView = getChildAt(i);
            if(childView.getVisibility() != View.GONE){
                final int childWidth = childView.getMeasuredWidth();
                mChildWidth = childWidth;
                childView.layout(childLeft,0,childLeft + childWidth,childView.getMeasuredHeight());
                childLeft += childWidth;
            }
        }
    }
    private void smoothScrollBy(int dx,int dy){
        mScroller.startScroll(getScrollX(),0,dx,0,500);
        invalidate();
    }
    @Override
    public void computeScroll(){
        if(mScroller.computeScrollOffset()){
            scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
            postInvalidate();
        }
    }
    @Override
    protected void onDetachedFromWindow(){
        mVelocityTracker.recycle();
        super.onDetachedFormWindow();
    }
}