自定义 CircleView - 继承 View 重写 onDraw

本文介绍如何创建一个圆形的自定义View,并探讨不同布局参数(如margin和padding)对其外观的影响。通过具体实例展示了如何解决wrap_content模式下尺寸计算的问题。

一、画一个圆形的 View

圆形View

如图,该圆形控件的宽为 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. 效果

    这里写图片描述

三、将圆形控件的宽设置为 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→EXACTLYAT_MOSTUNSPECIFIED
    dp/pxEXACTLY(childSize)EXACTLY(childSize)EXACTLY(childSize)
    match_parentEXACTLY(parentSize)AT_MOST(parentSize)UNSPECIFIED(0)
    wrap_contentAT_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. 效果

    这里写图片描述

四、为圆形控件添加 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值,即为圆心坐标

    这里写图片描述

  1. 效果

    padding生效

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值