自定義帶標尺的seekbar

蒼翼-墨羽發表於2019-02-21

前言

第一次書寫技術類的文章,希望能夠將自己的所學和成長記錄下來。

內容

由於公司的專案需要,需要製作一個指標滑動的seekbar,如圖。

Screenshot_20171106-131045.png

查閱了網上大量的相關例項和知識,在此進行一個總結,如有問題希望大家能夠提出。

功能需求是這樣的:需要一個帶有標尺的滑動條,滑動部分為遊標而不是常見的滑動背景的標尺,滑動遊標的時候其他的如TextView的控制元件可以直接拿到當前遊標所指的值。因此考慮首先需要一個標尺,本來考慮標尺的展示使用圖片,但是後來考慮到適配的問題較為繁瑣,因此採用自定義控制元件的時候畫出來。而遊標方面,也考慮樣式固定化,也採用的是繪製的方式。

繪製部分程式碼如下:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        //畫最下面的線
        canvas.drawLine(PADDING_LEFT,getHeight(),widthOfProgress,getHeight(),mLinePaint);
        for (int i = minProgress; i <= maxProgress; i=i+oneItemValue) {
            if (i % oneGroupValue == 0 || i == minProgress) {
                //起點x座標10畫素,畫釐米線
                canvas.drawLine(PADDING_LEFT, getHeight(), PADDING_LEFT, getHeight()/2+16*scaleX, mLinePaint);
                //計算刻度數
                String text = i + "";
                Rect rect = new Rect();
                //獲取文字寬度
                float txtWidth = mTextPaint.measureText(text);
                mTextPaint.getTextBounds(text, 0, text.length(), rect);
                //畫標尺的數字
                canvas.drawText(text, PADDING_LEFT - txtWidth / 2, getHeight()/2+16*scaleX-rect.height()  , mTextPaint);
            } else if (i % oneItemValue == 0) {
                //每隔小單位畫間隔線,lineHeight越長,線越短
                double lineHeight = (getHeight()/2)*0.6;
                canvas.drawLine(PADDING_LEFT, getHeight(), PADDING_LEFT, getHeight()/2+(float)lineHeight, mLinePaint);
            }
//            else {
//                //畫毫米線
//                double lineHeight = (getHeight()/2)*0.4;
//                canvas.drawLine(44, getHeight(), 44, getHeight()/2+(float)lineHeight, mLinePaint);
//            }
            //每隔一個單位畫素移動一次達到劃線效果
            canvas.translate(widthOfItem, 0);
        }

        canvas.restore();
        //畫紅線遊標
        canvas.drawLine(xProgress, getHeight()/2, xProgress, getHeight(), mRulerPaint);
        Log.i("--++",cursorRadius+"////////");
        canvas.drawCircle(xProgress, getHeight()/2, cursorRadius, mRulerPaint);
        //測試用顯示progress
//        BigDecimal bd = new BigDecimal((progrees - 18) / 180);
//        bd = bd.setScale(1, BigDecimal.ROUND_HALF_UP);
//        mTextPaint.setTextSize(36);
//        float cursorTextWidth = mTextPaint.measureText(bd.floatValue()+"");
//        canvas.drawText(progrees+"", getWidth()/2-cursorTextWidth/2, cursorTextWidth, mTextPaint);
    }
複製程式碼

attrs檔案中的內容:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="RangeSeekBar">
        <attr name="line_color" format="color"></attr>
        <attr name="cursor_color" format="color"></attr>
        <attr name="text_color" format="color"></attr>
        <attr name="max_progress" format="integer"></attr>
        <attr name="min_progress" format="integer"></attr>
        <attr name="oneitem_value" format="integer"></attr>
        <attr name="one_group_value" format="integer"></attr>
        <attr name="current_progress" format="float"></attr>
        <attr name="linewidth" format="float"></attr>
        <attr name="scrollable" format="boolean"></attr>
    </declare-styleable>
</resources>
複製程式碼

由於專案需要,該控制元件中的scrollable功能預設設定是true的,如果改為false,那麼該控制元件就變成只能選擇最大值和最小值。
該控制元件還做了滑動處理,當滑動到刻度中間時就自動滑動到附近最近的線上面。該部分的處理在該控制元件的事件處理當中,程式碼如下:

@Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_DOWN:
                if (event.getX() > PADDING_LEFT + widthOfProgress || event.getX() < PADDING_LEFT){
                    isCanMove = false;
                }else{
                    isCanMove = true;

                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isCanMove) {
                    return false;
                }
                float x = event.getX();

                if (x<PADDING_LEFT || x>widthOfProgress){

                    return false;
                }
                if (scrollable){
                    xProgress =  x;
                    progrees = minProgress+Math.round((xProgress-PADDING_LEFT)/widthOfItem)*oneItemValue;
                    if (onRangeRulerChangeListener != null){
                        onRangeRulerChangeListener.onValueChanged((int)progrees);
                    }
                    invalidate();
                }else{
                    if (x < widthOfProgress / 2){
                        xProgress = PADDING_LEFT;
                    }else{
                        xProgress = widthOfProgress;
                    }
                    progrees = minProgress+Math.round((xProgress-PADDING_LEFT)/widthOfItem)*oneItemValue;
                    if (onRangeRulerChangeListener != null){
                        onRangeRulerChangeListener.onValueChanged((int)progrees);
                    }
                    invalidate();

                }

                break;
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_UP:
                float x1 = event.getX();
                if (scrollable){
                    int number = Math.round((x1 - PADDING_LEFT)/ widthOfItem);
                    if (number < 0){
                        number = 0;

                    }else if (number >= numberOfItem){
                        number = numberOfItem;
                    }
                    xProgress = PADDING_LEFT + number * widthOfItem;
                    invalidate();
                }else{

                    if (x1  >= widthOfProgress/2){
                        xProgress = PADDING_LEFT + numberOfItem * widthOfItem;
                    }else{
                        xProgress = PADDING_LEFT;
                    }
                    invalidate();
                }
                progrees = minProgress+Math.round((xProgress-PADDING_LEFT)/widthOfItem)*oneItemValue;
                if (onRangeRulerChangeListener != null){
                    onRangeRulerChangeListener.onValueChanged((int)progrees);
                }


                break;


        }
        return true;
    }
複製程式碼

最後放上Main的邏輯,可以用來設定該控制元件的預設值和一些屬性:
Main.java方面:

public class MainActivity extends AppCompatActivity {
    private RangeSeekBar rangeSeekBar;
    private TextView tv1;
    private RangeSeekBar dayView;
    private TextView tvDay;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        rangeSeekBar = (RangeSeekBar)findViewById(R.id.seekbar_money);
        tv1 = (TextView)findViewById(R.id.tv_money);
        dayView = (RangeSeekBar) findViewById(R.id.seekbar_day);
        tvDay = (TextView) findViewById(R.id.tv_day);

        rangeSeekBar.setOnRangeRulerChangeListener(new OnRangeRulerChangeListener() {
            @Override
            public void onValueChanged(int value) {
                tv1.setText(value+"");
            }
        });
        //設定當前值一定要在設定了監聽之後
        rangeSeekBar.setCurrentProgress(500);
        dayView.setOnRangeRulerChangeListener(new OnRangeRulerChangeListener() {
            @Override
            public void onValueChanged(int value) {
                tvDay.setText(value+"");
            }
        });
    }

}
複製程式碼

xml方面:

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#f0f0f0">

    <LinearLayout

        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:layout_height="0dp"
        android:layout_weight="4">
        <TextView
            android:layout_centerHorizontal="true"
            android:layout_marginTop="10dp"
            android:id="@+id/tv_credirLine"
            android:gravity="center_vertical"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text=""
            android:textSize="17sp"/>

        <RelativeLayout
            android:layout_marginTop="15dp"
            android:id="@+id/rl_money"
            android:gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <ImageView
                android:layout_centerVertical="true"
                android:id="@+id/iv_money"
                android:layout_width="20dp"
                android:layout_height="20dp"
                />
            <TextView
                android:layout_marginLeft="15dp"
                android:layout_toRightOf="@+id/iv_money"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="借款金額"
                android:textSize="16sp"/>
        </RelativeLayout>
        <RelativeLayout
            android:padding="15dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            >
            <TextView
                android:layout_centerHorizontal="true"
                android:id="@+id/tv_money"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="300"
                android:textSize="18sp"/>

            <TextView
                android:layout_marginLeft="15dp"
                android:layout_centerHorizontal="true"
                android:layout_toRightOf="@+id/tv_money"
                android:id="@+id/tv_1"
                android:layout_centerVertical="true"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#333333"
                android:text="元"
                android:textSize="15sp"/>
        </RelativeLayout>
        <RelativeLayout
            android:layout_marginTop="15dp"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            >
            <com.moyu.wh.testcustomseekbar.customview.RangeSeekBar
                android:id="@+id/seekbar_money"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:line_color="#dddddd"
                app:text_color="#dddddd"/>
            <!--<ImageView-->
                <!--android:layout_centerInParent="true"-->
                <!--android:layout_width="15dp"-->
                <!--android:layout_height="50dp"-->
                <!--android:layout_alignParentBottom="true"-->
                <!--android:src="@drawable/ic_scoll_line"-->
                <!--/>-->
        </RelativeLayout>
    </LinearLayout>


    <LinearLayout
        android:layout_marginTop="50dp"
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:layout_height="0dp"
        android:layout_weight="3">
        <RelativeLayout
            android:layout_marginTop="25dp"
            android:id="@+id/rl_day"
            android:gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <ImageView
                android:layout_centerVertical="true"
                android:id="@+id/iv_day"
                android:layout_width="24dp"
                android:layout_height="24dp"
                />
            <TextView
                android:layout_marginLeft="10dp"
                android:layout_toRightOf="@+id/iv_day"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="借款期數"
                android:textSize="16sp"/>
        </RelativeLayout>
        <RelativeLayout
            android:padding="15dp"
            android:gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <TextView
                android:layout_centerHorizontal="true"
                android:id="@+id/tv_day"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="7"
                android:textSize="18sp"/>

            <TextView
                android:layout_centerVertical="true"
                android:layout_marginLeft="3dp"
                android:layout_toRightOf="@+id/tv_day"
                android:layout_centerHorizontal="true"
                android:id="@+id/tv_2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#333333"
                android:text="天"
                android:textSize="15sp"/>
        </RelativeLayout>

        <LinearLayout

            android:id="@+id/ll_day"

            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:orientation="horizontal">
            <com.moyu.wh.testcustomseekbar.customview.RangeSeekBar
                android:id="@+id/seekbar_day"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:max_progress="14"
                app:min_progress="7"
                app:one_group_value="7"
                app:oneitem_value="1"
                app:scrollable="false"
                app:current_progress="7"
                app:line_color="#dddddd"
                app:text_color="#dddddd"/>
        </LinearLayout>
    </LinearLayout>
    <LinearLayout
        android:layout_marginTop="50dp"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="2"
        >


    </LinearLayout>

</LinearLayout>
複製程式碼

謝謝閱讀,如有問題可以前來詢問,新人不才,程式碼如存在問題,敬請指點,互相學習成長進步。
Demo位置(CSDN)

相關文章