Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 自定義雙向滑動SeekBar

Android 自定義雙向滑動SeekBar

編輯:關於Android編程

Android 自定義雙向滑動SeekBar ,一些需要價格區間選擇的App可能需要用到

1. 自定義MySeekBar 繼承 View,先給一張效果圖。

\

2.原理:自定義attrs屬性,從布局中獲取SeekBar最小值、坐標點個數、2點間代表的數值。

3.由SeekBar最小值、坐標點個數、2點間代表的數值確定 每個坐標點的所代表的數值。

4.onMeasure()方法中設置MySeekBar長寬比。
5.onSizeChanged()方法中計算滑動指示器半徑、設置每個坐標點的坐標。

6.onDraw()方法中依次畫背景線、2個指示器間的區間線、2個滑動指示器。

7.onTouchEvent()方法中,Down判斷是否命中滑動指示器,Move時在命中的條件下進行有限滑動,Up時根據是否有滑動啟動屬性動畫。

8.MySeekBar 內部類 CircleIndicator代表滑動指示器,Point代表坐標類以及OnSeekFinishListener回調接口。

9.首先給出自定義屬性,後面會使用到。values文件夾建立attrs xml。



    
        
        
        
        
        
        
        
        
        
        
        
        
    
10.再看下布局。



    

        

            

            
        

        
    



11.MySeekBar的代碼如下:
public class MySeekBar extends View implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
    /**
     * SeekBar最小值
     */
    int minValue;
    /**
     * SeekBar共包含多少個坐標點
     */
    int pointCount;
    /**
     * 每個分段代表的數值
     */
    private int perValue;
    /**
     * 分段的端點坐標記錄數組,長度等於pointCount
     */
    Point[] mPoints;
    /**
     * SeekBar長寬比
     */
    float mLWRatio = 1f / 10f;
    CircleIndicator mLeftCI;//左側滑動指示器
    CircleIndicator mRightCI;//右側滑動指示器
    int mR;//滑動指示器半徑
    int mPadding = 5;//指定的Padding

    Paint linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    Paint indicatorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    float LINE_WIDTH = 8F;//線寬
    /**
     * 滑動指示器顏色
     */
    private int indicatorColor;
    /**
     * 2個滑動器之間的線顏色
     */
    private int indicatorLineColor;
    /**
     * 背景線顏色
     */
    private int backLineColor;
    /**
     * 圓形區域
     */
    private RectF mRectF;
    /**
     * 當前SeekBar是否有滑動指示器處於被滑動狀態
     */
    boolean isSelected = false;
    /**
     * 屬性動畫是否正在執行
     */
    boolean isPlaying;
    /**
     * 與滑動指示器最近的mPoints index值
     */
    private int mCloseIndex;
    /**
     * 動畫時長
     */
    private final long ANIM_DURATION = 200;


    public MySeekBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        //無視padding屬性 使用內部定義的mPadding
        setPadding(0, 0, 0, 0);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        //獲取自定義屬性
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MySeekBar);
        perValue = a.getInt(R.styleable.MySeekBar_per_value, 0);
        minValue = a.getInt(R.styleable.MySeekBar_min, 0);
        pointCount = a.getInt(R.styleable.MySeekBar_point_count, 0);
        backLineColor = a.getColor(R.styleable.MySeekBar_back_line_color, Color.LTGRAY);
        indicatorLineColor = a.getInt(R.styleable.MySeekBar_indicator_line_color, Color.GREEN);
        indicatorColor = a.getInt(R.styleable.MySeekBar_indicator_color, Color.GRAY);
        a.recycle();
        //初始化SeekBar內部坐標對象
        mPoints = new Point[pointCount];
        for (int i = 0; i < mPoints.length; i++) {
            mPoints[i] = new Point(minValue + i * perValue);
        }
        //初始化2個滑動指示器 默認左邊的位於mPoints數組第一個,右邊位於mPoints數組最後一個
        mLeftCI = new CircleIndicator();
        mLeftCI.setPoint(mPoints[0]);
        mRightCI = new CircleIndicator();
        mRightCI.setPoint(mPoints[mPoints.length - 1]);
        //初始化Paint
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setStrokeCap(Paint.Cap.ROUND);
        linePaint.setStrokeWidth(LINE_WIDTH);
        //初始化滑動指示器Paint
        indicatorPaint.setStyle(Paint.Style.FILL);
        indicatorPaint.setColor(indicatorColor);

        //設置陰影 注意:當前view需要添加 android:layerType="software"
        indicatorPaint.setShadowLayer(5, 2, 2, Color.LTGRAY);
        //初始化圓形區域
        mRectF = new RectF();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = (int) (size * mLWRatio);
        int spec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
        //設置View的長寬比
        setMeasuredDimension(widthMeasureSpec, spec);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //CircleIndicator半徑
        mR = h / 2 - mPadding;
        //指定滑動指示器半徑
        mLeftCI.setR(mR);
        mRightCI.setR(mR);
        //分段點坐標 mPoints數組 均分SeekBar寬度
        int y = h / 2;
        int perWidth = (w - 2 * mPadding - 2 * mR) / (mPoints.length - 1);
        for (int i = 0; i < mPoints.length; i++) {
            mPoints[i].setX(mPadding + mR + i * perWidth);
            mPoints[i].setY(y);
        }
        //更新一下 滑動指示器當前的坐標
        mLeftCI.setPoint(mLeftCI.getPoint());
        mRightCI.setPoint(mRightCI.getPoint());
        //回調當前Activity 告知2個滑動指示器的屬性
        callBack();
    }

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

        if (mPoints.length >= 2) {
            //畫背景線
            linePaint.setColor(backLineColor);
            canvas.drawLine(mPoints[0].getX(), mPoints[0].getY()
                    , mPoints[mPoints.length - 1].getX(), mPoints[mPoints.length - 1].getY(), linePaint);
            //畫區間線
            linePaint.setColor(indicatorLineColor);
            canvas.drawLine(mLeftCI.getCurX(), mLeftCI.getCurY(), mRightCI.getCurX(), mRightCI.getCurY(), linePaint);

            //畫左邊的Indicator
            mRectF.set(mLeftCI.getCurX() - mR, mLeftCI.getCurY() - mR,
                    mLeftCI.getCurX() + mR, mLeftCI.getCurY() + mR);
            canvas.drawOval(mRectF, indicatorPaint);
            //畫右邊的Indicator
            mRectF.set(mRightCI.getCurX() - mR, mRightCI.getCurY() - mR,
                    mRightCI.getCurX() + mR, mRightCI.getCurY() + mR);
            canvas.drawOval(mRectF, indicatorPaint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //如果當前正在執行動畫 則忽略用戶點擊
        if (isPlaying) {
            return true;
        }
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //檢查當前按下的坐標是否命中滑動指示器
                isSelected = checkPoint(x, y);
                break;
            case MotionEvent.ACTION_MOVE:
                //在命中的情況下,滑動指示器會在有限范圍內滑動
                move(x);
                break;
            case MotionEvent.ACTION_UP:
                //當Up時,檢查是否需要開啟屬性動畫
                reset();
                break;
        }
        //如果已經有滑動指示器呗滑動了,就需要刷新當前View了
        if (isSelected) {
            invalidate();
        }
        return true;
    }

    private void reset() {
        //重置滑動狀態
        isSelected = false;
        //執行動畫
        if (mLeftCI.isTouch()) {
            mCloseIndex = getCloseIndex(mLeftCI);
            statAnim(mLeftCI, mCloseIndex);
        }
        if (mRightCI.isTouch()) {
            mCloseIndex = getCloseIndex(mRightCI);
            statAnim(mRightCI, mCloseIndex);
        }
        mLeftCI.setIsTouch(false);
        mRightCI.setIsTouch(false);
    }

    /**
     * 加載動畫
     */
    private void statAnim(CircleIndicator rightCI, int closeIndex) {
        ObjectAnimator animator = ObjectAnimator.ofInt(rightCI, "curX", rightCI.getCurX(), mPoints[closeIndex].getX());
        animator.addUpdateListener(this);
        animator.addListener(this);
        animator.setDuration(ANIM_DURATION);
        animator.start();
    }

    /**
     * 獲取距離 該Indicator最近的 坐標點
     *
     * @param indicator
     * @return
     */
    private int getCloseIndex(CircleIndicator indicator) {
        int curX = indicator.getCurX();
        int distance = Integer.MAX_VALUE;
        int index = 0;
        //循環找出距離當前indicator 最近的坐標對象
        for (int i = 0; i < mPoints.length; i++) {
            int abs = Math.abs(curX - mPoints[i].getX());
            if (abs <= distance) {
                distance = abs;
                index = i;
            }
        }
        if (indicator.equals(mLeftCI)) {
            //如果是左邊的Indicator,那麼最大的index不能超過 右邊的Indicator所屬的坐標index
            if (mPoints[index].getX() >= mRightCI.getCurX()) {
                index--;
            }
            return index;
        }
        if (indicator.equals(mRightCI)) {
            //同理
            if (mPoints[index].getX() <= mLeftCI.getCurX()) {
                index++;
            }
            return index;
        }
        return index;
    }

    private void move(float x) {
        if (mLeftCI.isTouch()) {
            //如果左邊的Indicator呗拖拽,其x坐標應該在 第一個坐標 和右邊的Indicator 之間
            //即限定 indicator可移動的范圍
            if (x >= mPoints[0].getX() && x < mRightCI.getCurX()) {
                mLeftCI.setCurX((int) x);
            }
            return;
        }
        //同理
        if (mRightCI.isTouch()) {
            if (x <= mPoints[mPoints.length - 1].getX() && x > mLeftCI.getCurX()) {
                mRightCI.setCurX((int) x);
            }
        }
    }

    /**
     * 檢查 Down的x y是否命中 CircleIndicator,如果命中更新屬性
     *
     * @param x
     * @param y
     * @return true 命中, false 為命中
     */
    private boolean checkPoint(float x, float y) {
        boolean containsL = mLeftCI.getRect().contains((int) x, (int) y);
        if (containsL) {
            mLeftCI.setIsTouch(true);
            return true;
        }
        boolean containsR = mRightCI.getRect().contains((int) x, (int) y);
        if (containsR) {
            mRightCI.setIsTouch(true);
            return true;
        }
        return false;
    }

    /**
     * 設置2個Indicator的位置
     *
     * @param left
     * @param right
     */
    public void setPos(int left, int right) {
        if (right > left && right >= 0 && left >= 0 && right <= pointCount) {
            mLeftCI.setPoint(mPoints[left]);
            mRightCI.setPoint(mPoints[right]);
            callBack();
            invalidate();
        }
    }

    //addUpdateListener動畫回調部分
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        //每次修改完成屬性 就應該刷新當前View
        invalidate();
    }

    //addListener動畫回調部分
    @Override
    public void onAnimationStart(Animator animation) {
        //動畫執行開始
        isPlaying = true;
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        CircleIndicator indicator = (CircleIndicator) ((ObjectAnimator) animation).getTarget();
        //更新被移動Indicator 坐標屬性
        indicator.setPoint(mPoints[mCloseIndex]);
        //動畫執行結束
        isPlaying = false;
        //回調Activity告知 滑動指示器信息
        callBack();
    }

    @Override
    public void onAnimationCancel(Animator animation) {

    }

    @Override
    public void onAnimationRepeat(Animator animation) {

    }

    /**
     * SeekBar被拖拽的滑動指示器
     */
    public class CircleIndicator {
        int curX;//indicator x坐標
        int curY;//indicator y坐標
        int mR;  //indicator 半徑
        /**
         * 當前滑動指示器附著的坐標點
         */
        Point mPoint;
        /**
         * 是否被觸摸
         */
        boolean isTouch;
        /**
         * Indicator 所包含的矩形區域
         */
        Rect mRect = new Rect();

        /**
         * 獲取當前Indicator所在的矩形區域
         *
         * @return Rect
         */
        public Rect getRect() {
            mRect.set(curX - mR, curY - mR, curX + mR, curY + mR);
            return mRect;
        }

        public boolean isTouch() {
            return isTouch;
        }

        public void setIsTouch(boolean isTouch) {
            this.isTouch = isTouch;
        }

        public Point getPoint() {
            return mPoint;
        }

        public void setPoint(Point point) {
            mPoint = point;
            curX = point.getX();
            curY = point.getY();
            invalidate();
        }

        public void setPosition(Point point) {
            curX = point.getX();
            curY = point.getY();
        }

        public int getR() {
            return mR;
        }

        public void setR(int r) {
            mR = r;
        }

        public int getCurX() {
            return curX;
        }

        public void setCurX(int curX) {
            this.curX = curX;
        }

        public int getCurY() {
            return curY;
        }

        public void setCurY(int curY) {
            this.curY = curY;
        }
    }

    /**
     * SeekBar內部的 坐標類
     */
    public class Point {
        /**
         * 當前坐標點所代表的數值
         */
        int mark;
        int x;//x坐標
        int y;//y坐標

        public Point(int mark) {
            this.mark = mark;
        }

        public int getMark() {
            return mark;
        }

        public void setMark(int mark) {
            this.mark = mark;
        }

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }
    }

    public interface OnSeekFinishListener {
        void seekPos(CircleIndicator left, CircleIndicator right);
    }

    OnSeekFinishListener mListener;

    public void setListener(OnSeekFinishListener listener) {
        mListener = listener;
    }

    private void callBack() {
        if (mListener != null)
            mListener.seekPos(mLeftCI, mRightCI);
    }
}

 

12.通過setPos()方法來控制MySeekBar 滑動指示器所處的位置。

13.代碼裡有很多注釋,應該還算清楚,給自己留個底,供參考。完~~

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved