Android自定義控制元件之自定義屬性

總李寫程式碼發表於2016-07-28

前言:

      上篇介紹了自定義控制元件的基本要求以及繪製的基本原理,本篇文章主要介紹如何給自定義控制元件自定義一些屬性。本篇文章將繼續以上篇文章自定義圓形百分比為例進行講解。有關原理知識請參考Android自定義控制元件之基本原理(一)這篇文章。

 自定義控制元件相關文章地址:

需求產生背景:

     為何要引入自定義屬性?當Android提供的原生屬性不能滿足實際的需求的時候,比如我們需要自定義圓形百分比半徑大小、圓形背景、圓形顯示的位置、圓形進度的背景等等。這個時候就需要我們自定義屬性了。

自定義屬性步驟:

1.)在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 = with / 2;
        centerY = with / 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);  //根據進度畫圓弧
    }
}

執行結果:

根據不同的配置顯示的兩種效果

小結:

通過自定義屬性可以達到自定義的控制元件也能像原生的控制元件一樣實現可配置。但是在實際的專案開發中,像本文介紹的這種自定義控制元件使用頻率並不是最高的,使用頻率較高的是通過自定義一個組合控制元件的方式,來達到佈局檔案的複用,以減少專案維護成本以及開發成本,下篇文章將重點介紹如何自定義控制元件組合。

 

相關文章