学而实习之 不亦乐乎

Android自定义控件:自定义属性

2021-08-20 21:43:36

当Android提供的原生属性不能满足实际的需求的时候,比如我们需要自定义饼图的百分比半径大小、圆形背景、圆形显示的位置、圆形进度的背景等等。这个时候就需要我们自定义属性了。

自定义属性步骤:

1.attrs.xml文件

在res/values文件下添加一个attrs.xml文件,如果项目比较大的话,会导致attrs.xml代码相当庞大,这时可以根据相应的功能模块起名字,方便查找,例如:登录模块相关attrs_login.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="RoundImageView">
        <attr name="borderRadius" />
        <attr name="type" />
    </declare-styleable>
</resources>

2.声明一组属性

使用<declare-styleable name="PercentView"></declare-styleable>来定义一个属性集合,name就是属性集合的名字,这个名字一定要起的见名知意。

<declare-styleable name="PercentView">
    <!--添加属性-->
</declare-styleable>

然后就是定义属性值了,通过<attr name="textColor" format="color" /> 方式定义属性值,属性名字同样也要起的见名知意,format表示这个属性的值的类型,类型有以下几种:

reference:引用资源
string:字符串
Color:颜色
boolean:布尔值
dimension:尺寸值
float:浮点型
integer:整型
fraction:百分数
enum:枚举类型
flag:位或运算

基于上面的要求,我们可以定义一下百分比控件属性

<declare-styleable name="PercentView">
 <attr name="percent_circle_gravity"><!--圆形绘制的位置-->
  <flag name="left" value="0" />
  <flag name="top" value="1" />
  <flag name="center" value="2" />
  <flag name="right" value="3" />
  <flag name="bottom" value="4" />
 </attr>
 <attr name="percent_circle_radius" format="dimension" /><!--圆形半径-->
 <attr name="percent_circle_progress" format="integer" /><!--当前进度值-->
 <attr name="percent_progress_color" format="color" /><!--进度显示颜色-->
 <attr name="percent_background_color" format="color" /><!--圆形背景色-->
</declare-styleable>

3.布局中如何使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:lee="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.whoislcj.views.PercentView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_margin="10dp"
        android:background="@color/red"
        android:padding="10dp"
        lee:percent_background_color="@color/gray"
        lee:percent_circle_gravity="left"
        lee:percent_circle_progress="30"
        lee:percent_circle_radius="50dp"
        lee:percent_progress_color="@color/blue" />

</LinearLayout>

为属性集设置一个属性集名称,我这里用的lee,我这是因为实在想不起使用什么属性集名称了,建议在真正的项目中使用项目的缩写,比如微信可能就是使用wx。

4.自定义控件中如何获取自定义属性

每一个属性集合编译之后都会对应一个styleable对象,通过styleable对象获取TypedArray typedArray,然后通过键值对获取属性值,这点有点类似SharedPreference的取法。

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PercentView);
    if (typedArray != null) {
        backgroundColor = typedArray.getColor(R.styleable.PercentView_percent_background_color, Color.GRAY);
        progressColor = typedArray.getColor(R.styleable.PercentView_percent_progress_color, Color.BLUE);
        radius = typedArray.getDimension(R.styleable.PercentView_percent_circle_radius, 0);
        progress = typedArray.getInt(R.styleable.PercentView_percent_circle_progress, 0);
        gravity = typedArray.getInt(R.styleable.PercentView_percent_circle_gravity, CENTER);
        typedArray.recycle();
     }

5.完整示例 

public class PercentView extends View {
    private final static String TAG = PercentView.class.getSimpleName();
    private Paint mPaint;
    private int backgroundColor = Color.GRAY;
    private int progressColor = Color.BLUE;
    private float radius;
    private int progress;
    private float centerX = 0;
    private float centerY = 0;
    public static final int LEFT = 0;
    public static final int TOP = 1;
    public static final int CENTER = 2;
    public static final int RIGHT = 3;
    public static final int BOTTOM = 4;
    private int gravity = CENTER;
    private RectF rectF;  //用于定义的圆弧的形状和大小的界限

    public PercentView(Context context) {
        super(context);
        init();
    }

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

    public PercentView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initParams(context, attrs);
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        rectF = new RectF();
    }

    private void initParams(Context context, AttributeSet attrs) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        rectF = new RectF();
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PercentView);
        if (typedArray != null) {
            backgroundColor = typedArray.getColor(R.styleable.PercentView_percent_background_color, Color.GRAY);
            progressColor = typedArray.getColor(R.styleable.PercentView_percent_progress_color, Color.BLUE);
            radius = typedArray.getDimension(R.styleable.PercentView_percent_circle_radius, 0);
            progress = typedArray.getInt(R.styleable.PercentView_percent_circle_progress, 0);
            gravity = typedArray.getInt(R.styleable.PercentView_percent_circle_gravity, CENTER);
            typedArray.recycle();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        Log.e(TAG, "onMeasure--widthMode-->" + widthMode);
        switch (widthMode) {
            case MeasureSpec.EXACTLY://
                break;
            case MeasureSpec.AT_MOST:
                break;
            case MeasureSpec.UNSPECIFIED:
                break;
        }
        Log.e(TAG, "onMeasure--widthSize-->" + widthSize);
        Log.e(TAG, "onMeasure--heightMode-->" + heightMode);
        Log.e(TAG, "onMeasure--heightSize-->" + heightSize);
        int with = getWidth();
        int height = getHeight();
        Log.e(TAG, "onDraw---->" + with + "*" + height);
        centerX = widthSize / 2;
        centerY = heightSize / 2;
        switch (gravity) {
            case LEFT:
                centerX = radius + getPaddingLeft();
                break;
            case TOP:
                centerY = radius + getPaddingTop();
                break;
            case CENTER:
                break;
            case RIGHT:
                centerX = with - radius - getPaddingRight();
                break;
            case BOTTOM:
                centerY = height - radius - getPaddingBottom();
                break;
        }
        float left = centerX - radius;
        float top = centerY - radius;
        float right = centerX + radius;
        float bottom = centerY + radius;
        rectF.set(left, top, right, bottom);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.e(TAG, "onLayout");
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(backgroundColor);
        // FILL填充, STROKE描边,FILL_AND_STROKE填充和描边
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawCircle(centerX, centerY, radius, mPaint);
        mPaint.setColor(progressColor);

        double percent = progress * 1.0 / 100;
        int angle = (int) (percent * 360);
        canvas.drawArc(rectF, 270, angle, true, mPaint);  //根据进度画圆弧
    }
}