效果圖
ScreenGif4.gif
- 可以通過(guò)旋轉(zhuǎn)view或者直接拖動(dòng)來(lái)控制進(jìn)度诲祸。
- 根據(jù)旋轉(zhuǎn)角度,音量的移動(dòng)速度會(huì)改變
- 根據(jù)旋轉(zhuǎn)角度,歸位時(shí)候的速度會(huì)改變
實(shí)現(xiàn)流程
- 重寫(xiě)
onMeasure()
谍婉,使得高度為外部矩形的高度+padding更啄。 - 重寫(xiě)
onDraw()
,繪制兩個(gè)矩形和一個(gè)球稚疹。 - 重寫(xiě)
onTouchEvent()
判斷是點(diǎn)擊小球移動(dòng)還是旋轉(zhuǎn)控件移動(dòng),并且判斷點(diǎn)擊是控件左半部分還是右半部分祭务,在手指抬起時(shí)内狗,執(zhí)行歸為動(dòng)畫(huà)怪嫌。 - 設(shè)置音量改變接口供外部使用。
使用
記得要在外層Linearlayout中要添加clipChildred=false
柳沙。岩灭。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:clipChildren="false"
tools:context=".MainActivity">
<TextView
android:textSize="14sp"
android:textColor="#333333"
android:id="@+id/tv"
android:gravity="center"
android:layout_marginTop="100dp"
android:layout_marginBottom="20dp"
android:text="音量 :"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.lsp.ProgressView
android:id="@+id/voice"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
代碼
public class ProgressView extends View {
private static final String TAG = "HappyVoiceView";
/**
* 外層矩形框畫(huà)筆
*/
private Paint outRectPaint;
/**
* 內(nèi)層矩形框畫(huà)筆
*/
private Paint innerRectPaint;
/**
* 音量控制球畫(huà)筆
*/
private Paint ballPaint;
/**
* 外層,內(nèi)層赂鲤,球的矩形范圍
*/
private RectF rectF1, rectF2, ballRect;
/**
* 外層矩形高度
*/
private int outRectHeight = 50;
/**
* 內(nèi)層矩形框畫(huà)筆
*/
private int innerRectHeight = 20;
/**
* 小球的半徑
*/
private int circleRadius = 15;
/**
* 內(nèi)層小球可移動(dòng)范圍
*/
private int length = 0;
/**
* 是否是旋轉(zhuǎn)控件
*/
private boolean doRoate = false;
/**
* 豎直方向偏移
*/
private float downY;
/**
* 角度
*/
private int degress = 0;
/**
* 手指抬起歸位
*/
private ValueAnimator valueAnimator;
/**
* 小球當(dāng)前位置
*/
private int ballCurrentLength = 0;
/**
* 從左面旋轉(zhuǎn)控件
*/
private boolean isTouchLeft = false;
/**
* 從右面面旋轉(zhuǎn)控件
*/
private boolean isTouchBall = false;
/**
* 小球移動(dòng)最終速度
*/
private int speed = 1;
/**
* 小球最小速度
*/
private int minSpeed = 2;
/**
* 音量改變監(jiān)聽(tīng)
*/
private OnVoiceUpdateLinstener voiceUpdateLinstener;
public void setVoiceUpdateLinstener(OnVoiceUpdateLinstener voiceUpdateLinstener) {
this.voiceUpdateLinstener = voiceUpdateLinstener;
}
public ProgressView(Context context) {
super(context);
init();
}
public ProgressView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(widthSize, outRectHeight + getPaddingTop() + getPaddingBottom());
}
private void init() {
outRectPaint = initPaint();
outRectPaint.setColor(Color.CYAN);
innerRectPaint = initPaint();
innerRectPaint.setColor(Color.YELLOW);
ballPaint = initPaint();
ballPaint.setColor(Color.RED);
ballRect = new RectF();
}
private void initValueAnimator() {
valueAnimator = ValueAnimator.ofInt(degress, 0);
valueAnimator.setDuration(Math.abs(degress)/10*100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
degress = (int) valueAnimator.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
}
private Paint initPaint() {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); //抗鋸齒
paint.setDither(true); //防抖動(dòng)
paint.setColor(Color.CYAN);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeCap(Paint.Cap.SQUARE);
return paint;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);
dealBallLength();
rectF1 = new RectF((-getMeasuredWidth() / 2) + getPaddingLeft(), -outRectHeight / 2, (getMeasuredWidth() / 2) - getPaddingLeft(), outRectHeight / 2);
rectF2 = new RectF((-getMeasuredWidth() / 2) + getPaddingLeft() + getPaddingLeft(), -innerRectHeight / 2, (getMeasuredWidth() / 2) - getPaddingLeft() - getPaddingLeft(), innerRectHeight / 2);
length = (int) rectF2.width();
canvas.rotate(degress);
canvas.drawRoundRect(rectF1, 10, 10, outRectPaint);
canvas.drawRoundRect(rectF2, 20, 20, innerRectPaint);
canvas.drawCircle((rectF2.left + ballCurrentLength) + circleRadius / 2, rectF2.centerY(), circleRadius, ballPaint);
ballRect.left = (rectF2.left + ballCurrentLength) + circleRadius / 2 - circleRadius;
ballRect.right = (rectF2.left + ballCurrentLength) + circleRadius / 2 + circleRadius;
ballRect.top = rectF2.centerY() - circleRadius;
ballRect.bottom = rectF2.centerY() + circleRadius;
if (voiceUpdateLinstener != null) {
voiceUpdateLinstener.onVoiceChanged((int) ((float) ballCurrentLength / length * 100));
}
}
private int dealBallLength() {
speed = Math.abs(degress) / 3 + minSpeed;
if (degress > 0 && ballCurrentLength < length) {
speed = ballCurrentLength + speed > length ? (length - ballCurrentLength) : speed;
ballCurrentLength += speed;
invalidate();
} else if (degress < 0 && ballCurrentLength > 0) {
speed = ballCurrentLength - speed < 0 ? ballCurrentLength : speed;
ballCurrentLength -= speed;
invalidate();
}
return ballCurrentLength;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float y = event.getY();
float x = event.getX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (valueAnimator != null && valueAnimator.isRunning()) {
return false;
}
if (ballRect.contains(event.getX() - getMeasuredWidth() / 2, y - getMeasuredHeight() / 2)) {
isTouchBall = true;
break;
}
if (rectF1.contains(event.getX() - getMeasuredWidth() / 2, y - getMeasuredHeight() / 2)) { //平移過(guò)坐標(biāo)系
doRoate = true;
downY = (int) event.getY();
if (event.getX() - getMeasuredWidth() / 2 <= 0) {
isTouchLeft = true;
} else {
isTouchLeft = false;
}
}
break;
case MotionEvent.ACTION_MOVE:
if (isTouchBall) {
ballCurrentLength = (int) (x - getPaddingLeft() - getPaddingLeft());
if (ballCurrentLength < 0) {
ballCurrentLength = 0;
} else if (ballCurrentLength > length) {
ballCurrentLength = length;
}
invalidate();
break;
}
x = (float) Math.atan((y - downY) / rectF1.right);
degress = (int) Math.toDegrees(x);
degress = isTouchLeft ? -degress : degress;
invalidate();
break;
case MotionEvent.ACTION_UP:
if (doRoate) {
initValueAnimator();
}
doRoate = false;
isTouchBall = false;
break;
}
return true;
}
public interface OnVoiceUpdateLinstener {
void onVoiceChanged(int voice);
}
}