天天看點

Android 自定義控件 仿MX 播放器的SeekBar效果

一、分析

最近下載下傳了個MX 播放器來看視訊,發現他的SeekBar做的挺有趣的,先看看MX 播放器的SeekBar效果:

Android 自定義控件 仿MX 播放器的SeekBar效果
Android 自定義控件 仿MX 播放器的SeekBar效果

外面多了個框框,沒有了進度的那個小圓點,還可以以水準方向的擺放和豎直方向的擺放,也可以改顔色。這種效果可以通過自己實作onDraw()方法然後畫個方框裡面再畫一條線。

根據以上特征我們可以在SeekBar基礎上進行修改,畢竟SeekBar已經有了很多屬性以及實作了拖動效果,就沒必要重頭造輪子了。

看看實作的效果吧,所謂No picture you say shen ma。

Android 自定義控件 仿MX 播放器的SeekBar效果

二、實作

(一)自定義屬性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--外框線的大小-->
    <attr name="rect_line_width" format="dimension" />
    <!--外框線的顔色-->
    <attr name="rect_color" format="color" />
    <!--進度條與外框的間距-->
    <attr name="padding_rect" format="dimension" />
    <!--進度條的粗細-->
    <attr name="progress_stroke_width" format="dimension" />
    <!--進度條的顔色-->
    <attr name="progress_color" format="color" />
    <!--緩沖進度條的顔色-->
    <attr name="secondary_progress_color" format="color" />
    <!--進度條的方向-->
    <attr name="orientation" format="enum">
        <enum name="horizontal" value="0" />
        <enum name="vartical" value="1"/>
    </attr>
    <declare-styleable name="CustomSeekBar">
        <attr name="rect_line_width" />
        <attr name="rect_color" />
        <attr name="padding_rect" />
        <attr name="progress_stroke_width" />
        <attr name="progress_color" />
        <attr name="secondary_progress_color" />
        <attr name="orientation" />
    </declare-styleable>
</resources>
           

(二)解析屬性

private final static int DEFAULT_RECT_LINE_WIDTH=0;
    private final static int DEFAULT_RECT_COLOR=ColorStateList.valueOf(0x66000000).getDefaultColor();
    private final static int DEFAULT_PADDING_RECT=2;
    private final static int DEFAULT_PROGRESS_HEIGHT=30;
    private final static int DEFAULT_PROGRESS_COLOR=ColorStateList.valueOf(0xFF58ccff).getDefaultColor();
    private final static int DEFAULT_SECONDARY_PROGRESS_COLOR=ColorStateList.valueOf(0x22000000).getDefaultColor();

    /**
     * Seek bar的方向
     */
    public enum Orientation {
        HORIZONTAL, VARTICAL
    }
    private Context mContext;
    /**
     * 外邊框線的大小
     */
    private int mRectLineWidth=DEFAULT_RECT_LINE_WIDTH;
    /**
     * 外邊框線的顔色
     */
    private int mRectColor=DEFAULT_RECT_COLOR;
    /**
     * 裡面的進度條與外邊框的距離
     */
    private int mPaddingRect=dp2px(DEFAULT_PADDING_RECT);
    /**
     * 進度條的粗細
     */
    private int mProgressStrokeWidth=dp2px(DEFAULT_PROGRESS_HEIGHT);
    /**
     * 進度條的顔色
     */
    private int mProgressColor=DEFAULT_PROGRESS_COLOR;
    /**
     * 緩沖進度條的顔色
     */
    private int mSecondaryProgressColor=DEFAULT_SECONDARY_PROGRESS_COLOR;
    /**
     * Seek bar的方向 預設為水準方向
     */
    private Orientation mOrientation=Orientation.HORIZONTAL;
    /**
     * Seek bar真實的寬度
     */
    private int mRealWidth;
    /**
     * Seek bar真實的高度
     */
    private int mRealHeight;
    /**
     * 畫筆
     */
    private Paint mPaint;


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

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

    public CustomSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext=context;
        parseAttr(attrs, defStyleAttr);
        mPaint=new Paint();
        mPaint.setAntiAlias(true);
    }

    /**
     * 解析屬性
     *
     * @param attrs        AttributeSet
     * @param defStyleAttr defStyleAttr
     */
    private void parseAttr(AttributeSet attrs, int defStyleAttr) {
        TypedArray _TypedArray=mContext.obtainStyledAttributes(attrs, R.styleable.CustomSeekBar, defStyleAttr, 0);
        int _Count=_TypedArray.getIndexCount();
        for (int i=0; i < _Count; i++) {
            int _Index=_TypedArray.getIndex(i);
            switch (_Index) {
                case R.styleable.CustomSeekBar_rect_line_width:
                    mRectLineWidth=_TypedArray.getDimensionPixelSize(_Index, DEFAULT_RECT_LINE_WIDTH);
                    break;
                case R.styleable.CustomSeekBar_rect_color:
                    mRectColor=_TypedArray.getColor(_Index, DEFAULT_RECT_COLOR);
                    break;
                case R.styleable.CustomSeekBar_padding_rect:
                    mPaddingRect=_TypedArray.getDimensionPixelSize(_Index, dp2px(DEFAULT_PADDING_RECT));
                    break;
                case R.styleable.CustomSeekBar_progress_stroke_width:
                    mProgressStrokeWidth=_TypedArray.getDimensionPixelSize(_Index, dp2px(DEFAULT_PROGRESS_HEIGHT));
                    break;
                case R.styleable.CustomSeekBar_progress_color:
                    mProgressColor=_TypedArray.getColor(_Index, DEFAULT_PROGRESS_COLOR);
                    break;
                case R.styleable.CustomSeekBar_secondary_progress_color:
                    mSecondaryProgressColor=_TypedArray.getColor(_Index, DEFAULT_SECONDARY_PROGRESS_COLOR);
                    break;
                case R.styleable.CustomSeekBar_orientation:
                    int _Orientation=_TypedArray.getInt(_Index, 0);
                    if (_Orientation == 0) {
                        mOrientation=Orientation.HORIZONTAL;
                    } else {
                        mOrientation=Orientation.VARTICAL;
                    }
                    break;
            }
        }
        _TypedArray.recycle();
    }
           
private int dp2px(int pDpVal){
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, pDpVal, 
                getResources().getDisplayMetrics());
    }
           

解析屬性就沒什麼好說的了。

(三)覆寫onMeasure()方法修改寬度或高度

@Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == Orientation.HORIZONTAL) {
            int _HeightMode=MeasureSpec.getMode(heightMeasureSpec);
            if (_HeightMode != MeasureSpec.EXACTLY) {
                //進度條的高度 + 上下兩邊與外邊框的距離 + 上下外邊框的線的大小 + 上下padding
                int _Height=mProgressStrokeWidth + (mPaddingRect * 2) + (mRectLineWidth * 2)
                        + getPaddingTop() + getPaddingBottom();
                heightMeasureSpec=MeasureSpec.makeMeasureSpec(_Height, MeasureSpec.EXACTLY);
            }
        } else {
            int _WidthMode=MeasureSpec.getMode(widthMeasureSpec);
            if (_WidthMode != MeasureSpec.EXACTLY) {
                //進度條的寬度 + 左右兩邊與外邊框的距離 + 左右外邊框的線的大小 + 左右padding
                int _Width=mProgressStrokeWidth + (mPaddingRect * 2) + (mRectLineWidth * 2)
                        + getPaddingLeft() + getPaddingRight();
                widthMeasureSpec=MeasureSpec.makeMeasureSpec(_Width, MeasureSpec.EXACTLY);
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
           

如果是EXACTLY模式,我們就不需要處理了,其他的我們就需要重新計算下寬度或高度。

(四)擷取SeekBar真實的寬度和高度

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //寬度 - 左右兩邊padding
        mRealWidth=w - getPaddingLeft() - getPaddingRight();
        //高度 - 上下兩邊padding
        mRealHeight=h - getPaddingTop() - getPaddingBottom();
    }
           

(四)畫水準SeekBar外面的邊框

/**
     * 畫橫向的外面的邊框
     * @param canvas Canvas
     */
    private void drawHorizontalRect(Canvas canvas) {
        if (mRectLineWidth <= 0) return;
        canvas.save();
        //把坐标原點移到進度條左邊的垂直中間位置
        canvas.translate(getPaddingLeft(), getHeight() / 2.0f);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(mRectLineWidth);
        mPaint.setColor(mRectColor);
        //根據坐标原點計算左上右下的位置
        float _Left=mRectLineWidth / 2.0f;
        float _Top=-(mRealHeight / 2.0f - (mRectLineWidth / 2.0f));
        float _Right=mRealWidth - (mRectLineWidth / 2.0f);
        float _Bottom=mRealHeight / 2.0f - (mRectLineWidth / 2.0f);
        canvas.drawRect(_Left, _Top, _Right, _Bottom, mPaint);
        canvas.restore();
    }
           

為了友善計算,我們把原點移動到左邊的垂直中間的位置,計算出方框左上右下的位置,然後畫上,結合下圖了解。

Android 自定義控件 仿MX 播放器的SeekBar效果

(五)畫水準SeekBar的進度條

/**
     * 畫橫向進度條
     *
     * @param canvas Canvas
     */
    private void drawHorizontalProgress(Canvas canvas) {
        if (mProgressStrokeWidth <= 0) return;
        canvas.save();
        //将坐标原點移到進度條左邊中間的位置
        canvas.translate(getPaddingLeft() + mRectLineWidth + mPaddingRect, getHeight() / 2);

        //緩沖進度條所占總進度的百分比
        float _SecondaryPercentage=(float) getSecondaryProgress() / getMax();
        //進度條的總寬度
        float _TotalProgressWidth=mRealWidth - (2 * mRectLineWidth + 2 * mPaddingRect);
        //緩沖進度條的寬度
        float _SecondaryReachWidth=_TotalProgressWidth * _SecondaryPercentage;
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mSecondaryProgressColor);
        mPaint.setStrokeWidth(mProgressStrokeWidth);
        canvas.drawLine(0, 0, _SecondaryReachWidth, 0, mPaint);

        //進度條所占總進度的百分比
        float _Percentage=(float) getProgress() / getMax();
        //進度條的寬度
        float _ReachWidth=_TotalProgressWidth * _Percentage;
        mPaint.setColor(mProgressColor);
        canvas.drawLine(0, 0, _ReachWidth, 0, mPaint);

        canvas.restore();
    }
           

我們又将坐标原點移動了,進度條可以用畫線進行,計算出線的起始x和y的位置和結束的x和y的位置然後進行繪畫,結合下圖了解。

Android 自定義控件 仿MX 播放器的SeekBar效果

(六)畫垂直Seekbar外面的邊框

/**
     * 畫縱向的外邊框
     * @param canvas Canvas
     */
    private void drawVertacalRect(Canvas canvas) {
        if (mRectLineWidth <= 0) return;
        canvas.save();
        //将坐标原點移到SeekBar的底部中間
        canvas.translate(getWidth() / 2.0f, getHeight() - getPaddingBottom() - (mRectLineWidth / 2.0f));
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(mRectLineWidth);
        mPaint.setColor(mRectColor);
        //根據坐标原點計算左上右下的位置
        float _Left=-(mRealWidth / 2.0f - (mRectLineWidth / 2.0f));
        float _Top=-(mRealHeight - (mRectLineWidth));
        float _Right=mRealWidth / 2.0f - (mRectLineWidth / 2.0f);
        float _Bottom=0;
        canvas.drawRect(_Left, _Top, _Right, _Bottom, mPaint);
        canvas.restore();
    }
           

這次是畫垂直方向的方框了,照樣移動坐标原點,然後計算出方框的左上右下的位置進行繪畫。

Android 自定義控件 仿MX 播放器的SeekBar效果

(七)畫垂直SeekBar的進度條

/**
     * 畫縱向的進度條
     * @param canvas Canvas
     */
    private void drawVertacalProgress(Canvas canvas) {
        if (mProgressStrokeWidth <= 0) return;
        canvas.save();
        //把坐标原點移到進度條底部的中間
        canvas.translate(getWidth() / 2.0f, getHeight() - getPaddingBottom() - mRectLineWidth - mPaddingRect);
        
        //緩沖進度條所占總進度的百分比
        float _SecondaryPercentage=(float) getSecondaryProgress() / getMax();
        //進度條的總高度
        float _TotalProgressWidth=mRealHeight - (2 * mRectLineWidth + 2 * mPaddingRect);
        //緩沖進度條的高度
        float _SecondaryReachWidth=_TotalProgressWidth * _SecondaryPercentage;
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mSecondaryProgressColor);
        mPaint.setStrokeWidth(mProgressStrokeWidth);
        canvas.drawLine(0, 0, 0, -_SecondaryReachWidth, mPaint);

        //進度條所占總進度的百分比
        float _Percentage=(float) getProgress() / getMax();
        //進度條的高度
        float _ReachWidth=_TotalProgressWidth * _Percentage;
        mPaint.setColor(mProgressColor);
        canvas.drawLine(0, 0, 0, -_ReachWidth, mPaint);

        canvas.restore();
    }
           

為了友善,同樣要改變下坐标原點,計算出線的起始x和y的位置和結束的x和y的位置然後進行畫線,位置看下圖。

Android 自定義控件 仿MX 播放器的SeekBar效果

(八)覆寫onDraw()方法

基本的已經完成的差不多了,覆寫onDraw()方法調用剛才定義的方法。

@Override
    protected synchronized void onDraw(Canvas canvas) {
        if (mOrientation == Orientation.HORIZONTAL) {
            drawHorizontalRect(canvas);
            drawHorizontalProgress(canvas);
        } else {
            drawVertacalRect(canvas);
            drawVertacalProgress(canvas);
        }
    }
           

到這裡效果已經出來了,但是還有一個問題,就是垂直方向的SeekBar拖動還沒解決,在垂直方向的SeekBar上你會發現點選和上下滑動是沒效果的,隻能左右滑動,畢竟SeekBar本身就是左右滑動的,這時我們需要覆寫onTouchEvent()方法實作垂直方向上的SeekBar上下滑動也有效果。

(九)解決垂直方向的SeekBar的點選和上下滑動問題

@Override
    public boolean onTouchEvent(MotionEvent event) {
        //如果是縱向的就把觸摸事件攔截下來,如果是橫向的就用系統的
        if (mOrientation == Orientation.VARTICAL) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_MOVE:
                case MotionEvent.ACTION_UP:
                    int _Progress=0;
                    //總大小 - 觸摸時的高度與總高度比例換算成所占進度條的進度
                    _Progress=getMax() - (int) (getMax() * (event.getY() / getHeight()));
                    setProgress(_Progress);
                    break;
                case MotionEvent.ACTION_CANCEL:
                    break;
            }
            return true;
        }
        return super.onTouchEvent(event);
    }
           

覆寫onTouchEvent()方法,對于Action不是ACTION_CANCEL進行處理,用進度條的最大值減去觸摸時的高度與總高度比例換算成所占進度條的進度,因為現在我們的進度條是從下到上的,坐标Y是從上到下的,是以要用getY()和高度的比。

Android 自定義控件 仿MX 播放器的SeekBar效果

(十)完整的代碼

package com.ce.customseekbar;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.widget.SeekBar;

public class CustomSeekBar extends SeekBar {

    private final static int DEFAULT_RECT_LINE_WIDTH=0;
    private final static int DEFAULT_RECT_COLOR=ColorStateList.valueOf(0x66000000).getDefaultColor();
    private final static int DEFAULT_PADDING_RECT=2;
    private final static int DEFAULT_PROGRESS_HEIGHT=30;
    private final static int DEFAULT_PROGRESS_COLOR=ColorStateList.valueOf(0xFF58ccff).getDefaultColor();
    private final static int DEFAULT_SECONDARY_PROGRESS_COLOR=ColorStateList.valueOf(0x22000000).getDefaultColor();

    /**
     * Seek bar的方向
     */
    public enum Orientation {
        HORIZONTAL, VARTICAL
    }
    private Context mContext;
    /**
     * 外邊框線的大小
     */
    private int mRectLineWidth=DEFAULT_RECT_LINE_WIDTH;
    /**
     * 外邊框線的顔色
     */
    private int mRectColor=DEFAULT_RECT_COLOR;
    /**
     * 裡面的進度條與外邊框的距離
     */
    private int mPaddingRect=dp2px(DEFAULT_PADDING_RECT);
    /**
     * 進度條的粗細
     */
    private int mProgressStrokeWidth=dp2px(DEFAULT_PROGRESS_HEIGHT);
    /**
     * 進度條的顔色
     */
    private int mProgressColor=DEFAULT_PROGRESS_COLOR;
    /**
     * 緩沖進度條的顔色
     */
    private int mSecondaryProgressColor=DEFAULT_SECONDARY_PROGRESS_COLOR;
    /**
     * Seek bar的方向 預設為水準方向
     */
    private Orientation mOrientation=Orientation.HORIZONTAL;
    /**
     * Seek bar真實的寬度
     */
    private int mRealWidth;
    /**
     * Seek bar真實的高度
     */
    private int mRealHeight;
    /**
     * 畫筆
     */
    private Paint mPaint;


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

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

    public CustomSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext=context;
        parseAttr(attrs, defStyleAttr);
        mPaint=new Paint();
        mPaint.setAntiAlias(true);
    }

    /**
     * 解析屬性
     *
     * @param attrs        AttributeSet
     * @param defStyleAttr defStyleAttr
     */
    private void parseAttr(AttributeSet attrs, int defStyleAttr) {
        TypedArray _TypedArray=mContext.obtainStyledAttributes(attrs, R.styleable.CustomSeekBar, defStyleAttr, 0);
        int _Count=_TypedArray.getIndexCount();
        for (int i=0; i < _Count; i++) {
            int _Index=_TypedArray.getIndex(i);
            switch (_Index) {
                case R.styleable.CustomSeekBar_rect_line_width:
                    mRectLineWidth=_TypedArray.getDimensionPixelSize(_Index, DEFAULT_RECT_LINE_WIDTH);
                    break;
                case R.styleable.CustomSeekBar_rect_color:
                    mRectColor=_TypedArray.getColor(_Index, DEFAULT_RECT_COLOR);
                    break;
                case R.styleable.CustomSeekBar_padding_rect:
                    mPaddingRect=_TypedArray.getDimensionPixelSize(_Index, dp2px(DEFAULT_PADDING_RECT));
                    break;
                case R.styleable.CustomSeekBar_progress_stroke_width:
                    mProgressStrokeWidth=_TypedArray.getDimensionPixelSize(_Index, dp2px(DEFAULT_PROGRESS_HEIGHT));
                    break;
                case R.styleable.CustomSeekBar_progress_color:
                    mProgressColor=_TypedArray.getColor(_Index, DEFAULT_PROGRESS_COLOR);
                    break;
                case R.styleable.CustomSeekBar_secondary_progress_color:
                    mSecondaryProgressColor=_TypedArray.getColor(_Index, DEFAULT_SECONDARY_PROGRESS_COLOR);
                    break;
                case R.styleable.CustomSeekBar_orientation:
                    int _Orientation=_TypedArray.getInt(_Index, 0);
                    if (_Orientation == 0) {
                        mOrientation=Orientation.HORIZONTAL;
                    } else {
                        mOrientation=Orientation.VARTICAL;
                    }
                    break;
            }
        }
        _TypedArray.recycle();
    }

    @Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == Orientation.HORIZONTAL) {
            int _HeightMode=MeasureSpec.getMode(heightMeasureSpec);
            if (_HeightMode != MeasureSpec.EXACTLY) {
                //進度條的高度 + 上下兩邊與外邊框的距離 + 上下外邊框的線的大小 + 上下padding
                int _Height=mProgressStrokeWidth + (mPaddingRect * 2) + (mRectLineWidth * 2)
                        + getPaddingTop() + getPaddingBottom();
                heightMeasureSpec=MeasureSpec.makeMeasureSpec(_Height, MeasureSpec.EXACTLY);
            }
        } else {
            int _WidthMode=MeasureSpec.getMode(widthMeasureSpec);
            if (_WidthMode != MeasureSpec.EXACTLY) {
                //進度條的寬度 + 左右兩邊與外邊框的距離 + 左右外邊框的線的大小 + 左右padding
                int _Width=mProgressStrokeWidth + (mPaddingRect * 2) + (mRectLineWidth * 2)
                        + getPaddingLeft() + getPaddingRight();
                widthMeasureSpec=MeasureSpec.makeMeasureSpec(_Width, MeasureSpec.EXACTLY);
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        if (mOrientation == Orientation.HORIZONTAL) {
            drawHorizontalRect(canvas);
            drawHorizontalProgress(canvas);
        } else {
            drawVertacalRect(canvas);
            drawVertacalProgress(canvas);
        }
    }

    /**
     * 畫縱向的進度條
     * @param canvas Canvas
     */
    private void drawVertacalProgress(Canvas canvas) {
        if (mProgressStrokeWidth <= 0) return;
        canvas.save();
        //把坐标原點移到進度條底部的中間
        canvas.translate(getWidth() / 2.0f, getHeight() - getPaddingBottom() - mRectLineWidth - mPaddingRect);

        //緩沖進度條所占總進度的百分比
        float _SecondaryPercentage=(float) getSecondaryProgress() / getMax();
        //進度條的總高度
        float _TotalProgressWidth=mRealHeight - (2 * mRectLineWidth + 2 * mPaddingRect);
        //緩沖進度條的高度
        float _SecondaryReachWidth=_TotalProgressWidth * _SecondaryPercentage;
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mSecondaryProgressColor);
        mPaint.setStrokeWidth(mProgressStrokeWidth);
        canvas.drawLine(0, 0, 0, -_SecondaryReachWidth, mPaint);

        //進度條所占總進度的百分比
        float _Percentage=(float) getProgress() / getMax();
        //進度條的高度
        float _ReachWidth=_TotalProgressWidth * _Percentage;
        mPaint.setColor(mProgressColor);
        canvas.drawLine(0, 0, 0, -_ReachWidth, mPaint);

        canvas.restore();
    }

    /**
     * 畫縱向的外邊框
     * @param canvas Canvas
     */
    private void drawVertacalRect(Canvas canvas) {
        if (mRectLineWidth <= 0) return;
        canvas.save();
        //将坐标原點移到SeekBar的底部中間
        canvas.translate(getWidth() / 2.0f, getHeight() - getPaddingBottom() - (mRectLineWidth / 2.0f));
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(mRectLineWidth);
        mPaint.setColor(mRectColor);
        //根據坐标原點計算左上右下的位置
        float _Left=-(mRealWidth / 2.0f - (mRectLineWidth / 2.0f));
        float _Top=-(mRealHeight - (mRectLineWidth));
        float _Right=mRealWidth / 2.0f - (mRectLineWidth / 2.0f);
        float _Bottom=0;
        canvas.drawRect(_Left, _Top, _Right, _Bottom, mPaint);
        canvas.restore();
    }

    /**
     * 畫橫向進度條
     *
     * @param canvas Canvas
     */
    private void drawHorizontalProgress(Canvas canvas) {
        if (mProgressStrokeWidth <= 0) return;
        canvas.save();
        //将坐标原點移到進度條左邊中間的位置
        canvas.translate(getPaddingLeft() + mRectLineWidth + mPaddingRect, getHeight() / 2);

        //緩沖進度條所占總進度的百分比
        float _SecondaryPercentage=(float) getSecondaryProgress() / getMax();
        //進度條的總寬度
        float _TotalProgressWidth=mRealWidth - (2 * mRectLineWidth + 2 * mPaddingRect);
        //緩沖進度條的寬度
        float _SecondaryReachWidth=_TotalProgressWidth * _SecondaryPercentage;
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mSecondaryProgressColor);
        mPaint.setStrokeWidth(mProgressStrokeWidth);
        canvas.drawLine(0, 0, _SecondaryReachWidth, 0, mPaint);

        //進度條所占總進度的百分比
        float _Percentage=(float) getProgress() / getMax();
        //進度條的寬度
        float _ReachWidth=_TotalProgressWidth * _Percentage;
        mPaint.setColor(mProgressColor);
        canvas.drawLine(0, 0, _ReachWidth, 0, mPaint);

        canvas.restore();
    }

    /**
     * 畫橫向的外面的邊框
     * @param canvas Canvas
     */
    private void drawHorizontalRect(Canvas canvas) {
        if (mRectLineWidth <= 0) return;
        canvas.save();
        //把坐标原點移到進度條左邊的垂直中間位置
        canvas.translate(getPaddingLeft(), getHeight() / 2.0f);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(mRectLineWidth);
        mPaint.setColor(mRectColor);
        //根據坐标原點計算左上右下的位置
        float _Left=mRectLineWidth / 2.0f;
        float _Top=-(mRealHeight / 2.0f - (mRectLineWidth / 2.0f));
        float _Right=mRealWidth - (mRectLineWidth / 2.0f);
        float _Bottom=mRealHeight / 2.0f - (mRectLineWidth / 2.0f);
        canvas.drawRect(_Left, _Top, _Right, _Bottom, mPaint);
        canvas.restore();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //寬度 - 左右兩邊padding
        mRealWidth=w - getPaddingLeft() - getPaddingRight();
        //高度 - 上下兩邊padding
        mRealHeight=h - getPaddingTop() - getPaddingBottom();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //如果是縱向的就把觸摸事件攔截下來,如果是橫向的就用系統的
        if (mOrientation == Orientation.VARTICAL) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_MOVE:
                case MotionEvent.ACTION_UP:
                    int _Progress=0;
                    //總大小 - 觸摸時的高度與總高度比例換算成所占進度條的進度
                    _Progress=getMax() - (int) (getMax() * (event.getY() / getHeight()));
                    setProgress(_Progress);
                    break;
                case MotionEvent.ACTION_CANCEL:
                    break;
            }
            return true;
        }
        return super.onTouchEvent(event);
    }

    private int dp2px(int pDpVal){
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, pDpVal,
                getResources().getDisplayMetrics());
    }

}
           

三、使用

(一)布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.ce.customseekbar.MainActivity">
    <com.ce.customseekbar.CustomSeekBar
        android:id="@+id/custom_seek_bar_hor"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:paddingTop="5dp"
        android:layout_marginTop="10dp"
        app:rect_line_width="1dp"
        app:padding_rect="2dp"
        app:progress_color="#CC6ba3d0"
        app:progress_stroke_width="20dp"
        app:rect_color="#33000000"
        app:secondary_progress_color="#22000000"
        app:orientation="horizontal"
        android:max="100"
        android:progress="50"
        android:secondaryProgress="70"/>
    <com.ce.customseekbar.CustomSeekBar
        android:id="@+id/custom_seek_bar"
        android:layout_width="wrap_content"
        android:layout_height="200dp"
        android:layout_centerVertical="true"
        app:rect_line_width="1dp"
        app:padding_rect="2dp"
        app:progress_color="#CCFFAA00"
        app:progress_stroke_width="20dp"
        app:rect_color="#33000000"
        app:orientation="vartical"
        android:max="100"
        android:progress="50"/>
</RelativeLayout>
           

(二)Activity

package com.ce.customseekbar;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    private CustomSeekBar mHorCustomSeekBar;
    private CustomSeekBar mCustomSeekBar;

    private int mHorProgress = 0;
    private int mProgress = 0;

    private int mSecondaryProgress = 0;

    private final static int WHAT_UPDATE_HOR_PROGRESS = 1000;
    private final static int WHAT_UPDATE_VER_PROGRESS = WHAT_UPDATE_HOR_PROGRESS + 1;

    private android.os.Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            int _What = msg.what;
            switch (_What) {
                case WHAT_UPDATE_HOR_PROGRESS:
                    if (mHorProgress < 100) {
                        if (mSecondaryProgress < 100) {
                            mSecondaryProgress += 2;
                            mHorCustomSeekBar.setSecondaryProgress(mSecondaryProgress);
                        }
                        mHorCustomSeekBar.setProgress(++mHorProgress);
                        mHandler.sendEmptyMessageDelayed(WHAT_UPDATE_HOR_PROGRESS, 50);
                    }
                    break;
                case WHAT_UPDATE_VER_PROGRESS:
                    if (mProgress < 100) {
                        mCustomSeekBar.setProgress(++mProgress);
                        mHandler.sendEmptyMessageDelayed(WHAT_UPDATE_VER_PROGRESS, 50);
                    }
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHorCustomSeekBar =(CustomSeekBar) findViewById(R.id.custom_seek_bar_hor);
        mCustomSeekBar =(CustomSeekBar) findViewById(R.id.custom_seek_bar);
        mHandler.sendEmptyMessageDelayed(WHAT_UPDATE_HOR_PROGRESS, 50);
        mHandler.sendEmptyMessageDelayed(WHAT_UPDATE_VER_PROGRESS, 50);
    }

    @Override
    protected void onDestroy() {
        mHandler.removeMessages(WHAT_UPDATE_HOR_PROGRESS);
        mHandler.removeMessages(WHAT_UPDATE_VER_PROGRESS);
        super.onDestroy();
    }
}
           

源碼位址