Android自定义拽托挡位栏

自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="DragStepSeekBar">
        <!-- 滑轨的颜色 -->
        <attr name="trackColor" format="color" />
        <!-- 轨道的高度 -->
        <attr name="trackHeight" format="dimension" />
        <!-- 滑块的颜色 -->
        <attr name="thumbColor" format="color" />
        <!-- 滑块的圆角 -->
        <attr name="thumbRadius" format="dimension" />
        <!-- 滑块图片,如果设置了图片,则不使用颜色 -->
        <attr name="thumbImage" format="reference" />
        <!-- 挡位点的颜色 -->
        <attr name="positionColor" format="color" />
        <!-- 挡位点的圆角角度 -->
        <attr name="positionRadius" format="dimension" />
    </declare-styleable>
</resources>

Java代码

  • 挡位枚举
/**
 * 挡位
 */
public enum MVQuality {
    // 普通
    NORMAL("normal"),

    // 精美
    GOOD("good"),

    // 极致
    EXTREME("excellent");

    /**
     * 值
     */
    final String value;

    MVQuality(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}
  • 自定义View类
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * 自定义拖动式,挡位选择器
 */
public class DragStepSeekBar extends View {
    /**
     * 轨道的颜色
     */
    private int trackColor = Color.GRAY;
    /**
     * 轨道的高度,单位为px
     */
    private float trackHeight = dp2px(1f);
    /**
     * 滑块的颜色
     */
    private int thumbColor = Color.BLUE;
    /**
     * 定位点的颜色
     */
    private int positionColor = Color.DKGRAY;
    /**
     * 滑块的半径
     */
    private float thumbRadius = 20f;
    /**
     * 定位点的半径
     */
    private float positionRadius = 10f;

    /**
     * 当前挡位
     */
    private MVQuality currentStep = MVQuality.NORMAL;
    /**
     * 画笔
     */
    private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    /**
     * 挡位数量
     */
    private final float[] positions = new float[MVQuality.values().length];
    /**
     * 当前滑块的位置
     */
    private float thumbPosition;
    /**
     * 滑块的图片,如果设置了则使用图片,否则自己绘制圆形
     */
    private Bitmap thumbImage;
    /**
     * 挡位变化时回调
     */
    private OnStepChangeListener stepChangeListener;

    public DragStepSeekBar(Context context) {
        super(context);
        init(context, null);
    }

    public DragStepSeekBar(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public DragStepSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DragStepSeekBar);
            try {
                trackColor = typedArray.getColor(R.styleable.DragStepSeekBar_trackColor, Color.GRAY);
                thumbColor = typedArray.getColor(R.styleable.DragStepSeekBar_thumbColor, Color.WHITE);
                positionColor = typedArray.getColor(R.styleable.DragStepSeekBar_positionColor, Color.DKGRAY);
                thumbRadius = typedArray.getDimension(R.styleable.DragStepSeekBar_thumbRadius, thumbRadius);
                positionRadius = typedArray.getDimension(R.styleable.DragStepSeekBar_positionRadius, positionRadius);
                int thumbImageResId = typedArray.getResourceId(R.styleable.DragStepSeekBar_thumbImage, 0);
                if (thumbImageResId != 0) {
                    thumbImage = BitmapFactory.decodeResource(getResources(), thumbImageResId);
                    // 根据thumbRadius缩放图片
                    if (thumbImage != null) {
                        // 滑块图片的直径为2倍半径
                        int size = (int) (thumbRadius * 2);
                        thumbImage = Bitmap.createScaledBitmap(thumbImage, size, size, true);
                    }
                }
                // 轨道高度,默认2dp
                trackHeight = typedArray.getDimension(
                        R.styleable.DragStepSeekBar_trackHeight,
                        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics())
                );
            } finally {
                typedArray.recycle();
            }
        }

        // 设置画笔
        paint.setStrokeWidth(trackHeight);
        // 设置笔触为圆角
        paint.setStrokeCap(Paint.Cap.ROUND);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int minWidth = Math.max(getPaddingLeft() + getPaddingRight() + 200, getSuggestedMinimumWidth());
        int minHeight = Math.max(getPaddingTop() + getPaddingBottom() + 100, getSuggestedMinimumHeight());
        int width = resolveSize(minWidth, widthMeasureSpec);
        int height = resolveSize(minHeight, heightMeasureSpec);
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        initPositions();
        updateThumbPosition();
    }

    /**
     * 初始化定位点
     */
    private void initPositions() {
        MVQuality[] steps = MVQuality.values();
        int count = steps.length;

        int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight();

        if (count > 1) {
            // 等分计算公式:(总宽度)/(挡位数-1) * 当前索引
            float interval = (float) contentWidth / (count - 1);
            for (int i = 0; i < count; i++) {
                positions[i] = getPaddingLeft() + interval * i;
            }
        } else {
            // 只有一个挡位时居中显示
            positions[0] = getPaddingLeft() + contentWidth / 2f;
        }
    }

    /**
     * 根据当前挡位,更新滑块位置
     */
    private void updateThumbPosition() {
        thumbPosition = positions[currentStep.ordinal()];
    }

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

        // 绘制背景轨道
        paint.setColor(trackColor);
        float trackY = getHeight() / 2f;
        canvas.drawLine(getPaddingLeft(), trackY, getWidth() - getPaddingRight(), trackY, paint);

        // 绘制三个定位点
        paint.setColor(positionColor);
        for (int i = 0; i < positions.length; i++) {
            float position = positions[i];
            // 只画中间的那个定位点
            if (i == 1) {
                canvas.drawCircle(position, trackY, positionRadius, paint);
            }
        }

        // 绘制滑块图片
        if (thumbImage != null) {
            float left = thumbPosition - thumbImage.getWidth() / 2f;
            float top = trackY - thumbImage.getHeight() / 2f;
            canvas.drawBitmap(thumbImage, left, top, paint);
        } else {
            // 如果没有设置图片,则自己绘制圆形
            paint.setColor(thumbColor);
            canvas.drawCircle(thumbPosition, trackY, thumbRadius, paint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 请求父控件,不要拦截触摸事件
                getParent().requestDisallowInterceptTouchEvent(true);
            case MotionEvent.ACTION_MOVE:
                float x = Math.max(getPaddingLeft(), Math.min(event.getX(), getWidth() - getPaddingRight()));

                // 查找最近的定位点
                int closestIndex = findClosestIndex(x);
                // 根据定位点获取对应的挡位
                MVQuality newStep = MVQuality.values()[closestIndex];

                // 如果新的挡位和当前挡位不同,则更新
                if (newStep != currentStep) {
                    // 更新当前挡位
                    currentStep = newStep;
                    // 根据当前挡位,更新滑块位置
                    updateThumbPosition();
                    // 重绘
                    invalidate();
                    // 通知外部改变
                    if (stepChangeListener != null) {
                        stepChangeListener.onStepChanged(newStep);
                    }
                }
                return true;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 根据触摸点的x坐标,查找最近的定位点位置
     */
    private int findClosestIndex(float x) {
        int closestIndex = 0;
        float minDistance = Float.MAX_VALUE;
        for (int i = 0; i < positions.length; i++) {
            float distance = Math.abs(positions[i] - x);
            if (distance < minDistance) {
                minDistance = distance;
                closestIndex = i;
            }
        }
        return closestIndex;
    }

    /**
     * 设置挡位变化回调
     */
    public void setOnStepChangeListener(OnStepChangeListener listener) {
        this.stepChangeListener = listener;
    }

    /**
     * 设置当前挡位
     */
    public void setCurrentStep(MVQuality step) {
        currentStep = step;
        updateThumbPosition();
        invalidate();
    }

    /**
     * 设置滑块图片
     */
    public void setThumbImage(int resId) {
        thumbImage = BitmapFactory.decodeResource(getResources(), resId);
        // 根据当前滑块半径缩放
        if (thumbImage != null) {
            thumbImage = Bitmap.createScaledBitmap(thumbImage, (int) (thumbRadius * 2), (int) (thumbRadius * 2), true);
        }
        invalidate();
    }

    /**
     * 挡位变化监听回调
     */
    public interface OnStepChangeListener {
        void onStepChanged(MVQuality step);
    }

    private float dp2px(float dip) {
        Context context = getContext();
        Resources resources = context.getResources();
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, resources.getDisplayMetrics());
    }

    private static float px2dp(float pixel) {
        Context context = ContextUtil.getContext();
        Resources resources = context.getResources();
        DisplayMetrics displayMetrics = resources.getDisplayMetrics();
        return pixel / displayMetrics.density;
    }
}

简单使用

  • xml布局
<com.xxx.widget.DragStepSeekBar
    android:id="@+id/select_effect_level_drag_step_seek_bar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginStart="10dp"
    android:layout_marginTop="16dp"
    android:layout_marginEnd="10dp"
    android:paddingStart="7dp"
    android:paddingEnd="7dp"
    app:positionColor="#B1B1B1"
    app:positionRadius="2dp"
    app:thumbColor="@color/white"
    app:thumbImage="@drawable/ic_slider_thumb"
    app:thumbRadius="10dp"
    app:trackColor="#646464"
    app:trackHeight="2dp" />
  • Java代码中,设置监听
binding.selectEffectLevelLayout.selectEffectLevelDragStepSeekBar.setOnStepChangeListener(newStep -> {
    // 更新挡位
});
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容