一、前言

 

巩固自定义view基础用,本次尝试构建一个拖动条组件。代码参考于 https://github.com/woxingxiao/BubbleSeekBar ,精简其中高度可重用的部分,仅保留基本的拖拽功能,由于代码很巧妙,以后可以再深入探究学习。

 

本文在前面自定义view的基础上,增加了测量(onMeasure) 以及 触碰屏幕事件(onTouchEvent)。相信可以一步步踏实巩固,学会自定义view的知识。由于本拖动条仍是一个view,不需要涉及到布局(onLayout) 。以后学习自定义viewGroup时再另行探究。老规矩第一章先放效果图和全部代码。

 

二、效果图

 

   

 

三、代码

 

values/attr.xml

 

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MySeekBar">

        <attr name="progress_max" format="float|reference"/> <!-- 进度条最大值 -->
        <attr name="progress_min" format="float|reference"/> <!-- 进度条最小值 -->
        <attr name="progress_default" format="float|reference"/> <!-- 进度条默认值 -->

        <attr name="track_left_height" format="dimension|reference"/> <!-- 进度条左边高度 -->
        <attr name="track_right_height" format="dimension|reference"/> <!-- 进度条右边高度 -->

        <attr name="track_left_color" format="color|reference"/> <!-- 进度条左边颜色 -->
        <attr name="track_right_color" format="color|reference"/> <!-- 进度条右边颜色 -->

        <attr name="thumb_color_default" format="color|reference"/> <!-- 拖动滑块默认颜色 -->
        <attr name="thumb_radius_default" format="dimension|reference"/> <!-- 拖动滑块半径 -->

        <attr name="thumb_color_on_dragging" format="color|reference"/> <!-- 拖动滑块拖动中颜色 -->
        <attr name="thumb_radius_on_dragging" format="dimension|reference"/> <!-- 拖动滑块拖动中半径 -->

    </declare-styleable>
</resources>


MySeekBar.java

 

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;

import com.kms.myseekbar.util.DimensionUtil;

import androidx.annotation.Nullable;

public class MySeekBar extends View {

    /********************** 参数 **********************/

    private float progress_max; // 进度条最大值
    private float progress_min; // 进度条最小值
    private float progress_default; // 进度条默认值

    private int track_left_height; // 进度条左边高度
    private int track_right_height; // 进度条右边高度

    private int track_left_color; // 进度条左边颜色
    private int track_right_color; // 进度条右边颜色

    private int thumb_color_default; // 拖动滑块默认颜色
    private int thumb_radius_default; // 拖动滑块半径

    private int thumb_color_on_dragging; // 拖动滑块拖动中颜色
    private int thumb_radius_on_dragging; // 拖动滑块拖动中半径

    /********************** 绘制相关 **********************/

    private Paint paint; // 画笔
    private int xLeft; // 实际的绘图区域按距离父布局左边 padding 算起
    private int xRight; // 到距离父布局右边的的 padding 结束
    private int yCenter; // 确定绘制进度条Y轴意义上的中点

    private int thumb_radius; // 滑动滑块半径

    /********************** 构造函数 **********************/

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

    public MySeekBar(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MySeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MySeekBar, defStyleAttr, 0);

        this.progress_max = typedArray.getFloat(R.styleable.MySeekBar_progress_max, 100);
        this.progress_min = typedArray.getFloat(R.styleable.MySeekBar_progress_min, 0);
        this.progress_default = typedArray.getFloat(R.styleable.MySeekBar_progress_default, progress_min);

        this.track_left_height = typedArray.getDimensionPixelSize(R.styleable.MySeekBar_track_left_height, DimensionUtil.dp2px(8));
        this.track_right_height = typedArray.getDimensionPixelSize(R.styleable.MySeekBar_track_right_height, track_left_height - DimensionUtil.dp2px(2));

        this.track_left_color = typedArray.getColor(R.styleable.MySeekBar_track_left_color, Color.BLUE);
        this.track_right_color = typedArray.getColor(R.styleable.MySeekBar_track_right_color, Color.LTGRAY);

        this.thumb_color_default = typedArray.getColor(R.styleable.MySeekBar_thumb_color_default, track_left_color);
        this.thumb_radius_default = typedArray.getDimensionPixelSize(R.styleable.MySeekBar_thumb_radius_default, track_left_height + DimensionUtil.dp2px(2));

        this.thumb_color_on_dragging = typedArray.getColor(R.styleable.MySeekBar_thumb_color_on_dragging, thumb_color_default);
        this.thumb_radius_on_dragging = typedArray.getDimensionPixelSize(R.styleable.MySeekBar_thumb_radius_on_dragging, thumb_radius_default + DimensionUtil.dp2px(2));

        typedArray.recycle();

        thumb_radius = thumb_radius_default;

        initPaint(); // 初始化画笔
    }

    /********************** Getter and Setter **********************/

    public float getProgressMax() {
        return progress_max;
    }

    public MySeekBar setProgressMax(float progressMax) {
        this.progress_max = progressMax;
        return this;
    }

    public float getProgressMin() {
        return progress_min;
    }

    public MySeekBar setProgressMin(float progressMin) {
        this.progress_min = progressMin;
        return this;
    }

    public float getProgressDefault() {
        return progress_default;
    }

    public MySeekBar setProgressDefault(float progressDefault) {
        this.progress_default = progressDefault;
        return this;
    }

    public int getTrackLeftHeight() {
        return track_left_height;
    }

    public MySeekBar setTrackLeftHeight(int trackLeftHeight) {
        this.track_left_height = trackLeftHeight;
        return this;
    }

    public int getTrackRightHeight() {
        return track_right_height;
    }

    public MySeekBar setTrackRightHeight(int trackRightHeight) {
        this.track_right_height = trackRightHeight;
        return this;
    }

    public int getTrackLeftColor() {
        return track_left_color;
    }

    public MySeekBar setTrackLeftColor(int trackLeftColor) {
        this.track_left_color = trackLeftColor;
        return this;
    }

    public int getTrackRightColor() {
        return track_right_color;
    }

    public MySeekBar setTrackRightColor(int trackRightColor) {
        this.track_right_color = trackRightColor;
        return this;
    }

    public int getThumbColorDefault() {
        return thumb_color_default;
    }

    public MySeekBar setThumbColorDefault(int thumbColorDefault) {
        this.thumb_color_default = thumbColorDefault;
        return this;
    }

    public int getThumbRadiusDefault() {
        return thumb_radius_default;
    }

    public MySeekBar setThumbRadiusDefault(int thumbRadiusDefault) {
        this.thumb_radius_default = thumbRadiusDefault;
        return this;
    }

    public int getThumbColorOnDragging() {
        return thumb_color_on_dragging;
    }

    public MySeekBar setThumbColorOnDragging(int thumbColorOnDragging) {
        this.thumb_color_on_dragging = thumbColorOnDragging;
        return this;
    }

    public int getThumbRadiusOnDragging() {
        return thumb_radius_on_dragging;
    }

    public MySeekBar setThumbRadiusOnDragging(int thumbRadiusOnDragging) {
        this.thumb_radius_on_dragging = thumbRadiusOnDragging;
        return this;
    }

    /********************** 绘制相关 **********************/

    private void initPaint(){
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStrokeCap(Paint.Cap.ROUND);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 仅当 android_layout_width = wrap_content 或未指定时生效,若测出来的size大于你所指定的size (譬如这里是180dp),则使用所指定的size
        int width = resolveSize(DimensionUtil.dp2px(180), widthMeasureSpec);
        int height = thumb_radius_on_dragging * 2; // 控件高度按拖动时的滑块直径
        setMeasuredDimension(width, height); // 强制指定控件大小

        xLeft = getPaddingLeft() + thumb_radius_on_dragging; // 实际的绘图区域按距离父布局左边 padding 算起
        xRight = getMeasuredWidth() - getPaddingRight() - thumb_radius_on_dragging; // 到距离父布局右边的的 padding 结束
        yCenter = getPaddingTop() + thumb_radius_on_dragging; // 确定绘制进度条Y轴意义上的中点
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int currentProgress = (int) (progress_default / progress_max * (xRight-xLeft));
        drawRightTrack(canvas, currentProgress);
        drawLeftTrack(canvas, currentProgress);
        drawThumb(canvas, currentProgress);
    }

    /**
     * 绘制进度条左边
     */
    private void drawLeftTrack(Canvas canvas, int currentProgress){
        paint.setColor(track_left_color);
        paint.setStrokeWidth(track_left_height);
        canvas.drawLine(xLeft, yCenter, xLeft + currentProgress, yCenter, paint);
    }

    /**
     * 绘制拖动滑块
     */
    private void drawThumb(Canvas canvas, int currentProgress){
        paint.setColor(thumb_color_default);
        canvas.drawCircle(xLeft + currentProgress, yCenter, thumb_radius, paint);
    }

    /**
     * 绘制进度条右边
     */
    private void drawRightTrack(Canvas canvas, int currentProgress){
        paint.setColor(track_right_color);
        paint.setStrokeWidth(track_right_height);
        canvas.drawLine(xLeft + currentProgress, yCenter, xRight, yCenter, paint);
    }

    public void startAnim(){
        final ValueAnimator animator = ValueAnimator.ofInt(0,100);
        animator.setDuration(100*500);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setRepeatMode(ValueAnimator.RESTART);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                progress_default = (int) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.start();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()){
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                performClick(); // 若 SeekBar设置了 OnClickListener,可以在此处唤醒监听器
                getParent().requestDisallowInterceptTouchEvent(true); // 不允许父组件拦截触摸事件
                thumb_radius = thumb_radius_on_dragging;
                progress_default = calculateDraggingX(event.getX());
                break;
            default:
                thumb_radius = thumb_radius_default;
        }
        invalidate();
        return true;
    }

    /**
     * 计算拖动值
     *
     * @param x 屏幕上的event.getX()
     * @return 经转换后对应拖动条的进度值
     */
    private float calculateDraggingX(float x){
        if(x < xLeft){
            return progress_min;
        }
        if(x > xRight){
            return progress_max;
        }
        return x / getMeasuredWidth() * progress_max;
    }
}

 

util/DimensionUtil.java

 

import android.content.res.Resources;
import android.util.TypedValue;

public class DimensionUtil {

    public static int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
                Resources.getSystem().getDisplayMetrics());
    }

    public static int sp2px(int sp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
                Resources.getSystem().getDisplayMetrics());
    }

}

 

layout/activity_main.xml

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:padding="10dp">

    <com.kms.myseekbar.MySeekBar
        android:id="@+id/my_seek_bar"
        android:layout_width="match_parent"
        android:layout_height="20dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

MainActivity.java

 

import android.graphics.Color;
import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    MySeekBar mySeekBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mySeekBar = findViewById(R.id.my_seek_bar);
        mySeekBar.setThumbColorDefault(Color.BLUE).setProgressDefault(50);
        // mySeekBar.startAnim();
    }
}

 

完!下一节将讲解下基本思路

 

 posted on 2020-05-25 10:31  蓝天的抛物线  阅读(2451)  评论(0编辑  收藏  举报