View的工作原理

2018-09-26  本文已影响0人  要学的东西太多了
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int defautWidth=20,defaultHeight=20;//wrap_content下设置的默认值
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(defautWidth,defaultHeight);
        }else if(widthMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(defautWidth,heightSize);
        }else if(heightMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSize,defaultHeight);
        }else{
            super.onMeasure(widthMeasureSpec,heightMeasureSpec);
        }
    }
(1)onWindowFoucsChanged,不过要注意,这个方法会被频繁调用,典型代码如下:
    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if(hasFocus){
            int width = view.getMeasuredWidth();
            int heigt = view.getMeasuredHeight();
        }
    }

(2)view.post(runnable)。典型代码如下:
    @Override
    protected void onStart() {
        super.onStart();
        view.post(new Runnable(){
            @Override
            public void run() {
                int width = view.getMeasuredWidth();
                int heigt = view.getMeasuredHeight();
            }
        });
    }

(3)ViewTreeObserver。典型代码如下:
    @Override
    protected void onStart() {
        super.onStart();
        ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                int width = view.getMeasuredWidth();
                int heigt = view.getMeasuredHeight();
            }
        });
    }

(4)view.measure(int widthMeasureSpec , int heightMeasureSpec)。根据view的LayoutParams来处理,具体情况如下:
      1.match_parent,直接放弃,无法拿到父容器的剩余空间。
      2.具体数值,如下:
            int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
            int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY); 
            view.measure(widthMeasureSpec,heightMeasureSpec);
      3.wrap_content,如下:
            int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1, MeasureSpec.AT_MOST);
            int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1, MeasureSpec.AT_MOST);
            view.measure(widthMeasureSpec,heightMeasureSpec);
public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        getRunQueue().post(action);
        return true;
    }

下面分情况分析:

mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

getDefaultSize方法就是根据mode返回不同的size值,可以看出AT_MOST和EXACTLY都是返回的父容器可提供的最大值,UNSPECIFIED一般用于系统测量,这里返回的是传入的getSuggestedMinimumWidth,如下:

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;
    }

getSuggestedMinimumWidth里面返回的判断逻辑如下:

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

mMinWidth /mMinHeight ,即xml文件中的minWidth和minHeight,mBackground.getMinimumWidth()表示背景图片的实际大小,例如shape这一类背景是没有实际大小的,bitmap则有。
(2)如果有必要,支持padding。继承View的控件不在draw中处理padding的话,padding不起作用;继承ViewGroup的控件在onMeasure和onLayout中要考虑padding和子元素的margin带来的影响。
(3)尽量不要在View中使用handler,因为View本身提供了post系列方法。
(4)View中有线程和动画要及时停止,在onDetachedFromWindow方法中处理,否则可能内存泄漏。
(5)合理处理滑动冲突。

示例:
1.继承View:

public class CircleView extends View{
    private int defalultColor = Color.parseColor("#000000");
    private int circleColor = defalultColor;
    private float defalultRidus = 5.0f;
    private float ridus = defalultRidus;
    private Paint paint;
    private Context context;
    public CircleView(Context context) {
        this(context,null);
    }

    public CircleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        this.context=context;
        init(attrs);
    }

    private void init(AttributeSet attrs){
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        if(attrs!=null){
            TypedArray typedArray = context.getResources().obtainAttributes(attrs,R.styleable.CircleView);
            circleColor = typedArray.getColor(R.styleable.CircleView_circleColor, defalultColor);
            ridus = typedArray.getFloat(R.styleable.CircleView_ridus,defalultRidus);
            typedArray.recycle();
        }
        paint.setColor(circleColor);
        ridus = DisPlayUtils.dp2px(context,ridus);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int defautWidth=200,defaultHeight=200;//wrap_content下设置的默认值
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(defautWidth,defaultHeight);
        }else if(widthMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(defautWidth,heightSize);
        }else if(heightMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSize,defaultHeight);
        }else{
            super.onMeasure(widthMeasureSpec,heightMeasureSpec);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int width = getWidth()-getPaddingLeft()-getPaddingRight();
        int height = getHeight()-getPaddingBottom()-getPaddingTop();
        canvas.drawCircle(getPaddingLeft()+width/2,getPaddingTop()+height/2,ridus,paint);
    }
}

2.继承ViewGroup:

public class HorizontalScrollView extends ViewGroup{
    private Context context;
    private Scroller scroller;
    private VelocityTracker velocityTracker;//记录滑动速度
    private int lastX=0,lastY=0;//记录上次滑动的坐标
    private int lastInterceptX=0,lastInterceptY=0;//记录上次intercept中的坐标
    private int mChildIndex=0;
    private int mChildWidth=0;
    private int mChildSize = 0;
    public HorizontalScrollView(Context context) {
        this(context,null);
    }

    public HorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context=context;
        init();
    }

    private void init(){
        scroller = new Scroller(context);
        velocityTracker = VelocityTracker.obtain();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean isIntercept =false;
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                isIntercept = false;
                if(!scroller.isFinished()){
                    scroller.abortAnimation();
                    isIntercept=true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int delX = x-lastInterceptX;
                int delY = y-lastInterceptY;
                if(Math.abs(delX)>Math.abs(delY)){//横向移动的距离大于纵向的就拦截自己处理
                    isIntercept = true;
                }else{
                    isIntercept = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                isIntercept=false;
                break;
        }
        lastX = x;
        lastY = y;
        lastInterceptX = x;
        lastInterceptY = y;
        return isIntercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        velocityTracker.addMovement(event);
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                if(!scroller.isFinished()){
                    scroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int delX = x-lastX;
                scrollBy(-delX,0);
                break;
            case MotionEvent.ACTION_UP:
                int scrollX = getScrollX();
                velocityTracker.computeCurrentVelocity(1000);
                float xVelocity = velocityTracker.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,mChildSize-1));
                int dx = getTotalViewWidth() - scrollX;
                smoothScrollBy(dx,0);
                velocityTracker.clear();
                break;
        }
        lastX = x;
        lastY = y;
        return true;
    }

    private int getTotalViewWidth(){
        int total = 0;
        for(int i=0;i<mChildIndex;i++){
            View view = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
            total+=view.getMeasuredWidth()+lp.leftMargin+lp.rightMargin;
        }
        total += getPaddingLeft() + getPaddingRight();
        return total;
    }

    private void smoothScrollBy(int dx,int dy){
        scroller.startScroll(getScrollX(),0,dx,0,500);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if(scroller.computeScrollOffset()){
            scrollTo(scroller.getCurrX(),scroller.getCurrY());
            postInvalidate();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);
        int measureWidth = 0 , measureHeight = 0;
        int childCount = getChildCount();
        measureChildren(widthMeasureSpec,heightMeasureSpec);

        int widthSpedMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpedMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        if(childCount==0){
            if(widthSpedMode == MeasureSpec.AT_MOST && heightSpedMode == MeasureSpec.AT_MOST){
                setMeasuredDimension(paddingLeft+paddingRight,paddingTop+paddingBottom);
            }else if(widthSpedMode == MeasureSpec.AT_MOST){
                setMeasuredDimension(paddingLeft+paddingRight,heightSpecSize );
            }else if(heightSpedMode == MeasureSpec.AT_MOST){
                setMeasuredDimension(widthSpecSize ,paddingTop+paddingBottom);
            }else{
                setMeasuredDimension(widthSpecSize ,heightSpecSize );
            }
        }else{
            if(widthSpedMode == MeasureSpec.AT_MOST){
                for(int i=0;i<childCount;i++) {
                    View view = getChildAt(i);
                    MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
                    measureWidth+=view.getMeasuredWidth()+lp.leftMargin+lp.rightMargin;
                }
                measureWidth+=paddingLeft+paddingRight;
            }else{
                measureWidth = widthSpecSize;
            }
            if(heightSpedMode == MeasureSpec.AT_MOST){
                for(int i=0;i<childCount;i++) {
                    View view = getChildAt(i);
                    MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
                    measureHeight = Math.max(measureHeight,view.getMeasuredHeight()+lp.topMargin+lp.bottomMargin);
                }
                measureHeight+=paddingBottom+paddingTop;
            }else{
                measureHeight = heightSpecSize;
            }
            setMeasuredDimension(measureWidth,measureHeight);
        }
    }

    @Override
    protected void onLayout(boolean isChanged, int left, int top, int right, int bottom) {
        int mLeft = getPaddingLeft();
        int childCount = getChildCount();
        mChildSize = childCount;
        for(int n=0;n<childCount;n++){
            View childView = getChildAt(n);
            mChildWidth = childView.getMeasuredWidth();
            MarginLayoutParams lp =(MarginLayoutParams) childView.getLayoutParams();
            if(childView.getVisibility() != GONE){
                int childWidth = childView.getMeasuredWidth();
                int mTop = getPaddingTop()+lp.topMargin;
                mLeft+=lp.leftMargin;
                childView.layout(mLeft,mTop,mLeft+childWidth,mTop+childView.getMeasuredHeight());
                mLeft+=lp.rightMargin+childWidth;
            }
        }
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {//要支持margin必须重写这个方法
        return new MarginLayoutParams(context,attrs);
    }

    @Override
    protected void onDetachedFromWindow() {//释放资源,关闭线程等
        velocityTracker.recycle();
        super.onDetachedFromWindow();
    }
}
上一篇 下一篇

猜你喜欢

热点阅读