Android自定義控件 帶文字提示的SeekBar

封面

轉載請注明出處:http://www.reibang.com/p/b753c4a9ddfa

本文出自 容華謝后的博客

1.寫在前面

SeekBar控件在開發(fā)中還是比較常見的厨钻,比如音視頻進度贮缅、音量調節(jié)等涛漂,但是原生控件有時還不能滿足我們的需求,今天就來學習一下如何自定義SeekBar控件尚蝌,本文主要實現(xiàn)了一個帶文字指示器效果的SeekBar控件,看下最終效果:

IndicatorSeekBar

2.實現(xiàn)

IndicatorSeekBar

public class IndicatorSeekBar extends AppCompatSeekBar {

    // 畫筆
    private Paint mPaint;
    // 進度文字位置信息
    private Rect mProgressTextRect = new Rect();
    // 滑塊按鈕寬度
    private int mThumbWidth = dp2px(50);
    // 進度指示器寬度
    private int mIndicatorWidth = dp2px(50);
    // 進度監(jiān)聽
    private OnIndicatorSeekBarChangeListener mIndicatorSeekBarChangeListener;

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

    public IndicatorSeekBar(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.seekBarStyle);
    }

    public IndicatorSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new TextPaint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.parseColor("#00574B"));
        mPaint.setTextSize(sp2px(16));

        // 如果不設置padding,當滑動到最左邊或最右邊時魄懂,滑塊會顯示不全
        setPadding(mThumbWidth / 2, 0, mThumbWidth / 2, 0);

        // 設置滑動監(jiān)聽
        this.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                // NO OP
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                if (mIndicatorSeekBarChangeListener != null) {
                    mIndicatorSeekBarChangeListener.onStartTrackingTouch(seekBar);
                }
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                if (mIndicatorSeekBarChangeListener != null) {
                    mIndicatorSeekBarChangeListener.onStopTrackingTouch(seekBar);
                }
            }
        });
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        String progressText = getProgress() + "%";
        mPaint.getTextBounds(progressText, 0, progressText.length(), mProgressTextRect);

        // 進度百分比
        float progressRatio = (float) getProgress() / getMax();
        // thumb偏移量
        float thumbOffset = (mThumbWidth - mProgressTextRect.width()) / 2 - mThumbWidth * progressRatio;
        float thumbX = getWidth() * progressRatio + thumbOffset;
        float thumbY = getHeight() / 2f + mProgressTextRect.height() / 2f;
        canvas.drawText(progressText, thumbX, thumbY, mPaint);

        if (mIndicatorSeekBarChangeListener != null) {
            float indicatorOffset = getWidth() * progressRatio - (mIndicatorWidth - mThumbWidth) / 2 - mThumbWidth * progressRatio;
            mIndicatorSeekBarChangeListener.onProgressChanged(this, getProgress(), indicatorOffset);
        }
    }

    /**
     * 設置進度監(jiān)聽
     *
     * @param listener OnIndicatorSeekBarChangeListener
     */
    public void setOnSeekBarChangeListener(OnIndicatorSeekBarChangeListener listener) {
        this.mIndicatorSeekBarChangeListener = listener;
    }

    /**
     * 進度監(jiān)聽
     */
    public interface OnIndicatorSeekBarChangeListener {
        /**
         * 進度監(jiān)聽回調
         *
         * @param seekBar         SeekBar
         * @param progress        進度
         * @param indicatorOffset 指示器偏移量
         */
        public void onProgressChanged(SeekBar seekBar, int progress, float indicatorOffset);

        /**
         * 開始拖動
         *
         * @param seekBar SeekBar
         */
        public void onStartTrackingTouch(SeekBar seekBar);

        /**
         * 停止拖動
         *
         * @param seekBar SeekBar
         */
        public void onStopTrackingTouch(SeekBar seekBar);
    }

    /**
     * dp轉px
     *
     * @param dp dp值
     * @return px值
     */
    public int dp2px(float dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
                getResources().getDisplayMetrics());
    }

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

重點看下onDraw方法:

@Override
protected synchronized void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    String progressText = getProgress() + "%";
    mPaint.getTextBounds(progressText, 0, progressText.length(), mProgressTextRect);

    // 進度百分比
    float progressRatio = (float) getProgress() / getMax();
    // thumb偏移量
    float thumbOffset = (mThumbWidth - mProgressTextRect.width()) / 2 - mThumbWidth * progressRatio;
    float thumbX = getWidth() * progressRatio + thumbOffset;
    float thumbY = getHeight() / 2f + mProgressTextRect.height() / 2f;
    canvas.drawText(progressText, thumbX, thumbY, mPaint);

    if (mIndicatorSeekBarChangeListener != null) {
        float indicatorOffset = getWidth() * progressRatio - (mIndicatorWidth - mThumbWidth) / 2 - mThumbWidth * progressRatio;
        mIndicatorSeekBarChangeListener.onProgressChanged(this, getProgress(), indicatorOffset);
    }
}

再看一遍效果圖:

IndicatorSeekBar

可以看到,進度百分比文字是跟著進度變化在平移的闯第,所以X軸坐標根據(jù)進度動態(tài)計算就可以了【總寬度 * 進度百分比】(getWidth() * progressRatio)市栗,文字需要居中顯示,所以需要向右平移【(滑塊寬度 - 文字寬度)/ 2】((mThumbWidth - mProgressTextRect.width()) / 2)咳短。

為了避免滑塊滑動到終點時布局被隱藏填帽,需要為SeekBar設置左右padding,距離分別為滑塊寬度的一半咙好,篡腌,所以【控件總長度 = 控件實際長度 + 滑塊寬度】,向右平移的過程中就要動態(tài)減去滑塊寬度【滑塊寬度 * 進度百分比】(mThumbWidth * progressRatio)勾效,到這里文字的X軸坐標就計算完成了嘹悼。

文字在平移的過程中始終是垂直居中的,所以Y軸坐標可以這樣計算【控件高度 / 2 + 文字高度 / 2】(getHeight() / 2f + mProgressTextRect.height() / 2f)层宫,注意drawText方法默認是從左下角開始繪制文字的杨伙,如果對繪制文字還不太了解,可以看下這篇文章《Android 圖解Canvas drawText文字居中的那些事》

指示器跟隨滑塊移動

在IndicatorSeekBar中萌腿,向外提供了一個setOnSeekBarChangeListener方法用來回調SeekBar的狀態(tài)限匣,其中onProgressChanged方法中的indicatorOffset參數(shù)就是指示器控件的X坐標,計算方式與上文中進度百分比文字的計算方式一致:

// 【總寬度 * 進度百分比 -(指示器寬度 - 滑塊寬度)/ 2 - 滑塊寬度 * 進度百分比】
float indicatorOffset = getWidth() * progressRatio - (mIndicatorWidth - mThumbWidth) / 2 - mThumbWidth * progressRatio;
mIndicatorSeekBarChangeListener.onProgressChanged(this, getProgress(), indicatorOffset);

看下如何使用:

public class MainActivity extends AppCompatActivity {

    private TextView tvIndicator;
    private IndicatorSeekBar indicatorSeekBar;
    private Paint mPaint = new Paint();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvIndicator = findViewById(R.id.tv_indicator);
        indicatorSeekBar = findViewById(R.id.indicator_seek_bar);

        initData();
    }

    private void initData() {
        final LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) tvIndicator.getLayoutParams();
        indicatorSeekBar.setOnSeekBarChangeListener(new IndicatorSeekBar.OnIndicatorSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, float indicatorOffset) {
                String indicatorText = progress + "%";
                tvIndicator.setText(indicatorText);
                params.leftMargin = (int) indicatorOffset;
                tvIndicator.setLayoutParams(params);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                tvIndicator.setVisibility(View.VISIBLE);
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                tvIndicator.setVisibility(View.INVISIBLE);
            }
        });
    }
}

布局文件:

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_marginStart="20dp"
        android:layout_marginEnd="20dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_indicator"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/bg_indicator"
            android:gravity="center"
            android:textColor="#FFFFFF"
            android:textSize="16sp"
            android:visibility="invisible" />

        <com.yl.indicatorseekbar.IndicatorSeekBar
            android:id="@+id/indicator_seek_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:background="@null"
            android:max="100"
            android:maxHeight="2dp"
            android:minHeight="2dp"
            android:progress="50"
            android:progressDrawable="@drawable/seekbar_progress_drawable"
            android:thumb="@drawable/seekbar_thumb" />

    </LinearLayout>

</RelativeLayout>

3.寫在最后

代碼已上傳至GitHub哮奇,歡迎Star膛腐、Fork睛约!

GitHub地址:https://github.com/alidili/Demos/tree/master/IndicatorSeekBarDemo

本文Demo的Apk下載地址:https://github.com/alidili/Demos/raw/master/IndicatorSeekBarDemo/IndicatorSeekBarDemo.apk

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市哲身,隨后出現(xiàn)的幾起案子辩涝,更是在濱河造成了極大的恐慌,老刑警劉巖勘天,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怔揩,死亡現(xiàn)場離奇詭異,居然都是意外死亡脯丝,警方通過查閱死者的電腦和手機商膊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宠进,“玉大人晕拆,你說我怎么就攤上這事〔牡牛” “怎么了实幕?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長堤器。 經常有香客問我昆庇,道長,這世上最難降的妖魔是什么闸溃? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任整吆,我火速辦了婚禮,結果婚禮上辉川,老公的妹妹穿的比我還像新娘表蝙。我一直安慰自己番川,他們只是感情好昂勒,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著苏研,像睡著了一般寸齐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抄谐,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天渺鹦,我揣著相機與錄音,去河邊找鬼蛹含。 笑死毅厚,一個胖子當著我的面吹牛,可吹牛的內容都是我干的浦箱。 我是一名探鬼主播吸耿,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼祠锣,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了咽安?” 一聲冷哼從身側響起伴网,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎妆棒,沒想到半個月后澡腾,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡糕珊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年动分,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片红选。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡澜公,死狀恐怖,靈堂內的尸體忽然破棺而出喇肋,到底是詐尸還是另有隱情坟乾,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布苟蹈,位于F島的核電站糊渊,受9級特大地震影響,放射性物質發(fā)生泄漏慧脱。R本人自食惡果不足惜渺绒,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望菱鸥。 院中可真熱鬧宗兼,春花似錦、人聲如沸氮采。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鹊漠。三九已至主到,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間躯概,已是汗流浹背登钥。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留娶靡,地道東北人牧牢。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親塔鳍。 傳聞我的和親對象是個殘疾皇子伯铣,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內容