1. 程式人生 > >自定義View:重繪進度條

自定義View:重繪進度條

最近下大工夫功課自定義View這一關。我把自定義View劃分為八個類別,寫完這八個類別,我就基本上弄清楚自定義控制元件的門道了。以下是我自己劃分的八個類別:

1.使用現有控制元件佈局,對子控制元件進行格式化和監聽,純程式碼實現;
2.使用現有控制元件佈局,對子控制元件進行格式化和監聽,帶佈局檔案和屬性檔案;
3.繼承View,自己畫一個,純程式碼;
4.繼承View,自己畫一個,帶屬性檔案;
5.繼承現有控制元件,純程式碼擴充套件;
6.繼承現有控制元件,帶屬性檔案擴充套件;
7.繼承ViewGroup或者佈局,實現對子控制元件的操作,純程式碼;
8.繼承ViewGroup或者佈局,實現對子控制元件的操作,帶屬性檔案。

實際上是四大類。只不過為了使用方便,我比較喜歡那種純程式碼的自定義控制元件,方便移植。帶著attrs和佈局檔案很是不方便。雖然說有依賴可以用,但是怎麼也沒有一個單檔案方便。不過,為了方便佈局,屬性檔案和佈局檔案也是不可少的。所以一類就分為兩種實現方式。

昨天我簡單實現了第一種,動態新增子控制元件,簡單實現自己的監聽事件,還開放了純程式碼的設定介面,使用起來也算是比較靈活。今天這個主要是為了實現一個完全自己定義的進度條,沒有實現監聽,我先是使用純程式碼來控制,後來想著佈局方便,就把attrs和屬性的獲取,單位的轉換全加上了。基本上有了這個進度條,自定義控制元件的門道大致就比較清楚了。

以下是程式碼:

首先把attrs展示出來:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--預設的屬性-->
    <attr name="rule_color" format="color|integer" />
    <attr name="rule_height" format="dimension" />
    <attr name="padding" format="dimension" />
    <attr name="cursor_color" format="color|integer" />
    <attr name="cursor_radius" format="dimension" />

    <!--控制元件屬性集合-->
    <declare-styleable name="ProgressBarView">
        <attr name="rule_color" />
        <attr name="rule_height" />
        <attr name="padding" />
        <attr name="cursor_color" />
        <attr name="cursor_radius" />
    </declare-styleable>
</resources>

然後就是原始碼:
/**
 * 自定義progressBar
 * Created by Devin Chen on 2016/12/26.
 */

public class ProgressBarView extends View {
    public static final int DEFAULT_RULE_COLOR = 0xff444444;//預設標尺顏色
    public static final int DEFAULT_RULE_HEIGHT = 1;//預設標尺高度,即線條的粗細
    public static final int DEFAULT_PADDING = 10;//預設邊距
    public static final int DEFAULT_CURSOR_COLOR = 0xffff4444;//預設遊標顏色
    public static final int DEFAULT_CURSOR_RADIUS = 6;//預設遊標半徑

    private int mWidth;//控制元件寬
    private int mHeight;//控制元件高
    private int mMinWidth = 400;//最小寬度
    private int mMinHeight = 40;//最小高度

    private Paint mPaint;//畫筆
    private int progress = 0;//進度值

    private int ruleColor = DEFAULT_RULE_COLOR;//標尺顏色
    private int ruleHeight = dp2px(DEFAULT_RULE_HEIGHT);//標尺高度,即線條的粗細
    private int mPadding = dp2px(DEFAULT_PADDING);//邊距
    private int cursorColor = DEFAULT_CURSOR_COLOR;//遊標顏色
    private int cursorRadius = dp2px(DEFAULT_CURSOR_RADIUS);//遊標半徑

    public ProgressBarView(Context context) {
        this(context, null);
    }

    public ProgressBarView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ProgressBarView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        obtainStyledAttrs(attrs);
        initialize();
    }

    /**
     * 獲取佈局的屬性
     *
     * @param attrs
     */
    private void obtainStyledAttrs(AttributeSet attrs) {
        TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.ProgressBarView);

        ruleColor = array.getColor(R.styleable.ProgressBarView_rule_color, ruleColor);
        ruleHeight = (int) array.getDimension(R.styleable.ProgressBarView_rule_height, ruleHeight);
        mPadding = (int) array.getDimension(R.styleable.ProgressBarView_padding, mPadding);
        cursorColor = array.getColor(R.styleable.ProgressBarView_cursor_color, cursorColor);
        cursorRadius = (int) array.getDimension(R.styleable.ProgressBarView_cursor_radius, cursorRadius);

        array.recycle();
    }

    /**
     * 初始化
     */
    private void initialize() {
        initPaint();
    }

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setStrokeWidth(ruleHeight);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = getDefaultWidth(widthMeasureSpec);
        mHeight = getDefaultHeight(heightMeasureSpec);
        setMeasuredDimension(mWidth, mHeight);
    }

    /**
     * 得到預設的控制元件寬度
     *
     * @param widthMeasureSpec
     * @return
     */
    private int getDefaultWidth(int widthMeasureSpec) {
        int hMode = MeasureSpec.getMode(widthMeasureSpec);
        int hSize = MeasureSpec.getSize(widthMeasureSpec);
        //如果使用者設定了精確值。則按照精確值取值,否則返回最小寬度
        if (hMode == MeasureSpec.EXACTLY) {
            //寬度不能小於設定的最小值
            if (hSize < mMinWidth) {
                hSize = mMinWidth;
            }
            return hSize;
        } else {
            return mMinWidth;
        }
    }

    /**
     * 得到預設的控制元件高度
     *
     * @param heightMeasureSpec
     * @return
     */
    private int getDefaultHeight(int heightMeasureSpec) {
        int hMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSize = MeasureSpec.getSize(heightMeasureSpec);

        if (hMode == MeasureSpec.EXACTLY) {
            if (hSize < mMinHeight) {
                hSize = mMinHeight;
            }
            return hSize;
        } else {
            return mMinHeight;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        mPaint.setColor(ruleColor);
        mPaint.setStyle(Paint.Style.STROKE);
        //繪製標尺
        canvas.drawLine(mPadding, getHeight() / 2, mWidth - mPadding, mHeight / 2, mPaint);
        mPaint.setColor(cursorColor);
        mPaint.setStyle(Paint.Style.FILL);
        //繪製遊標
        canvas.drawCircle(mPadding + (mWidth - mPadding * 2) * (progress / 100.0f), mHeight / 2, cursorRadius, mPaint);
        canvas.restore();
    }

    /**
     * 獲取進度值
     *
     * @return
     */
    public int getProgress() {
        return progress;
    }

    /**
     * 設定進度值
     *
     * @param progress
     */
    public void setProgress(int progress) {
        if (progress <= 100) {
            this.progress = progress;
            invalidate();
        }
    }

    /**
     * sp轉換成px
     *
     * @param dpValue
     * @return
     */
    private int dp2px(int dpValue) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
    }

    /**
     * sp轉換成px
     *
     * @param spValue
     * @return
     */
    private int sp2px(int spValue) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, getResources().getDisplayMetrics());
    }
}

佈局檔案:
<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/activity_custom_view2"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#c7e2e2e2"
    android:orientation="vertical"
    tools:context="com.devin.customviewdemo.activity.CustomView2Activity">

    <Button
        android:id="@+id/btn_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="開始" />
    <TextView
        android:id="@+id/txt_val"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:textColor="#da650b"
        android:textSize="20sp"
        android:layout_height="wrap_content" />

    <com.devin.customviewdemo.customview.ProgressBarView
        android:id="@+id/progress_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#00ffffff"
        app:cursor_color="#cf870b"
        app:cursor_radius="5dp"
        app:padding="10dp"
        app:rule_color="#924daae8"
        app:rule_height="2dp" />
</LinearLayout>

最後是在程式中使用。為了看到效果,我們需要執行緒休眠。
/**
 * 自定義進度條的使用
 */
public class CustomView2Activity extends AppCompatActivity {

    @Bind(R.id.progress_1)
    ProgressBarView progress1;
    @Bind(R.id.btn_1)
    Button btn1;
    @Bind(R.id.txt_val)
    TextView txtVal;
    private int progress = 0;
    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            mHandler.sendEmptyMessage(0);
        }
    };
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (progress >= 100) {
                removeCallbacks(mRunnable);
            }
            progress1.setProgress(progress++);
            txtVal.setText(""+progress1.getProgress());
            postDelayed(mRunnable, 200);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_custom_view2);
        ButterKnife.bind(this);
        initView();
    }

    private void initView() {

    }

    @OnClick(R.id.btn_1)
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_1:
                progress = 0;
                mRunnable.run();
                break;
            default:
                break;
        }
    }
}

執行結果:



類似這樣的自定義進度條,多數人會選擇直接繼承ProgressBar,而我是喜歡純粹的用View來繪製。兩者基本上是一樣的,只不過不具備progressBar的優點。進度條還可以用兩個控制元件疊加來繪製,這樣需要繼承幀佈局,不過這樣擴充套件起來要更方便一些。留待後一步研究。