注:下文中SettingItemView源代码(由于涉及信息安全,个人私密博客,与本文总结的进度条并没有多少关联,仅供自己review)
需求简单描述:
在项目中有多个自定义的卡片SettingItemView(也是一个自定义View),其中一个卡片中要使用到一个竖直的进度条。
我们这边要实现的,就是设计一个自定义的竖直进度条,并且只在这一个卡片中显示出来。
这边需要
(1)自定义一个VerticalSeekBar.java文件
(2)在values文件中的color.xml和attrs.xml中添加(1)中需要的对应属性
(3)在卡片的xml中放入自定义的VerticalSeekBar,调整位置及visivility等属性。
(这边就是放入自定义的卡片SettingItemView对应的setting_item.xml中,因为后续需要总结的开发过程中的问题还会涉及到这个卡片,所以这边提一下)
综上,核心代码就是VerticalSeekBar.java,color.xml和attrs.xml,然后就可以把自定义的VerticalSeekBar直接在需要的卡片的xml文件中直接应用即可。
先直接放代码:
(1)VerticalSeekBar.java
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
public class VerticalSeekBar extends View {
int maxValue;
int minValue;
int currentValue;
int count;
float eachHeight;
float yCoordinate;
Paint backgroundPaint;
OnChangeListener listener;
public VerticalSeekBar(Context context) {
this(context, null);
}
public VerticalSeekBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public VerticalSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VerticalSeekBar);
maxValue = typedArray.getInteger(R.styleable.VerticalSeekBar_max_value, 7);
minValue = typedArray.getInteger(R.styleable.VerticalSeekBar_min_value, 1);
currentValue = minValue;
backgroundPaint = new Paint();
backgroundPaint.setColor(getResources().getColor(R.color.grey1));
backgroundPaint.setAntiAlias(true);
typedArray.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 14;
int height = 219;
int measuredWidth = getSize(widthMode, widthSize, width);
int measuredHeight = getSize(heightMode, heightSize, height);
setMeasuredDimension(measuredWidth, measuredHeight);
count = maxValue - minValue + 1;
eachHeight = (measuredHeight - measuredWidth) / (float) count;
yCoordinate = measuredWidth / 2f + eachHeight * (maxValue - currentValue + 1);
if (currentValue == maxValue) {
yCoordinate = measuredWidth / 2f;
} else if (currentValue == minValue) {
yCoordinate = measuredHeight - measuredWidth / 2f;
} else {
yCoordinate = measuredWidth / 2f + eachHeight * (maxValue - currentValue + 1);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
backgroundPaint.setColor(getResources().getColor(R.color.grey1));
RectF topRectF = new RectF(0,
measuredWidth / 2,
measuredWidth,
Math.min(Math.max(yCoordinate, getMeasuredWidth() / 2f),
getMeasuredHeight() - getMeasuredWidth() / 2f));
canvas.drawRect(topRectF, backgroundPaint);
backgroundPaint.setColor(getResources().getColor(yCoordinate == 0 ? R.color.yellow1 : R.color.grey1));
RectF topCircle = new RectF(0, 0, measuredWidth, measuredWidth);
canvas.drawArc(topCircle, 180, 180, false, backgroundPaint);
backgroundPaint.setColor(getResources().getColor(R.color.yellow1));
RectF bottomRectF = new RectF(0, Math.max(Math.min(yCoordinate, getMeasuredHeight() - getMeasuredWidth() / 2f), getMeasuredWidth() / 2f), measuredWidth, measuredHeight - measuredWidth / 2);
canvas.drawRect(bottomRectF, backgroundPaint);
RectF bottomCircle = new RectF(0, measuredHeight - measuredWidth, measuredWidth, measuredHeight);
backgroundPaint.setColor(getResources().getColor(yCoordinate == getMeasuredHeight() ? R.color.grey1 : R.color.yellow1));
canvas.drawArc(bottomCircle, 0, 180, false, backgroundPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
float y = event.getY();
if (y < getMeasuredWidth() / 2f) {
y = 0;
} else if (y > getMeasuredHeight() - getMeasuredWidth() / 2f) {
y = getMeasuredHeight();
}
yCoordinate = y;
if (listener != null) {
getCurrentValue(y);
listener.onChange(this, currentValue);
}
invalidate();
break;
default:
break;
}
return true;
}
private int getSize(int mode, int size, int defaultSize) {
int measuredSize = defaultSize;
switch (mode) {
case MeasureSpec.EXACTLY:
measuredSize = size;
break;
case MeasureSpec.AT_MOST:
break;
default:
break;
}
return measuredSize;
}
public void getCurrentValue(float eventY) {
float allHeight = eventY - getMeasuredWidth() / 2f;
int currentValue = maxValue - (int) (allHeight / eachHeight);
if (currentValue < minValue) {
currentValue = minValue;
}
this.currentValue = currentValue;
}
public void setOnChangeListener(OnChangeListener listener) {
this.listener = listener;
}
public int getCurrentValue() {
return currentValue;
}
public void setCurrentValue(int currentValue) {
this.currentValue = currentValue;
}
public void setMaxValue(int maxValue) {
this.maxValue = maxValue;
}
public void setMinValue(int minValue) {
this.minValue = minValue;
}
public interface OnChangeListener {
void onChange(View view, int currentValue);
}
}
通过直接修改60行的width和height属性可以更改进度条的宽高。
(2)color.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="grey1">#3B4152</color>
<color name="yellow1">#FFD800</color>
</resources>
(3)attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="VerticalSeekBar">
<attr name="image_src" format="reference" />
<attr name="max_value" format="integer" />
<attr name="min_value" format="integer" />
</declare-styleable>
</resources>
(4)将自定义的进度条放入卡片中(项目中对应的即setting_item.xml)使用,并设置一些对应的位置、visibility等属性
<com.example.verticalseekbardemo.VerticalSeekBar
android:id="@+id/vs_luminance_set"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="118.3dp"
android:layout_marginTop="23.4dp"
android:layout_marginBottom="23.6dp"
android:visibility="visibility"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
注:这边visibility设置的是visibility,是为了显示进度条,简化代码,方便本文的总结,实际开发中设置成gone,在需要显示时在SettingItemView中用代码实现setVisibility()方法进行显示。
运行上面的代码,即可实现竖直进度条的效果。
接下来总结项目中遇到的关于VerticalSeekBar进度条相关的一系列问题及解决方法:
(1)解决进度条在xml中要么设置成visiblity在所有卡片中显示,要么gone都不显示的问题。在java文件中setVisibility方法不起作用?
原因:
这是个很低级的错误,纯粹的粗心造成,放在这就当是提醒自己编码时要细心一点,少犯低级错误。
SettingItemView.java文件中119行开始
有下面代码:
boolean isBottomTextEmpty = TextUtils.isEmpty(mBottomTextContent);
if (isBottomTextEmpty) {
return;
}
使得判断还没开始就结束了。
解决方法:
把if(mShowProgressBar){}移动到上面代码的上方,先运行,则不会直接return。
(2)解决上述问题后进度条已经可以在需要的卡片中正常显示,但是又出现了一个新的问题:
即当进度条被拖动时,横向的滚动条也会被移动,这是个问题。
注:进度条所在的卡片及其他卡片都放置在一个可以拖动的横向滚动条中
解决方法:
在SettingItemView.java的第77行中添加如下代码:
mLuminanceSet.setOnTouchListener(((view1, motionEvent) -> {
view1.getParent().requestDisallowInterceptTouchEvent(true);
return false;
}));
作用是:限制外面父布局的监听(这个后续可以再仔细研究一下)
(3)问题2解决后,已经可以正常使用进度条,但是会有一个警告出现
(警告)Android中报Custom view ×××
has setOnTouchListener called on it but does not override performClick警告
大概意思就是点击事件会跟OnTocuch事件冲突
解决方法:在确保有执行OnTouchlistener的情况下,只需要抑制这种警告。加上@SuppressLint(“ClickableViewAccessibility”)就可以了。
即直接在构造方法外加一句:@SuppressWarnings(“ClickableViewAccessibility”)
则能够忽略警告,即可。(这边加在了SettingItemView.java的第77行,即构造方法SettingItemView的上方)