网上看的很多竖向的seekbar,但是进度条都是从下到上滑动,设计要求是从上到下的,就自己改了改,代码如下
/**
* Created by gdhuo on 2018/10/29.
*/
public class VerticalSeekBar extends SeekBar {
private static final String TAG = VerticalSeekBar.class.getSimpleName();
public static final int ROTATION_ANGLE_CW_90 = 90;
public static final int ROTATION_ANGLE_CW_270 = 270;
private int mRotationAngle = ROTATION_ANGLE_CW_90;
public VerticalSeekBar(Context context) {
super(context);//注意是super 而不是调用其他构造函数
initialize(context, null, 0, 0);
}
public VerticalSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs, 0, 0);
}
public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize(context, attrs, defStyle, 0);
}
private void initialize(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.VerticalSeekBar, defStyleAttr, defStyleRes);
final int rotationAngle = a.getInteger(R.styleable.VerticalSeekBar_seekBarRotation, 0);
if (isValidRotationAngle(rotationAngle)) {
mRotationAngle = rotationAngle;
}
a.recycle();
}
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(h, w, oldh, oldw);
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
protected void onDraw(Canvas c) {
if(mRotationAngle == ROTATION_ANGLE_CW_270) {
//从下到上
c.rotate(270);
c.translate(-getHeight(), 0);//注意旋转后需要移动才可显示出来
} else if(mRotationAngle == ROTATION_ANGLE_CW_90) {
//从上到下
c.rotate(90);
c.translate(0, -getWidth());
}
super.onDraw(c);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
if(mRotationAngle == ROTATION_ANGLE_CW_270) {
//从下到上
int progress = getMax() - (int)(event.getY()*(getMax()*2+1)/(getHeight()*2)+0.5);
setProgress(progress>0?progress:0);
} else if(mRotationAngle == ROTATION_ANGLE_CW_90) {
//从上到下
setProgress((int) (getMax() * event.getY() / getHeight()));
}
onSizeChanged(getWidth(), getHeight(), 0, 0);
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
private static boolean isValidRotationAngle(int angle) {
return (angle == ROTATION_ANGLE_CW_90 || angle == ROTATION_ANGLE_CW_270);
}
样式如下
<declare-styleable name="VerticalSeekBar">
<attr name="seekBarRotation">
<enum name="CW90" value="90" />
<enum name="CW270" value="270" />
</attr>
</declare-styleable>
遇到的坑
1.直接调用seekbar.setProgress()发现thumb图片位置一直不对,解决方法如下
@Override
public void setProgress(int progress) {//重写setProgress
super.setProgress(progress);
onSizeChanged(getWidth(), getHeight(), 0, 0);
}
2.按下后始终有白点,并且白点位置一直在未翻转之前的位置上移动,可以通过设置android:background="@null"去掉
3.按下后thumb位置总是不准确,需要在滑动或放手时候重新计算progress。方法如下
//从下到上
int progress = getMax() - (int)(event.getY()*(getMax()*2+1)/(getHeight()*2)+0.5);
4.按下后发现样式配置里设置的selector的pressed对应的图片显示不出来
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/seekbar_pressed" android:state_pressed="true"/>
<item android:drawable="@drawable/seekbar_unselect"/>
</selector>
解决方法如下
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
setPressed(true);//移动的时候设置按下效果,然后重新绘制thumb
if (getThumb() != null) {
// This may be within the padding region.
invalidate(getThumb().getBounds());
}
calProgress(event);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
setPressed(false);//放手的时候取消按下效果,重新绘制thumb
if (getThumb() != null) {
// This may be within the padding region.
invalidate(getThumb().getBounds());
}
calProgress(event);
break;
}
return true;
}
private void calProgress(MotionEvent event) {
if(mRotationAngle == ROTATION_ANGLE_CW_270) {
//从下到上
int progress = getMax() - (int)(event.getY()*(getMax()*2+1)/(getHeight()*2)+0.5);
progress = progress>0?progress:0;
setProgress(progress);
} else if(mRotationAngle == ROTATION_ANGLE_CW_90) {
//从上到下
setProgress((int) (getMax() * event.getY() / getHeight()));
}
onSizeChanged(getWidth(), getHeight(), 0, 0);
}
5.用户调节seekbar,但是在onProgressChanged回调的参数fromUser一直为false。查看源码后需要反射setProgressInternal,解决方法
private void reflectSetProgress(int progress) {
//反射方法 boolean setProgressInternal(int progress, boolean fromUser, boolean animate)
try {
Class clazz = ProgressBar.class;
Method method = clazz.getDeclaredMethod("setProgressInternal",int.class,boolean.class,boolean.class);
method.setAccessible(true);
method.invoke(this,progress,true,false);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private void calProgress(MotionEvent event) {
if(mRotationAngle == ROTATION_ANGLE_CW_270) {
//从下到上
int progress = getMax() - (int)(event.getY()*(getMax()*2+1)/(getHeight()*2)+0.5);
LogUtil.d(TAG,"set progress "+progress + " max "+getMax() + " getY "+event.getY() +" getHeight "+getHeight());
progress = progress>0?progress:0;
reflectSetProgress(progress);
} else if(mRotationAngle == ROTATION_ANGLE_CW_90) {
//从上到下
reflectSetProgress((int) (getMax() * event.getY() / getHeight()));
}
onSizeChanged(getWidth(), getHeight(), 0, 0);
}
记录一下,以便日后查阅,遇到相同问题的人也可以参考参考