欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

自定义 CircleView - 继承 View 重写 onDraw

程序员文章站 2022-05-30 22:45:52
...

一、画一个圆形的 View


自定义 CircleView - 继承 View 重写 onDraw

如图,画一个简单的圆形控件,该控件的宽为 match_parent,高给出固定值 150dp,为了看到控件的整体宽高效果,为控件加了背景色即浅绿色:#3300aa00

  1. 该页面的布局

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
    
        <cc.catface.helloworld.view.CircleView
            android:id="@+id/cv_ver01"
            android:layout_width="match_parent"
            android:layout_height="150dp"
            android:background="#3300aa00" />
    
    </LinearLayout>
  2. 初始化工作

    public class CircleView extends View {
    
        private Paint mPaint;
    
        public CircleView(Context context) {
            super(context);
            init();
        }
    
        public CircleView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        // 初始化画笔,并为画笔设置为蓝色
        private void init() {
            mPaint = new Paint();
            mPaint.setColor(Color.BLUE);
        }
    
        ......
    }
  3. 绘制

    @Override protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    
        // get到控件的宽为match_parent,即根布局LinearLayout宽度即屏幕宽度
        int width = getWidth();
        // get到控件的高度,即150dp
        int height = getHeight();
    
        // 取控件宽高最小值作为直径,然后画圆
        int radius = Math.min(width, height) / 2; // 半径
        // (控件宽度/2, 控件高度/2)即为圆心坐标
        canvas.drawCircle(width / 2, height / 2, radius, mPaint); // 画圆
    }

二、为圆形控件添加 margin值

  1. 控件布局添加 margin

    android:layout_margin="20dp"
  2. 分析

    • margin 即外边距。看到 layout,可以知道这是父容器来控制子控件的

    • 若设置了 margin,即为该控件设置了距离其父控件或其兄弟控件的边距

  3. 效果


    自定义 CircleView - 继承 View 重写 onDraw

三、将圆形控件的宽设置为 wrap_content

  1. 控件布局添加 padding

    <cc.catface.helloworld.view.CircleView
        android:id="@+id/cv_ver01"
        android:layout_width="wrap_content"
        android:layout_height="150dp"
        android:layout_margin="20dp"
        android:background="#3300aa00" />

    运行会发现效果与设置为 match_parent 一样

  2. 分析

    按下表很容易得出原因:父容器的 layout_width 是 match_parent 即测量模式为 EXACTLY,圆形控件的宽 LayoutParams 为 wrap_content 即得出圆形控件的测量模式为 AT_MOST,其宽度即为父容器的建议宽度 parentSize,所以效果与使用 match_parent 一样。


    childLayoutParams↓↓↓ &&& parentSpecMode→→→ EXACTLY AT_MOST UNSPECIFIED
    dp/px EXACTLY(childSize) EXACTLY(childSize) EXACTLY(childSize)
    match_parent EXACTLY(parentSize) AT_MOST(parentSize) UNSPECIFIED(0)
    wrap_content AT_MOST(parentSize) AT_MOST(parentSize) UNSPECIFIED(0)

    解决办法:在 onMeasure方法 中指定 wrap_content 模式的默认宽/高,比如此处将默认宽/高分别设置为 800px和 200px

    // 解决 wrap_content 无效的问题,手动设置宽/高的大小
    @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 && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(800, 200);
    
        // 由上2.分析我们得出控件的宽测量模式是AT_MOST(wrap_content),高测量模式是EXACTLY(150dp),故走此判断
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            // 手动设置控件的宽为800px,高即为父容器建议的值,也就是控件设置的layout_height值150dp
            setMeasuredDimension(800, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, 200);
        }
    }

    分析:走完 onMeasure,最后在 setMeasuredDimension方法 中将宽 800px,高 150dp的设置值交给了系统

  3. 效果


    自定义 CircleView - 继承 View 重写 onDraw

四、为圆形控件添加 padding值

  1. 控件布局添加 padding

    android:padding="20dp"
  2. 分析

    • padding 即内边距。是控件自己控制自己的属性

    • 若设置了 padding,即设置了控件距其子控件或其内部内容(如文本)的边距

    • 需要在代码中得到 padding值,并做相应的修改即可得到 padding 的效果

      @Override protected void onDraw(Canvas canvas) {
          super.onDraw(canvas);
      
          // 分别获取上下左右的padding值,其实此处都相同,因为设置的padding对上下左右都有效且相同
          final int paddingLeft = getPaddingLeft();
          final int paddingRight = getPaddingRight();
          final int paddingTop = getPaddingTop();
          final int paddingBottom = getPaddingBottom();
      
          // 控件内容的宽度即为setMeasuredDimension得到的宽度值800px-左右padding值
          int width = getWidth() - paddingLeft - paddingRight;
          // 控件内容的高度即为setMeasuredDimension得到的高度值150dp-上下padding值
          int height = getHeight() - paddingTop - paddingBottom;
      
          int radius = Math.min(width, height) / 2;
          canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2, radius, mPaint);
      }
      • onDraw方法中的 width/height 即内容区域的宽/高,再分别加上对应 padding值,即为圆心坐标


    自定义 CircleView - 继承 View 重写 onDraw

  3. 效果


    自定义 CircleView - 继承 View 重写 onDraw