View和ViewGroup的绘制过程简单总结

需要了解的

  • 先来张图说明一下它们的关系 
    view和viewgroup 
    你还要知道ViewGroup之间是可以嵌套的.

View的绘制流程

  • 不知道大家有没有这种疑惑, 为什么我们在写布局文件的时候, 一定要写layout_width和layout_height呢, 难道就没有默认值吗? 颜色, 背景, 等等其他的都有默认值, 为什么宽高就一定要我们手动写呢? 接下来就让我们一起来解答这个疑惑吧.
  • 绘制流程的源码就不贴出来了, 有兴趣的可以打开View的源码对照着来看, 印象会更深刻, 当然, 不看源码, 理解以下的实例代码, 也不会影响你对整个流程的理解.
  • 首先看一下View的绘制流程示例:
public class MyView extends View {

    private static final String TAG = "MyView";


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

    /**
     * measure - > onMeasure ,view的源码中,measure会调用onMeasure
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  //测量, 表示这个view的大小, 在View的源码中, Measure是final修饰的, 我们只能重写onMeasure方法
        // TODO Auto-generated method stub
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.d(TAG, "measure");
    }

    /**
     * layout - > setFrame 和 onLayout
     */
    @Override
    public void layout(int l, int t, int r, int b) {  //布局,决定了摆放在父容器中的哪个位置
        // TODO Auto-generated method stub
        super.layout(l, t, r, b);
        Log.d(TAG, "layout");
    }

    /**
     * draw - > onDraw 
     */
    @Override
    public void draw(Canvas canvas) {  //绘制
        // TODO Auto-generated method stub
        super.draw(canvas);
        Log.d(TAG, "draw");
    }
}
  • 以上就是View绘制显示在屏幕上必定会调用的三个方法, 可能你还不太理解, 不过没关系, 先混个脸熟, 有个印象先, 下面我们一个个分析.

View的onMeasure

  • 来看一段Demo
public class MyView extends View {

    private static final String TAG = "MyView";

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

    /**
     * measure -> onMeasure
     * 
     * 1.父容器拿到孩子的申请的宽高layout_width, layout_height封装成宽高的期望widthMeasureSpec和heightMeasureSpec
     * 父容器Relativelayout(或者其他Linearlyout)
     * 调用MyView的 measure(int widthMeasureSpec, int heightMeasureSpec)传入对孩子宽高的期望
     * measure -> onMeasure(widthMeasureSpec, heightMeasureSpec)
     * 
     * @param widthMeasureSpec 父容器(RelativeLaoyut)对孩子MyView的宽度期望, 跟layout_width相关
     * @param heightMeasureSpec 父容器(RelativeLaoyut)对孩子MyView的高度期望, 跟layout_height相关
     * 这是我们为什么一定要指定layout_width和layout_height的原因.
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        /**
        * int widthMeasureSpec
        * 32位二进制
        * 前两位 是测量模式 mode
        * public static final int UNSPECIFIED = 0 << MODE_SHIFT; 父容器对孩子没有任何的限制,孩子想多大多大
        * public static final int EXACTLY     = 1 << MODE_SHIFT; 父容器对孩子有确切的大小要求,大小就会后30位
        * public static final int AT_MOST     = 2 << MODE_SHIFT; 父容器对孩子的最大值有要求,大小就会后30位
        * 后30位表示大小
        */
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        Log.d(TAG, "onMeasure mode " + (mode>>30) + " " + size);

        //super方法默认使用父容器对我的期望的宽高
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //我们也可以不调用super直接调用setMeasuredDimension(50, 50)来指定宽高
    }

}
  • 注释里的setMeasuredDimension(int measuredWidth, int measuredHeight)是一个重要的方法, 它是整个测量结束的标志, 只有这个方法调用了, 我们才能调用getMeasuredWidth()或者getMeasuredHeight()方法获得测量宽高(注意, 不是实际宽高, 实际宽高要在布局完成之后)。
  • 自定义View如果要使用wrap_content属性的话,则需重写onMeasure方法。

View的layout

  1. layout方法里面调用setFrame(),给View的上下左右四个位置mLeft, mTop,mRight, mBottom赋值,完成布局工作.
  2. onLayout()是一个空的方法,说明具体的布局不应该由view来决定
  3. 我们的view并不需要关心layout方法, 布局的事应该交由父容器去处理, 让它决定它的孩子应该摆放在哪个地方.
  4. 在布局完成后, 我们才能调用getWidth()和getHeight获得实际的宽高.
  5. getWidth()和getMeasuredWidth()的区别: getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

View的draw

  • view的绘制分为6步:

    1. 对视图的背景进行绘制
    2. If necessary, save the canvas’ layers to prepare for fading (暂时忽略它)
    3. 对视图的内容进行绘制, 在onDraw(canvas)方法中完成
    4. 对当前视图的所有子视图进行绘制 ,调用dispatchDraw。
    5. If necessary, draw the fading edges and restore layers (暂时忽略它)
    6. 绘制装饰品(如滚动条)任何一个视图都是有滚动条的,只是一般情况下我们都没有让它显示出来而已.
  • 即我们关心四个步骤:

    1. 绘制背景
    2. 绘制内容
    3. 绘制孩子
    4. 绘制装饰
  • 绘制需要两个类, 画布(Canvas)和画笔(Paint), 通过以下Demo通过onDraw方法利用画笔在画布上绘制我们的图案吧.

public class MyView extends View {

    private Paint mPaint;
    private Bitmap mBitmap;
    private Path mPath;
    private RectF mOval;


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

        mPaint = new Paint();
        //设置去锯齿
        mPaint.setAntiAlias(true);
        //配置画笔,画空心圆
        mPaint.setStyle(Style.STROKE);
        //设置画笔宽度
        mPaint.setStrokeWidth(3);

        //设置画笔颜色
        mPaint.setColor(Color.BLUE);

        //画图片时需要设置图片
        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.haha);

        //设置扇形的大小
        mOval = new RectF(5, 5, 195, 195);

        initPath();
    }

    private void initPath() {
        mPath = new Path();
        //确定三个点
        int x1 = 100, y1 = 5;
        int x2 = 195, y2 = 195;
        int x3 = 5, y3 = 195;
        //移动到第一个点
        mPath.moveTo(x1, y1);
        //链接第一个点和第二个点
        mPath.lineTo(x2, y2);
        //链接第二个点和第三个点
        mPath.lineTo(x3, y3);
        mPath.lineTo(x1, y1);

    }

    /**
     * 不要在onDraw方法里面创建新的对象,因为onDraw方法可能会频繁调用
     */
    @Override
    protected void onDraw(Canvas canvas) {
//      6. 裁剪
//      canvas.clipPath(mPath);


//      1. 画直线
//      int startX =5, startY = 100;
//      int stopX = 195, stopY = 100;
//      canvas.drawLine(startX, startY, stopX, stopY, mPaint);
//      2. 画圆
//      int cx = 100,  cy = 100;
//      int radius = 80;
//      canvas.drawCircle(cx, cy, radius, mPaint);
//      3. 画空心圆

//      4. 画图片
//      canvas.drawBitmap(mBitmap, 0, 0, mPaint);
//      5. 画三角形
//      canvas.drawPath(mPath, mPaint);

        //7.画扇形
        int startAngle = -90; //开始的角度
        int sweepAngle = 45;  //扫过的角度
        boolean useCenter = false;//是否画出扇形的两边
        canvas.drawArc(mOval, startAngle, sweepAngle, useCenter, mPaint);
    }
}

View的重新绘制

  • invalidate(); //触发View的重新绘制 onDraw
  • postInvalidate(); //请求在主线程重新绘制控件 onDraw

ViewGroup的绘制流程

  • ViewGroup继承View,绘制流程跟View是一致

ViewGroup的测量

  • 相同点:measure -> onMeasure
  • 不同点:ViewGroup需要在onMeasure去测量孩子
  • 自定义ViewGroup一定要重写onMeasure方法,如果不重写则子View获取不到宽和高。重写是在onMeasure方法中调用measureChildern()方法,遍历出所有子View并对其进行测量。

ViewGroup的布局

  • 相同点:layout (父容器调用) -》 onLayout
  • 不同点:ViewGroup需要实现onLayout方法去布局孩子,调用孩子的layout方法,指定孩子上下左右的位置
  • requestLayout();//请求重新布局 onLayout

ViewGroup的绘制

  • 相同点:draw -> onDraw
  • 不同点:ViewGroup一般不绘制自己,ViewGroup默认实现dispatchDraw去绘制孩子
版权声明:本文为博主原创文章,随意转载 https://blog.csdn.net/u011155781/article/details/52584044

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值