View绘制

文章参考
文章参考

View树结构

在这里插入图片描述
每一个视图的绘制过程都必须经历三个最主要的阶段:onMeasure()、onLayout()、onDraw()

系统内部会依次调用DecorView的measure(),layout()和draw()三大流程方法。
measure()方法又会调用onMeasure()方法对它所有的子元素进行测量,如此反复调用下去就能完成整个View树的遍历测量。
同样的,layout()和draw()两个方法里也会调用相似的方法去对整个View树进行遍历布局和绘制。

测量规格MeasureSpec

MeasureSpec类:

  public static class MeasureSpec {
  
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        
        public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

       
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }

       
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
    }

widthMeasureSpec和heightMeasureSpec是32位的int类型,,它的高2位代表测量模式Mode,低30位代表测量大小Size。

测量模式三类:

unspecified父容器不对 view 有任何限制,要多大给多大
exactly父容器已经检测出 view 所需要的大小
at_most父容器指定了一个大小, view 的大小不能大于这个值

绘制过程

1、measure()

    /*
    * final 标识符 , 不能被重载
    */
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        //回调onMeasure()方法  
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...
    }
   //回调View视图里的onMeasure过程
   private void onMeasure(int height , int width){
	   //设置该view的实际宽(mMeasuredWidth)高(mMeasuredHeight)
	   //1、该方法必须在onMeasure调用,否者报异常。
	   setMeasuredDimension(h , l) ;
	   
	   //2、如果该View是ViewGroup类型,则对它的每个子View进行measure()过程
	   int childCount = getChildCount() ;
	   
	   for(int i=0 ;i<childCount ;i++){
		   //获得每个子View对象引用
		   View child = getChildAt(i) ;
		   
		   //整个measure()过程就是个递归过程
		   //该方法只是一个过滤器,最后会调用measure()过程 ;或者 measureChild(child , h, i)方法
		   measureChildWithMargins(child , h, i) ; 
		   
		   //其实,对于我们自己写的应用来说,最好的办法是去掉框架里的该方法,直接调用view.measure(),如下:
		   //child.measure(h, l)
	   }
   }
	//该方法具体实现在ViewGroup.java里 。
   protected  void measureChildWithMargins(View v, int height , int width){
	   v.measure(h,l)   
   }

2、layout()

   /* final 标识符 , 不能被重载 , 参数为每个视图位于父视图的坐标轴
    * @param l Left position, relative to parent
    * @param t Top position, relative to parent
    * @param r Right position, relative to parent
    * @param b Bottom position, relative to parent
    */
   public final void layout(int l, int t, int r, int b) {
   		//设置每个视图位于父视图的坐标轴
       boolean changed = setFrame(l, t, r, b); 
       if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
           if (ViewDebug.TRACE_HIERARCHY) {
               ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
           }
 
 			//回调onLayout函数 ,设置每个子视图的布局
           onLayout(changed, l, t, r, b);
           mPrivateFlags &= ~LAYOUT_REQUIRED;
       }
       mPrivateFlags &= ~FORCE_LAYOUT;
   }
	//回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现
   private void onLayout(int left , int top , right , bottom){
	 
	   //如果该View不是ViewGroup类型
	   //调用setFrame()方法设置该控件的在父视图上的坐标轴
	   
	   setFrame(l ,t , r ,b) ;
	   
	   //--------------------------
	   
	   //如果该View是ViewGroup类型,则对它的每个子View进行layout()过程
	   int childCount = getChildCount() ;
	   
	   for(int i=0 ;i<childCount ;i++){
		   //获得每个子View对象引用
		   View child = getChildAt(i) ;
		   //整个layout()过程就是个递归过程
		   child.layout(l, t, r, b) ;
	   }
	}

3、draw()

	private void draw(Canvas canvas){
		   //1、绘制该View的背景
		   //2、为绘制渐变框做一些准备操作
		   //3、调用onDraw()方法绘制视图本身
		   //4、调用dispatchDraw()方法绘制每个子视图,dispatchDraw()已经在Android框架中实现了,在ViewGroup方法中。应用程序程序一般不需要重写该方法,但可以捕获该方法的发生,做一些特别的事情。
		   //5、绘制渐变框
	//ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法
   @Override
   protected void dispatchDraw(Canvas canvas) {
	   
	   //其实现方法类似如下:
	   int childCount = getChildCount() ;
	   
	   for(int i=0 ;i<childCount ;i++){
		   View child = getChildAt(i) ;
		   //调用drawChild完成
		   drawChild(child,canvas) ;
	   }
	}
	//ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法
   protected void drawChild(View child,Canvas canvas) {
	   ...
	   //简单的回调View对象的draw()方法,递归就这么产生了。
	   child.draw(canvas) ;
	   ...
   }

1)绘制背景 – drawBackground()
2)绘制自己 – onDraw()
3)绘制孩子 – dispatchDraw()
4)绘制装饰 – onDrawScrollbars()

dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个 地方“需要重绘”的视图才会调用draw()方法)。
值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。

示例demo

1、自定义View
在这里插入图片描述

public class MyView extends View {

    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(800, 800);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //画笔1:绘制圆,椭圆
        Paint paint1 = new Paint();
        paint1.setStyle(Paint.Style.FILL_AND_STROKE);
        paint1.setColor(Color.BLACK);
        canvas.drawCircle(120, 100, 50, paint1);
        canvas.drawOval(250, 75, 400, 125, paint1);

        //画笔2:绘制三角形,弧线
        Path path_triangle = new Path();
        path_triangle.moveTo(210, 175);//起始位置
        path_triangle.lineTo(120, 260);
        path_triangle.lineTo(300, 260);
        path_triangle.lineTo(210, 175);
        //三角形
        Paint paint2 = new Paint();
        paint2.setStyle(Paint.Style.STROKE);
        paint2.setStrokeWidth(5);//画笔宽度
        paint2.setColor(Color.BLUE);
        canvas.drawPath(path_triangle, paint2);
        //弧线
        RectF rectF = new RectF(40, 150, 300, 325);//外切矩形范围
        rectF.offset(50, 10);//矩形向右偏移100像素,向下偏移20像素
        canvas.drawArc(rectF, 40, 100, false, paint2);

        //画笔3:绘制矩形
        Paint paint3 = new Paint();
        paint3.setStyle(Paint.Style.FILL_AND_STROKE);
        paint3.setColor(Color.RED);
        canvas.drawRect(100, 400, 350, 500, paint3);
    }
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_orange_light">


    <com.example.viewmeasure_zlz.MyView
        android:id="@+id/id_my_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>
	findViewById(R.id.id_my_view).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.i(TAG, "------ My view is clicked !");
        }
    });

2、自定义ViewGroup
在这里插入图片描述

public class MyViewGroup extends ViewGroup {

    private Context mContext;
    public Button button;
    public ImageView imageView;
    public TextView textView;
    public MyView myView;


    public MyViewGroup(Context context) {
        super(context);
        mContext = context;
        init();
    }

    public MyViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init();
    }

    public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init();
    }


    private void init() {

        //child view 1 : Button
        button = new Button(mContext);
        button.setText("Button");
        this.addView(button);

        //child view 2 : ImageView
        imageView = new ImageView(mContext);
        imageView.setBackgroundResource(R.mipmap.ic_launcher);
        this.addView(imageView);

        //child view 3 : TextView
        textView = new TextView(mContext);
        textView.setText("text");
        this.addView(textView);

        //child view 4 : 自定义View
        myView = new MyView(mContext);
        this.addView(myView);
    }

    /**
     * 对每个子View进行measure():设置每子View的大小,即实际宽和高
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        //获取该ViewGroup的实际长和宽  涉及到MeasureSpec类的使用
        int specSizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int specSizeHeight = MeasureSpec.getSize(heightMeasureSpec);

        //设置本ViewGroup的宽高
        setMeasuredDimension(specSizeWidth, specSizeHeight);

        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            childView.measure(300, 300);
//            this.measureChild(childView, widthMeasureSpec, heightMeasureSpec);
//            this.measureChildWithMargins(childView, widthMeasureSpec, heightMeasureSpec);
        }

    }

    /**
     * 对每个子View视图进行布局
     *
     * @param changed
     * @param l
     * @param t
     * @param r
     * @param b
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        //每个子View的起始横坐标
        int startLeft = 0;
        //每个子View距离父视图的位置,可以理解为 margin = 10px
        int startTop = 10;

        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            childView.layout(startLeft, startTop, startLeft + childView.getMeasuredWidth(), startTop + childView.getMeasuredHeight());
            //校准startLeft值,View之间的间距设为10px
            startLeft = startLeft + childView.getMeasuredWidth() + 10;
            //校准startTop值,View之间的间距设为20px
            startTop = startTop + childView.getMeasuredHeight() + 20;
        }
    }


    @Override
    protected void dispatchDraw(Canvas canvas) {
        Log.i("TAG_ZLZ", "------ dispatchDraw()");
        super.dispatchDraw(canvas);
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        Log.i("TAG_ZLZ", "------ drawChild()");
        return super.drawChild(canvas, child, drawingTime);
    }
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_orange_light">


    <com.example.viewmeasure_zlz.MyViewGroup
        android:id="@+id/id_my_view_group"
        android:layout_width="300dp"
        android:layout_height="600dp"
        android:background="@android:color/white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>
	MyViewGroup myViewGroup = findViewById(R.id.id_my_view_group);
    myViewGroup.button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.i("TAG_ZLZ", "------ myViewGroup button click !");
        }
    });
    myViewGroup.imageView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.i("TAG_ZLZ", "------ myViewGroup imageView click !");
        }
    });
    myViewGroup.textView.setText("test");
    myViewGroup.myView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.i("TAG_ZLZ", "------ myViewGroup myView click !");
        }
    });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KillerNoBlood

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值