1. 程式人生 > >Android之SeekBar(0在中間)

Android之SeekBar(0在中間)

本文主要記錄一些零碎的東西

公司UI想出一個SeekBar,中間是0 ,往左是負的,往右是正的,自帶的完全無法滿足,只能自己擼了,想了一下,有些思路

主要是Canvas 類畫直線,畫圓,畫圓角矩形,看看效果


效果看著還可以,因為要支援最左邊的點是0,所以設定了兩種模式的切換,

跟隨手勢移動的處理:
touch事件,判斷touch點是否在Thumb上,如果是在拉動圓點

/**
     * 判斷是否 touch 在 seekBar thumb 上
     *
     * @param event
     * @return
     */
    private boolean isTouchingTarget(MotionEvent event) {
        isTouchLegal = event.getRawX() > progressPosition - DEFAULT_TOUCH_TARGET_SIZE
                && event.getRawX() < progressPosition + DEFAULT_TOUCH_TARGET_SIZE;
//        Log.i("slack", "isTouchLegal " + isTouchLegal);
        return isTouchLegal;
    }
根據手在螢幕上的位置以及該View的佈局(寬度高度)計算出當前的progress
/**
     * return  progress
     * -maxProgress          minProgress              maxProgress
     * \------------------------0---------------------------\
     * min                   center     touch-->\          max
     * (min center touch max are positions in the screen)
     * touch progress = (touch - center) / (max - center) * maxProgress;
     */
    private float clamp(int value) {
        if (mIsCenterState) {
            int centerX = getWidth() / 2;
            float min = centerX - width / 2;// the start point
            float max = centerX + width / 2;// the end point
            if (value > centerX) {
                if (value >= max) {
                    return maxProgress;
                } else {
                    return (int) ((maxProgress - minProgress) * (value - centerX) / (width / 2f));
                }
            } else if (value < centerX) {
                if (value <= min) {
                    return -maxProgress;
                } else {
                    return (int) ((maxProgress - minProgress) * (value - centerX) / (width / 2f));
                }
            } else {
                return minProgress;
            }
        } else {
            int centerX = getWidth() / 2;
            float min = centerX - width / 2;// the start point
            float max = centerX + width / 2;// the end point
            if (value >= max) {
                return maxProgress;
            } else if (value <= min) {
                return minProgress;
            } else {
                return (maxProgress - minProgress) * (value - min) / width ;
            }
        }
    }
移動時增加自定義動畫
/**
     * 自定義動畫  ofFloat(Object target, String propertyName, float... values)
     * 第一個引數用於指定這個動畫要操作的是哪個控制元件
     * 第二個引數用於指定這個動畫要操作這個控制元件的哪個屬性
     * 第三個引數是可變長引數,這個就跟ValueAnimator中的可變長引數的意義一樣了,就是指這個屬性值是從哪變到哪
     * 對於自定義的屬性,d第二個引數需要提供setXXX 方法,like:  public void setMThumbRadius(int mThumbRadius)
     * set方法 屬性的第一個字母記得要大寫 !  ObjectAnimator.ofInt , ofInt 對應引數的型別,如果float 為ofFloat
     */
    private ObjectAnimator getTargetAnimator(boolean touching) {
        final ObjectAnimator anim = ObjectAnimator.ofFloat(this,
                "mThumbRadius",
                mThumbRadius,
                touching ? mThumbPressedRadius : mThumbNormalRadius);
        anim.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
//                Log.i("slack","onAnimationUpdate...");
                postInvalidate(); // 在子執行緒中使用重新整理View
            }
        });
        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
//                Log.i("slack","onAnimationEnd...");
                anim.removeAllListeners();
            }
        });
        anim.setInterpolator(new AccelerateInterpolator());
        return anim;
    }

    public void setMThumbRadius(float mThumbRadius) {
//        Log.i("slack","setmThumbRadius...");
        this.mThumbRadius = mThumbRadius;
    }

看看完整程式碼 com.cl.slack.seekbar.CenterSeekBar
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.ColorInt;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateInterpolator;

/**
 * 從中間可以向兩邊滑動 左負右正
 * Created by slack on 2016/12/12 19:00.
 */

public class CenterSeekBar extends View {

    private static final int DEFAULT_TOUCH_TARGET_SIZE = 40;
    private final int DEFAULT_TEXT_PADDING = 10;

    private Paint paint;

    private float width = 800; // need <= getWidth()

    /**
     * progress start max
     */
    private int minProgress = 0;

    /**
     * progress end max
     */
    private int maxProgress = 100;

    /**
     * 進度條的顏色 底色 背景色
     */
    @ColorInt
    private int progressBackColor = Color.BLACK;

    /**
     * 進度條的底色 高度
     */
    private float progressBackHeight = 10;

    /**
     * 進度條 底色 圓角矩形邊框 描邊
     */
    @ColorInt
    private int progressFrameColor = Color.WHITE;

    /**
     * 進度條圓角矩形邊框 高度
     */
    private float progressFrameHeight = 3;

    /**
     * 進度條的顏色
     */
    @ColorInt
    private int progressColor = Color.GREEN;

    /**
     * 進度條的 高度
     */
    private float progressHeight = 20;

    /**
     * 如果0在中間,負進度條的顏色
     */
    @ColorInt
    private int progressMinusColor = Color.RED;

    /**
     * current progress
     */
    private int progress = 50;

    /**
     * seekBar Thumb normal radius
     */
    private float mThumbNormalRadius = 14;

    /**
     * seekBar Thumb radius when touched
     */
    private float mThumbPressedRadius = 24;

    /**
     * seekBar Thumb color
     */
    @ColorInt
    private int mThumbColor = Color.BLUE;

    /**
     * progress 字型大小
     */
    private float mTextSize = 40;

    /**
     * progress 字型 color
     */
    @ColorInt
    private int mTextColor = Color.WHITE;

    /**
     * progress 字型 背景 color
     */
    @ColorInt
    private int mTextBackColor = 0x7DD2D3D4;

    /**
     * progress 字型 背景 radius
     */
    private float mTextBackRadius = 10;

    /**
     * 判斷是否是 0 在中間
     */
    private boolean mIsCenterState = false;

    private float mThumbRadius = mThumbNormalRadius;
    private float progressPosition;
    private boolean isTouchLegal = false;
    private ObjectAnimator mAnimator; //  seekBar Thumb Animator
    private RectF mTextRectF,mBackRectF,mProgressRectF;

    private OnSeekBarProgressListener mOnSeekBarProgressListener;
    private OnSeekBarFinishedListener mOnSeekBarFinishedListener;

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

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

    public CenterSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        paint = new Paint();

        if (attrs != null) {
            TypedArray styledAttrs = getContext().obtainStyledAttributes(attrs,
                    R.styleable.CenterSeekBar, 0, 0);
            maxProgress = styledAttrs.getInteger(R.styleable.CenterSeekBar_maxProgress,100);
            minProgress = styledAttrs.getInteger(R.styleable.CenterSeekBar_minProgress,0);
            width = styledAttrs.getDimension(R.styleable.CenterSeekBar_width,800);
            mIsCenterState = styledAttrs.getBoolean(R.styleable.CenterSeekBar_centerState,false);
            progressBackColor = styledAttrs.getColor(R.styleable.CenterSeekBar_backColor,Color.BLACK);
            progressBackHeight = styledAttrs.getDimension(R.styleable.CenterSeekBar_backHeight,10);
            progressFrameColor = styledAttrs.getColor(R.styleable.CenterSeekBar_backFrameColor,Color.WHITE);
            progressFrameHeight = styledAttrs.getDimension(R.styleable.CenterSeekBar_backFrameSize,3);
            progressColor = styledAttrs.getColor(R.styleable.CenterSeekBar_progressColor,Color.GREEN);
            progressHeight = styledAttrs.getDimension(R.styleable.CenterSeekBar_progressHeight,progressBackHeight);
            progressMinusColor = styledAttrs.getColor(R.styleable.CenterSeekBar_progressMinusColor,Color.RED);
            progress = styledAttrs.getInteger(R.styleable.CenterSeekBar_progress,50);
            mThumbNormalRadius = styledAttrs.getDimension(R.styleable.CenterSeekBar_thumbNormalRadius,14);
            mThumbPressedRadius = styledAttrs.getDimension(R.styleable.CenterSeekBar_thumbPressRadius,24);
            mThumbColor = styledAttrs.getColor(R.styleable.CenterSeekBar_thumbColor,Color.BLUE);
            progressColor = styledAttrs.getColor(R.styleable.CenterSeekBar_progressColor,Color.BLUE);
            mTextColor = styledAttrs.getColor(R.styleable.CenterSeekBar_textColor,Color.WHITE);
            mTextSize = styledAttrs.getDimension(R.styleable.CenterSeekBar_textSize,40);
            mTextBackColor = styledAttrs.getColor(R.styleable.CenterSeekBar_textBackColor,0x7DD2D3D4);
            mTextBackRadius = styledAttrs.getDimension(R.styleable.CenterSeekBar_textBackRadius,10);

            styledAttrs.recycle();
        }

        mAnimator = getTargetAnimator(false);
        mTextRectF = new RectF();
        mBackRectF = new RectF();
        mProgressRectF = new RectF();
    }

    /**
     * 設定是否是 0 在中間
     * @param enable
     */
    public CenterSeekBar setCenterModeEnable(boolean enable) {
        // 將負值 變成正值
        if(mIsCenterState){
            if(!enable){
                if(progress < 0){
                    progress = -progress;
                }
            }
        }
        mIsCenterState = enable;
        invalidate();
        return this;
    }

    public CenterSeekBar setProgress(int progress){
        if(progress < maxProgress && progress > minProgress){
            this.progress = progress;
        } else {
            progress = minProgress;
        }
        return this;
    }

    public CenterSeekBar OnSeekBarProgressListener(OnSeekBarProgressListener l){
        mOnSeekBarProgressListener = l;
        return this;
    }

    public CenterSeekBar OnSeekBarFinishedListener(OnSeekBarFinishedListener l){
        mOnSeekBarFinishedListener = l;
        return this;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
//        Log.i("slack","onDraw... " + mThumbRadius);
        int centerX = getWidth() / 2; // x 是center
        int centerY = 3 * getHeight() / 4; // y 是 3/4 高度
        float startX = centerX - width / 2;

        // draw background line
        paint.setColor(progressBackColor);
        paint.setStrokeWidth(progressBackHeight);
        paint.setStyle(Paint.Style.FILL); // 實心
        mBackRectF.left = startX;
        mBackRectF.top = centerY - progressBackHeight;
        mBackRectF.right = startX + width;
        mBackRectF.bottom = centerY;
        canvas.drawRoundRect(mBackRectF,mTextBackRadius,mTextBackRadius,paint);
        paint.setColor(progressFrameColor);
        paint.setStrokeWidth(progressFrameHeight);
        paint.setStyle(Paint.Style.STROKE); // 空心
        canvas.drawRoundRect(mBackRectF,mTextBackRadius,mTextBackRadius,paint);

//        canvas.drawLine(startX, centerY, centerX + width / 2, centerY, paint);

        paint.setStyle(Paint.Style.FILL);
        paint.setColor(progressColor);

        // draw progress
        paint.setStrokeWidth(progressHeight);
        paint.setColor(progressColor);
        if (mIsCenterState) {
//            if (progress < 0) {
//                paint.setColor(progressMinusColor);
//            }
            startX = centerX;
            progressPosition = startX + (int) ((progress * (width / 2f) / (maxProgress - minProgress)));
        }else {
            progressPosition = startX + ((progress * width / (maxProgress - minProgress)));
        }
        mProgressRectF.top = centerY - progressBackHeight;
        mProgressRectF.bottom = centerY;
        if(progress > 0) {
            mProgressRectF.left = startX;
            mProgressRectF.right = progressPosition;
        }else {
            mProgressRectF.left = progressPosition;
            mProgressRectF.right = startX;
        }
        canvas.drawRoundRect(mProgressRectF,mTextBackRadius,mTextBackRadius,paint);

//        canvas.drawLine(startX, centerY, progressPosition, centerY, paint);

        // draw point
        paint.setColor(mThumbColor);
        canvas.drawCircle(progressPosition, centerY- progressBackHeight/2, mThumbRadius, paint);

        /** mThumbRadius will change
         * mThumbRadius  : mThumbNormalRadius  ----------------- mThumbPressedRadius
         *  alpha        :  0                  ------------------ 255
         *
         */
        int alpha = (int)(255 * (mThumbRadius - mThumbNormalRadius) / (mThumbPressedRadius - mThumbNormalRadius));

        // draw text progress
        paint.setColor(mTextBackColor);
        paint.setAlpha(alpha);
        mTextRectF.bottom = centerY - mThumbPressedRadius - DEFAULT_TEXT_PADDING;
        mTextRectF.right = progressPosition + mTextSize + DEFAULT_TEXT_PADDING;
        mTextRectF.top = mTextRectF.bottom - mTextSize - DEFAULT_TEXT_PADDING * 3;
        mTextRectF.left = progressPosition - mTextSize - DEFAULT_TEXT_PADDING;
        canvas.drawRoundRect(mTextRectF, mTextBackRadius, mTextBackRadius, paint);
        paint.setTextSize(mTextSize);
        paint.setColor(mTextColor);
        paint.setAlpha(alpha);
        // 為了讓text 在背景的中心
        paint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(progress + "%", progressPosition, mTextRectF.bottom - DEFAULT_TEXT_PADDING * 2, paint);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
//        Log.i("slack","onTouchEvent " + event.toString());
        if (!isEnabled())
            return false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                checkTouchingTarget(event);
                break;
            case MotionEvent.ACTION_MOVE:
                if (isTouchLegal) {
                    progress = (int)clamp((int) event.getRawX());
//                    Log.i("slack","progress " + progress);
                    invalidate(); // 在UI執行緒中使用 重新整理View
                    if(mOnSeekBarProgressListener != null){
                        mOnSeekBarProgressListener.onProgress(progress);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (isTouchLegal) {
                    mAnimator.cancel();
                    mAnimator = getTargetAnimator(false);
                    mAnimator.start();
                    if(mOnSeekBarFinishedListener != null){
                        mOnSeekBarFinishedListener.onFinished(progress);
                    }
                }
                break;
        }
        return true;
    }

    /**
     * if touch , seekBar Thumb Animation
     */
    private void checkTouchingTarget(MotionEvent event) {
        if (isTouchingTarget(event)) {
            mAnimator.cancel();
            mAnimator = getTargetAnimator(true);
            mAnimator.start();
        }
    }

    /**
     * 判斷是否 touch 在 seekBar thumb 上
     *
     * @param event
     * @return
     */
    private boolean isTouchingTarget(MotionEvent event) {
        isTouchLegal = event.getRawX() > progressPosition - DEFAULT_TOUCH_TARGET_SIZE
                && event.getRawX() < progressPosition + DEFAULT_TOUCH_TARGET_SIZE;
//        Log.i("slack", "isTouchLegal " + isTouchLegal);
        return isTouchLegal;
    }

    /**
     * 自定義動畫  ofFloat(Object target, String propertyName, float... values)
     * 第一個引數用於指定這個動畫要操作的是哪個控制元件
     * 第二個引數用於指定這個動畫要操作這個控制元件的哪個屬性
     * 第三個引數是可變長引數,這個就跟ValueAnimator中的可變長引數的意義一樣了,就是指這個屬性值是從哪變到哪
     * 對於自定義的屬性,d第二個引數需要提供setXXX 方法,like:  public void setMThumbRadius(int mThumbRadius)
     * set方法 屬性的第一個字母記得要大寫 !  ObjectAnimator.ofInt , ofInt 對應引數的型別,如果float 為ofFloat
     */
    private ObjectAnimator getTargetAnimator(boolean touching) {
        final ObjectAnimator anim = ObjectAnimator.ofFloat(this,
                "mThumbRadius",
                mThumbRadius,
                touching ? mThumbPressedRadius : mThumbNormalRadius);
        anim.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
//                Log.i("slack","onAnimationUpdate...");
                postInvalidate(); // 在子執行緒中使用重新整理View
            }
        });
        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
//                Log.i("slack","onAnimationEnd...");
                anim.removeAllListeners();
            }
        });
        anim.setInterpolator(new AccelerateInterpolator());
        return anim;
    }

    public void setMThumbRadius(float mThumbRadius) {
//        Log.i("slack","setmThumbRadius...");
        this.mThumbRadius = mThumbRadius;
    }

    /**
     * return  progress
     * -maxProgress          minProgress              maxProgress
     * \------------------------0---------------------------\
     * min                   center     touch-->\          max
     * (min center touch max are positions in the screen)
     * touch progress = (touch - center) / (max - center) * maxProgress;
     */
    private float clamp(int value) {
        if (mIsCenterState) {
            int centerX = getWidth() / 2;
            float min = centerX - width / 2;// the start point
            float max = centerX + width / 2;// the end point
            if (value > centerX) {
                if (value >= max) {
                    return maxProgress;
                } else {
                    return (int) ((maxProgress - minProgress) * (value - centerX) / (width / 2f));
                }
            } else if (value < centerX) {
                if (value <= min) {
                    return -maxProgress;
                } else {
                    return (int) ((maxProgress - minProgress) * (value - centerX) / (width / 2f));
                }
            } else {
                return minProgress;
            }
        } else {
            int centerX = getWidth() / 2;
            float min = centerX - width / 2;// the start point
            float max = centerX + width / 2;// the end point
            if (value >= max) {
                return maxProgress;
            } else if (value <= min) {
                return minProgress;
            } else {
                return (maxProgress - minProgress) * (value - min) / width ;
            }
        }
    }

    public interface OnSeekBarProgressListener {
        void onProgress(int progress);
    }

    public interface OnSeekBarFinishedListener {
        void onFinished(int progress);
    }

}
屬性檔案 values/seekbar.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CenterSeekBar">
        <attr name="maxProgress" format="integer"/> <!--max progress -->
        <attr name="minProgress" format="integer"/> <!-- min minProgress -->
        <attr name="width" format="dimension"/><!-- seekbar width needs <= seekbar veiw layout_width -->
        <attr name="centerState" format="boolean"/> <!-- 是否 0 在中心 -->
        <attr name="backColor" format="color"/><!-- 背景 拉條杆 底色 -->
        <attr name="backHeight" format="dimension"/><!-- 背景 拉條杆 height -->
        <attr name="backFrameColor" format="color"/><!-- 背景 圓角邊框 描邊-->
        <attr name="backFrameSize" format="dimension"/><!-- 背景 圓角邊框 寬度 -->
        <attr name="progressColor" format="color"/><!--progress 進度 顏色-->
        <attr name="progressHeight" format="dimension"/><!--progress height -->
        <attr name="progressMinusColor" format="color"/><!-- 0在中心 時  負數 的顏色 -->
        <attr name="progress" format="integer"/><!--當前 progress-->
        <attr name="thumbNormalRadius" format="dimension"/><!-- thumb 圓 半徑 -->
        <attr name="thumbPressRadius" format="dimension"/><!-- thumb 圓 觸控時 半徑 -->
        <attr name="thumbColor" format="color"/><!-- thumb 圓 顏色 -->
        <attr name="textSize" format="dimension"/><!--進度字型大小-->
        <attr name="textColor" format="color"/><!--進度字型 顏色-->
        <attr name="textBackColor" format="color"/><!--進度字型 背景 顏色-->
        <attr name="textBackRadius" format="dimension"/><!--進度字型背景 圓角矩形 弧度-->
    </declare-styleable>
</resources>


呼叫就很簡單啦
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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:background="@android:color/darker_gray"
    android:orientation="vertical"
    tools:context="com.cl.slack.seekbar.MainActivity">

    <com.cl.slack.seekbar.CenterSeekBar
        android:id="@+id/seekbar"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@color/colorAccent"
        app:maxProgress="100"
        app:minProgress="0"
        app:backColor="#FFFFFF"
        app:backHeight="5dp"
        app:backFrameColor="#C9C6C6"
        app:backFrameSize="1dp"
        app:thumbColor="#F96650"
        app:thumbNormalRadius="10dp"
        app:thumbPressRadius="12dp"
        app:progressColor="#FCB2A7"
        app:progress="0"
        />

    <Button
        android:onClick="reFlush"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text"/>
</LinearLayout>

activity
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    private CenterSeekBar mSeekBar;
    private boolean center = false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mSeekBar = (CenterSeekBar) findViewById(R.id.seekbar);
    }

    public void reFlush(View view) {
        center = !center;
        mSeekBar.setCenterModeEnable(center);
    }
}

-----update 2017.05.20----------

減少重新整理頻率,修復佈局非全屏時部分問題

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.ColorInt;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateInterpolator;

import com.benqu.core.util.D;
import com.benqu.wuta.R;

/**
 * 從中間可以向兩邊滑動 左負右正
 * Created by slack on 2016/12/16 10:11.
 */
public class SeekBarView extends View {

    private static final int DEFAULT_TOUCH_TARGET_SIZE = 40;
    private final int DEFAULT_TEXT_PADDING = 10;
    private final int DEFAULT_THUMB_COLOR = Color.GRAY;

    private final Paint paint;

    private float width = 800; // need <= getWidth()

    /**
     * progress start max
     */
    private int minProgress = 0;

    /**
     * progress end max
     */
    private int maxProgress = 100;

    /**
     * 進度條的顏色 底色 背景色
     */
    @ColorInt
    private int progressBackColor = Color.BLACK;

    /**
     * 進度條的底色 高度
     */
    private float progressBackHeight = 10;

    /**
     * 進度條 底色 圓角矩形邊框 描邊
     */
    @ColorInt
    private int progressFrameColor = Color.WHITE;

    /**
     * 進度條圓角矩形邊框 高度
     */
    private float progressFrameHeight = 3;

    /**
     * 進度條的顏色
     */
    @ColorInt
    private int progressColor = Color.GREEN;

    /**
     * 進度條的 高度
     */
    private float progressHeight = 20;

    /**
     * 如果0在中間,負進度條的顏色
     */
    @ColorInt
    private int progressMinusColor = Color.RED;

    /**
     * current progress
     */
    private int progress = 50;

    /**
     * seekBar Thumb normal radius
     */
    private float mThumbNormalRadius = 14;

    /**
     * seekBar Thumb radius when touched
     */
    private float mThumbPressedRadius = 24;

    /**
     * seekBar Thumb color
     */
    @ColorInt
    private int mThumbColor = DEFAULT_THUMB_COLOR;

    private float mTextLocation = 1;
    /**
     * progress 字型大小
     */
    private float mTextSize = 40;

    /**
     * progress 字型 color
     */
    @ColorInt
    private int mTextColor = Color.WHITE;

    /**
     * progress 字型 背景 color
     */
    @ColorInt
    private int mTextBackColor = 0x7DD2D3D4;

    /**
     * progress 字型 背景 radius
     */
    private float mTextBackRadius = 10;

    /**
     * 判斷是否是 0 在中間
     */
    private boolean mIsCenterState = false;

    private float mThumbRadius = mThumbNormalRadius;
    private float progressPosition;
    private boolean isTouchLegal = false;
    private ObjectAnimator mAnimator; //  seekBar Thumb Animator
    private RectF mTextRectF, mBackRectF, mProgressRectF;
    private int mThumbDrawColor = DEFAULT_THUMB_COLOR;

    private OnSeekBarChangeListener mOnSeekBarChangeListener;
    private OnSeekBarProgressListener mOnSeekBarProgressListener;
    private OnSeekBarFinishedListener mOnSeekBarFinishedListener;

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

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

    public SeekBarView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        paint = new Paint();
        paint.setAntiAlias(true);

        if (attrs != null) {
            TypedArray styledAttrs = getContext().obtainStyledAttributes(attrs,
                    R.styleable.SeekBarView, 0, 0);
            maxProgress = styledAttrs.getInteger(R.styleable.SeekBarView_S_maxProgress, 100);
            minProgress = styledAttrs.getInteger(R.styleable.SeekBarView_S_minProgress, 0);
            width = styledAttrs.getDimension(R.styleable.SeekBarView_S_width, 800);
            mIsCenterState = styledAttrs.getBoolean(R.styleable.SeekBarView_S_centerState, false);
            progressBackColor = styledAttrs.getColor(R.styleable.SeekBarView_S_backColor, Color.BLACK);
            progressBackHeight = styledAttrs.getDimension(R.styleable.SeekBarView_S_backHeight, 10);
            progressFrameColor = styledAttrs.getColor(R.styleable.SeekBarView_S_backFrameColor, Color.WHITE);
            progressFrameHeight = styledAttrs.getDimension(R.styleable.SeekBarView_S_backFrameSize, 3);
            progressColor = styledAttrs.getColor(R.styleable.SeekBarView_S_progressColor, Color.GREEN);
            progressHeight = styledAttrs.getDimension(R.styleable.SeekBarView_S_progressHeight, progressBackHeight);
            progressMinusColor = styledAttrs.getColor(R.styleable.SeekBarView_S_progressMinusColor, Color.RED);
            progress = styledAttrs.getInteger(R.styleable.SeekBarView_S_progress, 50);
            mThumbNormalRadius = styledAttrs.getDimension(R.styleable.SeekBarView_S_thumbNormalRadius, 14);
            mThumbPressedRadius = styledAttrs.getDimension(R.styleable.SeekBarView_S_thumbPressRadius, 24);
            mThumbColor = styledAttrs.getColor(R.styleable.SeekBarView_S_thumbColor, Color.BLUE);
            progressColor = styledAttrs.getColor(R.styleable.SeekBarView_S_progressColor, Color.BLUE);
            mTextLocation = styledAttrs.getInteger(R.styleable.SeekBarView_S_textLocation, 1);
            mTextColor = styledAttrs.getColor(R.styleable.SeekBarView_S_textColor, Color.WHITE);
            mTextSize = styledAttrs.getDimension(R.styleable.SeekBarView_S_textSize, 40);
            mTextBackColor = styledAttrs.getColor(R.styleable.SeekBarView_S_textBackColor, 0x7DD2D3D4);
            mTextBackRadius = styledAttrs.getDimension(R.styleable.SeekBarView_S_textBackRadius, 10);

            mThumbRadius = mThumbNormalRadius;
            mThumbDrawColor = mThumbColor;
            styledAttrs.recycle();
        }

        mAnimator = getTargetAnimator(false);
        mTextRectF = new RectF();
        mBackRectF = new RectF();
        mProgressRectF = new RectF();
    }

    /**
     * 設定是否是 0 在中間
     *
     * @param enable
     */
    public SeekBarView setCenterModeEnable(boolean enable) {
        // 將負值 變成正值
        if (mIsCenterState) {
            if (!enable) {
                if (progress < 0) {
                    progress = -progress;
                }
            }
        }
        mIsCenterState = enable;
        invalidate();
        return this;
    }

    public SeekBarView setProgress(int progress) {
        if (mIsCenterState) {
            if (progress <= maxProgress && progress >= minProgress - maxProgress) {
                this.progress = progress;
            } else {
                this.progress = minProgress;
            }
        } else {
            if (progress <= maxProgress && progress >= minProgress) {
                this.progress = progress;
            } else {
                this.progress = minProgress;
            }
        }
        invalidate();
        return this;
    }

    /**
     * 是否可用
     */
    public SeekBarView setProgressEnable(boolean enable) {
        if (enable) {
            this.setEnabled(true);
            mThumbDrawColor = mThumbColor;
        } else {
            this.setEnabled(false);
            this.progress = 0;
            mThumbDrawColor = DEFAULT_THUMB_COLOR;
        }
        invalidate();
        return this;
    }

    public int getProgress() {
        return progress;
    }

    public SeekBarView setOnSeekBarChangeListener(OnSeekBarChangeListener l) {
        mOnSeekBarChangeListener = l;
        return this;
    }

    public SeekBarView setOnSeekBarProgressListener(OnSeekBarProgressListener l) {
        mOnSeekBarProgressListener = l;
        return this;
    }

    public SeekBarView setOnSeekBarFinishedListener(OnSeekBarFinishedListener l) {
        mOnSeekBarFinishedListener = l;
        return this;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
//        Log.i("slack","onDraw... " + mThumbRadius);
        int centerX = getWidth() / 2; // x 是center
        int centerY = getHeight() / 2; // y 是 2/3 高度
        float startX = centerX - width / 2 ;

        // draw background line
        paint.setColor(progressBackColor);
        paint.setStrokeWidth(progressBackHeight);
        paint.setStyle(Paint.Style.FILL); // 實心
        mBackRectF.left = startX;
        mBackRectF.top = centerY - progressBackHeight;
        mBackRectF.right = startX + width;
        mBackRectF.bottom = centerY;
        canvas.drawRoundRect(mBackRectF, mTextBackRadius, mTextBackRadius, paint);
        paint.setColor(progressFrameColor);
        paint.setStrokeWidth(progressFrameHeight);
        paint.setStyle(Paint.Style.STROKE); // 空心
        canvas.drawRoundRect(mBackRectF, mTextBackRadius, mTextBackRadius, paint);

//        canvas.drawLine(startX, centerY, centerX + width / 2, centerY, paint);

        paint.setStyle(Paint.Style.FILL);
        paint.setColor(progressColor);

        // draw progress
        paint.setStrokeWidth(progressHeight);
        paint.setColor(progressColor);
        if (mIsCenterState) {
//            if (progress < 0) {
//                paint.setColor(progressMinusColor);
//            }
            startX = centerX;
            progressPosition = startX + (int) ((progress * (width / 2f) / (maxProgress - minProgress)));
        } else {
            progressPosition = startX + ((progress * width / (maxProgress - minProgress)));
        }
        mProgressRectF.top = centerY - progressBackHeight;
        mProgressRectF.bottom = centerY;
        if (progress > 0) {
            mProgressRectF.left = startX;
            mProgressRectF.right = progressPosition;
        } else {
            mProgressRectF.left = progressPosition;
            mProgressRectF.right = startX;
        }
        canvas.drawRoundRect(mProgressRectF, mTextBackRadius, mTextBackRadius, paint);

//        canvas.drawLine(startX, centerY, progressPosition, centerY, paint);

        // draw point
        paint.setColor(mThumbDrawColor);
        canvas.drawCircle(progressPosition, centerY - progressBackHeight / 2, mThumbRadius, paint);

        /** mThumbRadius will change
         * mThumbRadius  : mThumbNormalRadius  ----------------- mThumbPressedRadius
         *  alpha        :  0                  ------------------ 255
         *
         */
        int alpha = (int) (255 * (mThumbRadius - mThumbNormalRadius) / (mThumbPressedRadius - mThumbNormalRadius));

        // draw text progress  up the Thumb  code is ok

        if (mTextLocation == 1) {
            paint.setColor(mTextBackColor);
            paint.setAlpha(alpha);
            mTextRectF.bottom = centerY - mThumbPressedRadius - DEFAULT_TEXT_PADDING;
            mTextRectF.right = progressPosition + mTextSize + DEFAULT_TEXT_PADDING;
            mTextRectF.top = mTextRectF.bottom - mTextSize - DEFAULT_TEXT_PADDING * 3;
            mTextRectF.left = progressPosition - mTextSize - DEFAULT_TEXT_PADDING;
            canvas.drawRoundRect(mTextRectF, mTextBackRadius, mTextBackRadius, paint);
            paint.setTextSize(mTextSize);
            paint.setColor(mTextColor);
            paint.setAlpha(alpha);
            // 為了讓text 在背景的中心
            paint.setTextAlign(Paint.Align.CENTER);
            canvas.drawText(progress + "%", progressPosition, mTextRectF.bottom - DEFAULT_TEXT_PADDING * 2, paint);
        } else if (mTextLocation == 2) {
            // draw text in Thumb
            paint.setTextSize(mTextSize);
            paint.setColor(mTextColor);
            paint.setAlpha(alpha);
            paint.setTextAlign(Paint.Align.CENTER);
            canvas.drawText(progress + "%", progressPosition, centerY, paint);
        }
    }

//    private int mLastProgress;
    private long mLastTime;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
//        Log.i("slack","onTouchEvent " + event.toString());
        if (!isEnabled())
            return false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                checkTouchingTarget(event);
                break;
            case MotionEvent.ACTION_MOVE:
                if (isTouchLegal) {
                    progress = (int) clamp((int) event.getRawX() - getLeft());
//                    if (mLastProgress == progress) {
//                        // 兩次一樣就不需要重畫
//                        break;
//                    }

                    long currentTime = System.currentTimeMillis();
                    if (currentTime - mLastTime < 50) {
                        // 重新整理 FPS 不超過 20 fps
                        break;
                    }
                    mLastTime = currentTime;

//                    Log.i("slack","progress " + progress);
                    invalidate(); // 在UI執行緒中使用 重新整理View
                    if (mOnSeekBarChangeListener != null) {
                        mOnSeekBarChangeListener.onProgress(progress);
                    } else if (mOnSeekBarProgressListener != null) {
                        mOnSeekBarProgressListener.onProgress(progress);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                invalidate();
//                mLastProgress = -1;
                if (isTouchLegal) {
                    mAnimator.cancel();
                    mAnimator = getTargetAnimator(false);
                    mAnimator.start();
                    if (mOnSeekBarChangeListener != null) {
                        mOnSeekBarChangeListener.onFinished(progress);
                    } else if (mOnSeekBarFinishedListener != null) {
                        mOnSeekBarFinishedListener.onFinished(progress);
                    }
                }
                break;
        }
        return true;
    }

    /**
     * if touch , seekBar Thumb Animation
     */
    private void checkTouchingTarget(MotionEvent event) {
        if (isTouchingTarget(event)) {
            mAnimator.cancel();
            mAnimator = getTargetAnimator(true);
            mAnimator.start();
        }
    }

    /**
     * 判斷是否 touch 在 seekBar thumb 上
     *
     * @param event
     * @return
     */
    private boolean isTouchingTarget(MotionEvent event) {
        float location = progressPosition + getLeft();
        isTouchLegal = event.getRawX() > location - DEFAULT_TOUCH_TARGET_SIZE
                && event.getRawX() < location + DEFAULT_TOUCH_TARGET_SIZE;
        D.i("slack", "isTouchLegal " + isTouchLegal + " "  + event.getRawX() + " " +
                event.getRawY() + " " + progressPosition);
        return isTouchLegal;
    }

    /**
     * 自定義動畫  ofFloat(Object target, String propertyName, float... values)
     * 第一個引數用於指定這個動畫要操作的是哪個控制元件
     * 第二個引數用於指定這個動畫要操作這個控制元件的哪個屬性
     * 第三個引數是可變長引數,這個就跟ValueAnimator中的可變長引數的意義一樣了,就是指這個屬性值是從哪變到哪
     * 對於自定義的屬性,d第二個引數需要提供setXXX 方法,like:  public void setMThumbRadius(int mThumbRadius)
     * set方法 屬性的第一個字母記得要大寫 !  ObjectAnimator.ofInt , ofInt 對應引數的型別,如果float 為ofFloat
     */
    private ObjectAnimator getTargetAnimator(boolean touching) {
        final ObjectAnimator anim = ObjectAnimator.ofFloat(this,
                "mThumbRadius",
                mThumbRadius,
                touching ? mThumbPressedRadius : mThumbNormalRadius);
        anim.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
//                Log.i("slack","onAnimationUpdate...");
                postInvalidate(); // 在子執行緒中使用重新整理View
            }
        });
        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
//                Log.i("slack","onAnimationEnd...");
                anim.removeAllListeners();
            }
        });
        anim.setInterpolator(new AccelerateInterpolator());
        return anim;
    }

    public void setMThumbRadius(float mThumbRadius) {
//        Log.i("slack","setmThumbRadius...");
        this.mThumbRadius = mThumbRadius;
    }

    /**
     * return  progress
     * -maxProgress          minProgress              maxProgress
     * \------------------------0---------------------------\
     * min                   center     touch-->\          max
     * (min center touch max are positions in the screen)
     * touch progress = (touch - center) / (max - center) * maxProgress;
     */
    private float clamp(int value) {
        if (mIsCenterState) {
            int centerX = getWidth() / 2;
            float min = centerX - width / 2;// the start point
            float max = centerX + width / 2;// the end point
            if (value > centerX) {
                if (value >= max) {
                    return maxProgress;
                } else {
                    return (int) ((maxProgress - minProgress) * (value - centerX) / (width / 2f));
                }
            } else if (value < centerX) {
                if (value <= min) {
                    return -maxProgress;
                } else {
                    return (int) ((maxProgress - minProgress) * (value - centerX) / (width / 2f));
                }
            } else {
                return minProgress;
            }
        } else {
            int centerX = getWidth() / 2;
            float min = centerX - width / 2;// the start point
            float max = centerX + width / 2;// the end point
            if (value >= max) {
                return maxProgress;
            } else if (value <= min) {
                return minProgress;
            } else {
                return (maxProgress - minProgress) * (value - min) / width;
            }
        }
    }

    public interface OnSeekBarProgressListener {
        void onProgress(int progress);
    }

    public interface OnSeekBarFinishedListener {
        void onFinished(int progress);
    }

    public interface OnSeekBarChangeListener extends OnSeekBarProgressListener, OnSeekBarFinishedListener {
    }

}




相關推薦

AndroidSeekBar(0中間)

本文主要記錄一些零碎的東西 公司UI想出一個SeekBar,中間是0 ,往左是負的,往右是正的,自帶的完全無法滿足,只能自己擼了,想了一下,有些思路 主要是Canvas 類畫直線,畫圓,畫圓角矩形,看看效果 效果看著還可以,因為要支援最左邊的點是0,所以設定了兩種模式的切

android4.0控制元件switch自定義開關背景圖片和控制寬度

<style name="widget_gender_switch">        <item name="android:layout_height">wrap_content</item>        <item name=

android4.0的系統主題style修改android:Theme.Holo.Light

1.修改主介面背景顏色 轉自http://www.dewen.io/q/9466 在AndroidMenifest.xml application 屬性設定時不使用系統的 android:theme="@android:style/Theme.Holo", 而是自定義的

android自己定義 5.0 風格progressBar

rcp 方式 rem listen angle 一個 arr 5.0 自己 近期做項目,用到了ProgressBar 。就想到了要使用Android5.0 的效果,就隨手實現了一下。 效果圖: 大概的思路: 1. 圓圈通過Canvas去繪

[Android FrameWork 6.0源碼學習] View的重繪過程Layout

種子 noop cond req cor ide boolean 需要 bound View繪制的三部曲,測量,布局,繪畫現在我們分析布局部分測量部分在上篇文章中已經分析過了。不了解的可以去我的博客裏找一下View的布局和測量一樣,都是從ViewRootImpl中發起,Vi

[Android FrameWork 6.0源碼學習] View的重繪過程Draw

鐘表 store 傳遞 lan play deb kill gre 學習 View繪制的三部曲,測量,布局,繪畫現在我們分析繪畫部分測量和布局 在前兩篇文章中已經分析過了。不了解的可以去我的博客裏找一下 下面進入正題,開始分析調用以及函數原理 private void

Android - 圖片處理Glide4.0

郭大神關於Glide文章的連線,很詳細 Android圖片載入框架最全解析(一),Glide的基本用法 Android圖片載入框架最全解析(二),從原始碼的角度理解Glide的執行流程 Android圖片載入框架最全解析(三),深入探究Glide的快取機制 Android圖

Android Studio 3.0踩坑篇自定義apk名稱

報錯日誌 Cannot set the value of read-only property ‘outputFile’ Android Studio3.0之前用法如下 applicationVariants.all { variant ->

Android響應式程式設計RxJava2.0

前言 優點: 1、鏈式操作 2、非同步優化 實戰 先來個簡單的使用示例 Observable .create(new ObservableOnSubscribe<String>() {

Android Studio 3.0+以上 版本 填坑依賴報錯。

Error:java.lang.RuntimeException: Annotation processors must be explicitly declared now.  The following dependencies on the compile classpath a

Android動態許可權6.0以上及6.0以下動態許可權申請遇到的問題

今天來記錄一個問題,因為專案需要用到zxing,那就必須要用到攝像頭,那麼就需要動態的去申請許可權,先貼個6.0以上的申請許可權的程式碼 這個在網上隨便搜一搜都有很多程式碼塊 就不做過多的解釋了 今天主要討論的是在6.0以下遇到的一個問題 /** * 請求許可

Android常用控制元件SeekBar的使用

SeekBar的應用非常廣,比如用來顯示音量條、播放進度條,有水平顯示也有垂直顯示,但Android只給我們提供了水平的,可以用系統預設的樣式也可以用我們自定義的樣式,總之進度條的用法多種多樣,如果Android沒有提供也能我們自己去定製,先上圖 使用圖片自定義水平進度

GradleCould not find com.android.tools.buildgradle3.0.0. Searched in the followi

Could not find com.android.tools.buildgradle3.0.0. Searched 解決辦法:這是因為在gradle中的repositories 缺少了 ‘goog

Android Studio 3.0 填坑依賴報錯。

Error:java.lang.RuntimeException: Annotation processors must be explicitly declared now.  The follow

AndroidJNI① AS3.0以下DNK下載配置和第一個JNI程式

一、JNI介紹 JNI(Java Native Interface):一個協議,這個協議用來溝通java程式碼和外部的原生代碼(c/c++), 外部的c/c++程式碼也可以呼叫java程式碼。 1.1 C語言的優勢: ①效率上 C/C++是本地語言,比java更高效;

[Android] 環境配置正式版Android Studio 1.0

昨天看見 Android Studio 1.0 正式版本釋出了;心裡挺高興的。 算是忠實使用者了吧,從去年開發者大會一開始出現 AS 後就開始使用了;也是從那時開始就基本沒有用過 Eclipse 了;一路走來,遇到過 BUG ,也不斷的去國外找資源 找解決辦法。總的來說挺

Android 美 從0到1 -- 高手路》

Android 之美 從0到1 – 高手之路 隨著Android 面試題總結,已經形成比較多的篇幅,為了方便大家閱讀,本篇將作為面試題總結導讀,也將成為Android 面試題的大綱,也只是Android 之美 從0到1 的一部分,陸續補充和完善,希望大家

android 開發 EventBus3.0不同處詳細介紹

compile 'org.greenrobot:eventbus:3.0.0' 如果你看了之前的文章,應該已經會簡單使用EventBus了,上一節,我們用了onEventMainThread這個方法。其實在3.0之前還有其它的一些方法 onEven

AndroidAndroid 6.0許可權機制及開發流程詳解

許可權機制變更的背景 在Android6.0之前,app安裝時會提示使用者此app需要使用哪些許可權,但使用者不能單獨對某項許可權進行授權或拒絕,只要使用者選擇了安裝,即表示使用者接受了app對這些許可權的使用,如果使用者不希望app獲取某些涉及隱私的資訊,例如讀取

一步一坑學android禁用Appt2(andriod studio3.0

唔,你的問題是什麼呢?1)上方提示R檔案缺失?2)Error:java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs fo