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 {
    }

}


属性文件 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="S_centerState" format="boolean"/> <!-- 是否 0 在中心 -->
        <attr name="S_backColor" format="color"/><!-- 背景 拉条杆 底色 -->
        <attr name="S_backHeight" format="dimension"/><!-- 背景 拉条杆 height -->
        <attr name="S_backFrameColor" format="color"/><!-- 背景 圆角边框 描边-->
        <attr name="S_backFrameSize" format="dimension"/><!-- 背景 圆角边框 宽度 -->
        <attr name="S_progressColor" format="color"/><!--progress 进度 颜色-->
        <attr name="S_progressHeight" format="dimension"/><!--progress height -->
        <attr name="S_progressMinusColor" format="color"/><!-- 0在中心 时  负数 的颜色 -->
        <attr name="S_progress" format="integer"/><!--当前 progress-->
        <attr name="S_thumbNormalRadius" format="dimension"/><!-- thumb 圆 半径 -->
        <attr name="S_thumbPressRadius" format="dimension"/><!-- thumb 圆 触摸时 半径 -->
        <attr name="S_thumbColor" format="color"/><!-- thumb 圆 颜色 -->
        <attr name="S_textSize" format="dimension"/><!--进度字体大小-->
        <attr name="S_textColor" format="color"/><!--进度字体 颜色-->
        <attr name="S_textBackColor" format="color"/><!--进度字体 背景 颜色-->
        <attr name="S_textBackRadius" format="dimension"/><!--进度字体背景 圆角矩形 弧度-->
    </declare-styleable>
</resources>

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值