1. 程式人生 > >Android 自定義標尺滑動選擇值

Android 自定義標尺滑動選擇值

實現滑動標尺選擇值,效果圖

這裡寫圖片描述

1.自定義屬性attrs.xml

<declare-styleable name="RulerView">
        <attr name="textColor" format="color" />
        <attr name="textSize" format="dimension" />
        <attr name="lineColor" format="color" />
        <attr name="lineSpaceWidth" format="dimension" />
        <attr name="lineWidth" format="dimension" />
        <attr name="lineMaxHeight" format="dimension" />
        <attr name="lineMidHeight" format="dimension" />
        <attr name="lineMinHeight" format="dimension" />
        <attr name="textMarginTop" format="dimension" />
        <attr name="alphaEnable" format="boolean" />
        <attr name="minValue" format="float"/>
        <attr name="maxValue" format="float"/>
        <attr name="selectorValue" format="float"/>
        <attr name="perValue" format="float"/>
    </declare-styleable>

2.自定義RulerView

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Scroller;


public class RulerView extends View {

    private int mMinVelocity;
    private Scroller mScroller;  //Scroller是一個專門用於處理滾動效果的工具類   用mScroller記錄/計算View滾動的位置,再重寫View的computeScroll(),完成實際的滾動
    private VelocityTracker mVelocityTracker; //主要用跟蹤觸控式螢幕事件(flinging事件和其他gestures手勢事件)的速率。
    private int mWidth;
    private int mHeight;

    private float mSelectorValue = 50.0f; // 未選擇時 預設的值 滑動後表示當前中間指標正在指著的值
    private float mMaxValue = 200;        // 最大數值
    private float mMinValue = 100.0f;     //最小的數值
    private float mPerValue = 1;          //最小單位  如 1:表示 每2條刻度差為1.   0.1:表示 每2條刻度差為0.1
    // 在demo中 身高mPerValue為1  體重mPerValue 為0.1

    private float mLineSpaceWidth = 5;    //  尺子刻度2條線之間的距離
    private float mLineWidth = 4;         //  尺子刻度的寬度

    private float mLineMaxHeight = 420;   //  尺子刻度分為3中不同的高度。 mLineMaxHeight表示最長的那根(也就是 10的倍數時的高度)
    private float mLineMidHeight = 30;    //  mLineMidHeight  表示中間的高度(也就是 5  15 25 等時的高度)
    private float mLineMinHeight = 17;    //  mLineMinHeight  表示最短的那個高度(也就是 1 2 3 4 等時的高度)

    private float mTextMarginTop = 10;    //o
    private float mTextSize = 30;         //尺子刻度下方數字 textsize

    private boolean mAlphaEnable = false;  // 尺子 最左邊 最後邊是否需要透明 (透明效果更好點)

    private float mTextHeight;            //尺子刻度下方數字  的高度

    private Paint mTextPaint;             // 尺子刻度下方數字( 也就是每隔10個出現的數值) paint
    private Paint mLinePaint;             //  尺子刻度  paint

    private int mTotalLine;               //共有多少條 刻度
    private int mMaxOffset;               //所有刻度 共有多長
    private float mOffset;                // 預設狀態下,mSelectorValue所在的位置  位於尺子總刻度的位置
    private int mLastX, mMove;
    private OnValueChangeListener mListener;  // 滑動後數值回撥

    private int mLineColor = Color.GRAY;   //刻度的顏色
    private int mTextColor = Color.BLACK;    //文字的顏色


    public RulerView(Context context) {
        this(context, null);

    }

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

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

    protected void init(Context context, AttributeSet attrs) {
        mScroller = new Scroller(context);
        this.mLineSpaceWidth = myfloat(25.0F);
        this.mLineWidth = myfloat(2.0F);
        this.mLineMaxHeight = myfloat(100.0F);
        this.mLineMidHeight = myfloat(60.0F);
        this.mLineMinHeight = myfloat(40.0F);
        this.mTextHeight = myfloat(40.0F);


        final TypedArray typedArray = context.obtainStyledAttributes(attrs,
                R.styleable.RulerView);

        mAlphaEnable = typedArray.getBoolean(R.styleable.RulerView_alphaEnable, mAlphaEnable);

        mLineSpaceWidth = typedArray.getDimension(R.styleable.RulerView_lineSpaceWidth, mLineSpaceWidth);
        mLineWidth = typedArray.getDimension(R.styleable.RulerView_lineWidth, mLineWidth);
        mLineMaxHeight = typedArray.getDimension(R.styleable.RulerView_lineMaxHeight, mLineMaxHeight);
        mLineMidHeight = typedArray.getDimension(R.styleable.RulerView_lineMidHeight, mLineMidHeight);
        mLineMinHeight = typedArray.getDimension(R.styleable.RulerView_lineMinHeight, mLineMinHeight);
        mLineColor = typedArray.getColor(R.styleable.RulerView_lineColor, mLineColor);

        mTextSize = typedArray.getDimension(R.styleable.RulerView_textSize, mTextSize);
        mTextColor = typedArray.getColor(R.styleable.RulerView_textColor, mTextColor);
        mTextMarginTop = typedArray.getDimension(R.styleable.RulerView_textMarginTop, mTextMarginTop);

        mSelectorValue = typedArray.getFloat(R.styleable.RulerView_selectorValue, 0.0f);
        mMinValue = typedArray.getFloat(R.styleable.RulerView_minValue, 0.0f);
        mMaxValue = typedArray.getFloat(R.styleable.RulerView_maxValue, 100.0f);
        mPerValue = typedArray.getFloat(R.styleable.RulerView_perValue, 0.1f);


        mMinVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity();

        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setColor(mTextColor);
        mTextHeight = getFontHeight(mTextPaint);

        mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mLinePaint.setStrokeWidth(mLineWidth);
        mLinePaint.setColor(mLineColor);


        // setValue(1990, 1940, 2016, 1);

    }


    public static int myfloat(float paramFloat) {
        return (int) (0.5F + paramFloat * 1.0f);
    }

    private float getFontHeight(Paint paint) {
        Paint.FontMetrics fm = paint.getFontMetrics();
        return fm.descent - fm.ascent;
    }


    /**
     * @param selectorValue 未選擇時 預設的值 滑動後表示當前中間指標正在指著的值
     * @param minValue      最大數值
     * @param maxValue      最小的數值
     * @param per           最小單位  如 1:表示 每2條刻度差為1.   0.1:表示 每2條刻度差為0.1 在demo中 身高mPerValue為1  體重mPerValue 為0.1
     */
    public void setValue(float selectorValue, float minValue, float maxValue, float per) {
        this.mSelectorValue = selectorValue;
        this.mMaxValue = maxValue;
        this.mMinValue = minValue;
        this.mPerValue = (int) (per * 10.0f);
        this.mTotalLine = ((int) ((mMaxValue * 10 - mMinValue * 10) / mPerValue)) + 1;


        mMaxOffset = (int) (-(mTotalLine - 1) * mLineSpaceWidth);
        mOffset = (mMinValue - mSelectorValue) / mPerValue * mLineSpaceWidth * 10;
        invalidate();
        setVisibility(VISIBLE);
    }

    public void setOnValueChangeListener(OnValueChangeListener listener) {
        mListener = listener;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        super.onSizeChanged(w, h, oldw, oldh);
        if (w > 0 && h > 0) {
            mWidth = w;
            mHeight = h;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        float left, height;
        String value;
        int alpha = 0;
        float scale;
        int srcPointX = mWidth / 2;
        for (int i = 0; i < mTotalLine; i++) {
            left = srcPointX + mOffset + i * mLineSpaceWidth;

            if (left < 0 || left > mWidth) {
                continue;  //  先畫預設值在正中間,左右各一半的view。  多餘部分暫時不畫(也就是從預設值在中間,畫旁邊左右的刻度線)
            }

            /*文字*/
            if (i % 10 == 0) {
                value = String.valueOf((int) (mMinValue + i * mPerValue / 10));
                if (mAlphaEnable) {
                    mTextPaint.setAlpha(alpha);
                }
                canvas.drawText(value, left - mTextPaint.measureText(value) / 2,
                        mTextHeight, mTextPaint);    // 在為整數時,畫 數值
            }

            /*線條*/
            if (i % 10 == 0) {
                height = mLineMinHeight;
            } else if (i % 5 == 0) {
                height = mLineMidHeight;
            } else {
                height = mLineMaxHeight;
            }
            if (mAlphaEnable) {
                scale = 1 - Math.abs(left - srcPointX) / srcPointX;
                alpha = (int) (255 * scale * scale);

                mLinePaint.setAlpha(alpha);
            }
            canvas.drawLine(left, mLineMaxHeight + mTextMarginTop + mTextHeight, left, height, mLinePaint);

        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        int xPosition = (int) event.getX();

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mScroller.forceFinished(true);
                mLastX = xPosition;
                mMove = 0;
                break;
            case MotionEvent.ACTION_MOVE:
                mMove = (mLastX - xPosition);
                changeMoveAndValue();
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                countMoveEnd();
                countVelocityTracker();
                return false;
            default:
                break;
        }

        mLastX = xPosition;
        return true;
    }

    private void countVelocityTracker() {
        mVelocityTracker.computeCurrentVelocity(1000);  //初始化速率的單位
        float xVelocity = mVelocityTracker.getXVelocity(); //當前的速度
        if (Math.abs(xVelocity) > mMinVelocity) {
            mScroller.fling(0, 0, (int) xVelocity, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
        }
    }


    /**
     * 滑動結束後,若是指標在2條刻度之間時,改變mOffset 讓指標正好在刻度上。
     */
    private void countMoveEnd() {

        mOffset -= mMove;
        if (mOffset <= mMaxOffset) {
            mOffset = mMaxOffset;
        } else if (mOffset >= 0) {
            mOffset = 0;
        }

        mLastX = 0;
        mMove = 0;

        mSelectorValue = mMinValue + Math.round(Math.abs(mOffset) * 1.0f / mLineSpaceWidth) * mPerValue / 10.0f;
        mOffset = (mMinValue - mSelectorValue) * 10.0f / mPerValue * mLineSpaceWidth;


        notifyValueChange();
        postInvalidate();
    }


    /**
     * 滑動後的操作
     */
    private void changeMoveAndValue() {
        mOffset -= mMove;

        if (mOffset <= mMaxOffset) {
            mOffset = mMaxOffset;
            mMove = 0;
            mScroller.forceFinished(true);
        } else if (mOffset >= 0) {
            mOffset = 0;
            mMove = 0;
            mScroller.forceFinished(true);
        }
        mSelectorValue = mMinValue + Math.round(Math.abs(mOffset) * 1.0f / mLineSpaceWidth) * mPerValue / 10.0f;


        notifyValueChange();
        postInvalidate();
    }

    private void notifyValueChange() {
        if (null != mListener) {
            mListener.onValueChange(mSelectorValue);
        }
    }


    /**
     * 滑動後的回撥
     */
    public interface OnValueChangeListener {
        void onValueChange(float value);
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {     //mScroller.computeScrollOffset()返回 true表示滑動還沒有結束
            if (mScroller.getCurrX() == mScroller.getFinalX()) {
                countMoveEnd();
            } else {
                int xPosition = mScroller.getCurrX();
                mMove = (mLastX - xPosition);
                changeMoveAndValue();
                mLastX = xPosition;
            }
        }
    }
}

3.xml中使用activity_main.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">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:visibility="visible">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:includeFontPadding="false"
            android:maxHeight="17.0sp"
            android:text="身高(cm)"
            android:textColor="#cc222222"
            android:textSize="15.0sp" />

        <TextView
            android:id="@+id/tv_info_height_value"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="11.0dip"
            android:includeFontPadding="false"
            android:maxHeight="24.0sp"
            android:textColor="#cc222222"
            android:textSize="24.0sp" />

        <RelativeLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content">

            <com.demo.ruleview.RulerView
                android:id="@+id/ruler_height"
                android:layout_width="fill_parent"
                android:layout_height="68.0dip"
                android:layout_marginTop="24.0dip"
                app:alphaEnable="true"
                app:lineColor="@color/gray"
                app:lineMaxHeight="40dp"
                app:lineMidHeight="30dp"
                app:lineMinHeight="20dp"
                app:lineSpaceWidth="10dp"
                app:lineWidth="2dip"
                app:maxValue="250.0"
                app:minValue="80.0"
                app:perValue="1"
                app:textColor="@color/black" />

            <ImageView
                android:layout_width="14.0dip"
                android:layout_height="46.0dip"
                android:layout_centerHorizontal="true"
                android:layout_marginTop="40.0dip"
                android:scaleType="fitXY"
                android:src="@drawable/info_ruler" />
        </RelativeLayout>


        <Button
            android:id="@+id/click"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="點選改變" />

    </LinearLayout>
</LinearLayout>

4.Activity中使用MainActivity

public class MainActivity extends AppCompatActivity {

    private int maxValue = 250;
    private int minValue = 80;

    private RulerView rulerHeight;
    private TextView tvHeightValue;

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

        rulerHeight = (RulerView) findViewById(R.id.ruler_height);
        tvHeightValue = (TextView) findViewById(R.id.tv_info_height_value);

        showNumInt();

        findViewById(R.id.click).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                showNumInt();

            }
        });

        rulerHeight.setOnValueChangeListener(new RulerView.OnValueChangeListener() {
            @Override
            public void onValueChange(float value) {
                tvHeightValue.setText(String.valueOf(value));
            }
        });
    }

    private void showNumInt() {
        Random rand = new Random();
        int value = rand.nextInt(maxValue - minValue + 1) + minValue;
        rulerHeight.setValue(value, minValue, maxValue, 1);
        tvHeightValue.setText(String.valueOf(Integer.valueOf(value)));
    }
}

PS:可自行根據需要繪製線條和文字,上下選擇文字位置