自定义属性
<?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;
}
}
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;
}
}
简单使用
<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" />
binding.selectEffectLevelLayout.selectEffectLevelDragStepSeekBar.setOnStepChangeListener(newStep -> {
// 更新挡位
});