自定义View例子

首先我们先来实现一个很简单的圆形自定义View

1.继承View并且重写onDraw方法

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

public class CircleView extends View {
	//声明画笔颜色
    private int mColor = Color.RED;
    //声明画笔并设置抗锯齿
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    public CircleView(Context context) {
        super(context);
        init();
    }

    private void init() {
        mPaint.setColor(mColor);
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        int radius = Math.min(width,height)/2;
        canvas.drawCircle(width/2,height/2,radius,mPaint);
    }
}

此时一个圆形效果的自定义View已经写好了,它会一自己的宽/高的最小值来绘制一个红色的实心圆。

在把它放入布局文件中

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.sihan.myview.CircleView
        android:id="@+id/circleView"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="#000000"></com.sihan.myview.CircleView>

</LinearLayout>

如下图所示一个红色的实心圆就已经生成了
在这里插入图片描述
下面再来设置margin属性 为其设置20dp 效果如下
在这里插入图片描述
此时可以发现margin属性是生效的。因为margin属性是由父容器控制的。因此不需要特殊处理。

再来为其设置20dp的padding。效果如下
在这里插入图片描述

此时我们可以发现padding属性根本没有达到预期的效果,所以需要在代码中对padding进行处理。
再来设置宽度为wrap_content。我们也可以发现跟使用match_parent根本没有任何区别。 这一点是因为对直接继承View的控件,如果不对wrap_content做特殊处理,那么wrap_content就相当于matchParent。

解决warp_content

先处理wrap_content,为什么wrap_content的效果和match_parent一样呢?简单说:在View类中,当该View的布局宽高值为wrap_content,或match_parent时,该View测量的最终大小就是MeasureSpec中的测量大小–>SpecSize。因此,在自定义View时,需要重写onMeasure(w,h)用来处理wrap_content的情况,然后调用setMeasuredDimession(w,h)完成测量。若需要从源码角度了解该原理,可以先全面了解下onMeasure,就比较好理解这里的结论了。见文章Android自定义控件之测量onMeasure

下面,我们设定:当布局宽为wrap_content时,设置specSize为400px;当布局高为wrap_content时,设置specSize为200px。于是重写onMeasure(w,h)代码如下:

//两个参数是父View给的测量建议值MeasureSpec,代码执行到onMeasure(w,h),说明MyCircleView的measure(w,h)在执行中
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);//宽的测量大小,模式
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);

        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);//高的测量大小,模式
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

        int w = widthSpecSize;   //定义测量宽,高(不包含测量模式),并设置默认值,查看View#getDefaultSize可知
        int h = heightSpecSize;

        //处理wrap_content的几种特殊情况
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            w = 400;  //单位是px
            h = 200;
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            //只要宽度布局参数为wrap_content, 宽度给固定值200dp(处理方式不一,按照需求来)
            w = 400;
            //按照View处理的方法,查看View#getDefaultSize可知
            h = heightSpecSize;
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            w = widthSpecSize;
            h = 200;
        }
        //给两个字段设置值,完成最终测量
        setMeasuredDimension(w, h);
    }

解决Padding

针对padding的问题我们可以在onDraw的时候稍微修改一下就好

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        int width = getWidth() - paddingLeft - paddingRight;
        int height = getHeight() -paddingBottom - paddingTop;
        int radius = Math.min(width,height)/2;
        canvas.drawCircle(paddingLeft+width/2,paddingTop+height/2,radius,mPaint);
    }

以下是上面两个问题解决之后的效果
在这里插入图片描述

自定义属性

1.在value下创建自定义属性attrs.xml,文件内容如下

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleView">
        <attr name="circle_color" format="color"/>
    </declare-styleable>
</resources>

在上面xml文件中声明了一个自定义属性集合“CircleView",在这个集合里可以很多由自定义属性,这里color值的是颜色。除了颜色格式,自定义属性还有其他格式,比如reference是指资源id,dimmension是指尺寸,而像string, integer和boolean这种是指基本属性类型。

2.在View的构造方法中解析自定义属性的指并做响应的处理

public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.CircleView);
        mColor = a.getColor(R.styleable.CircleView_circle_color,Color.RED);
        a.recycle();
        init();
    }

首先加载自定义属性集合CircleView,接着解析CircleView属性集合中的circle_color属性。在这一步骤中如果没有指定颜色则选择红色。
效果就不演示了,偷懒~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值