如图所示,最近做了一个圆形的裁剪控件,我也觉得这有点奇怪,横的不好吗,不过没办法设计出来就是要搞,那只能画了。
首先简单分析下怎么搞。我们把它拆分成两部分就容易了,简单的就是两个圆形的seekbar。然后裁剪的时间范围肯定不能小于0,所以开始的seekbar不能超过结束的seekbar。分析完成就开始画了。三部曲就不说了,首页画一个圆形的可以拖动的seekbar。
1、画一个底色的圆弧,就是那灰色的一圈,
canvas.drawArc(mRectF, 0, 360, false, circlePaint);
简单解释下第二、三个参数,开始画的地方,要画的角度。
2、画原点了
canvas.drawBitmap(dotBitmapStart, new Rect(0, 0, pointWidth, pointHeight),
new Rect((int) (markPointX - pointWidth / 2), (int) (markPointY - pointWidth / 2),
(int) (markPointX + pointWidth / 2), (int) (markPointY + pointHeight / 2)), circlePaint);
3、就是动态的画原点和扫过的角度了。
拆分就很简单了。
下面贴下代码:
/*
* Copyright (C) 2017 BullyBoo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.lewanjia.dancelog.views;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.lewanjia.dancelog.R;
/**
* by lzj
*/
public class CutMusicCircleSeekBar extends View {
private Paint circlePaint;//
private Paint circlePaintOver;//
private float mHeight;
private float mWidth;
//x轴的原点坐标
private int xOri;
//y轴的原点坐标
private int yOri;
private Context mContext;
private RectF mRectF;
private Bitmap dotBitmapStart;
private Bitmap dotBitmapEnd;
private Drawable dotDrawableEnd;
private float dotRadius;
private float circleB = 0;//半径
private double pointDegrees = 270;//初始圆的位置
private double pointDegreesEnd =270;//初始圆的位置
private final static int VAULE_3 = 3;
private final static int VAULE_6 = 6;
private float markPointX = 0;
private float markPointY = 0;
private float markPointXEnd = 0;
private float markPointYEnd = 0;
private float angle = 90;
private float startangle = 270;
private int maxProgress = 100;
private int progressPercent = 0;
private int progress;
private Matrix matrix;
private float startProcess = 0;
private float endProcess = 360;
private int pointWidth;
private int pointHeight;
public CutMusicCircleSeekBar(Context context) {
this(context, null);
}
public CutMusicCircleSeekBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CutMusicCircleSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
initView(context, attrs, defStyleAttr);
}
private void initView(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CutMusicCircleSeekBar, defStyleAttr, 0);
int count = array.getIndexCount();
for (int i = 0; i < count; i++) {
int attr = array.getIndex(i);
switch (attr) {
}
}
array.recycle();
}
/**
* 初始化画笔
*/
private void init() {
circlePaint = new Paint();
circlePaint.setColor(Color.parseColor("#a4a4a4"));
circlePaint.setAntiAlias(true);
circlePaint.setStyle(Paint.Style.STROKE);// 设置中空的样式
circlePaint.setFlags(Paint.ANTI_ALIAS_FLAG);// 帮助消除锯齿
circlePaint.setStrokeWidth(dip2px(mContext, VAULE_6));
circlePaintOver = new Paint();
circlePaintOver.setColor(Color.parseColor("#ff0000"));
circlePaintOver.setAntiAlias(true);
circlePaintOver.setStyle(Paint.Style.STROKE);// 设置中空的样式
circlePaintOver.setFlags(Paint.ANTI_ALIAS_FLAG);// 帮助消除锯齿
circlePaintOver.setStrokeWidth(dip2px(mContext, VAULE_6));
mRectF = new RectF();
matrix = new Matrix();
Drawable dotDrawable = mContext.getResources().getDrawable(R.mipmap.ic_play_progress_handle_big);
dotDrawableEnd = mContext.getResources().getDrawable(R.mipmap.ic_play_progress_handle_big);
if (dotDrawable != null) {
dotBitmapStart = ((BitmapDrawable) dotDrawable).getBitmap();
dotBitmapEnd = ((BitmapDrawable) dotDrawableEnd).getBitmap();
dotRadius = Math.max(dotRadius, Math.max(dotBitmapStart.getWidth() / 2, dotBitmapStart.getHeight() / 2));
circleB = getWidth() / 2 - dip2px(mContext, VAULE_3) - dotBitmapStart.getWidth() / 4;
pointWidth = dotBitmapEnd.getWidth();
pointHeight = dotBitmapEnd.getHeight();
}
dotRadius = dotBitmapStart.getWidth() / 2;
mRectF = new RectF(dip2px(mContext, VAULE_3) + dotBitmapStart.getWidth() / 4, dip2px(mContext, VAULE_3) + dotBitmapStart.getWidth() / 4,
getWidth() - dip2px(mContext, VAULE_3) - dotBitmapStart.getWidth() / 4, getHeight() - dip2px(mContext, VAULE_3) - dotBitmapStart.getWidth() / 4);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width;
int height;
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = widthSize * 1 / 2;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = heightSize * 1 / 2;
}
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mHeight = getHeight();
mWidth = getWidth();
xOri = getWidth() / 2;
yOri = getHeight() / 2;
init();
}
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e("234", "canvas");
float swipAngle = endProcess - startProcess;
float start = 270 + startProcess;
if (start > 360) {
start = start - 360;
}
canvas.drawArc(mRectF, 0, 360, false, circlePaint);
canvas.drawArc(mRectF, start, swipAngle, false, circlePaintOver);
drawBitmpStart(canvas);
canvas.save();
}
float lastx;
float lasty;
boolean isOntouch = false;
boolean moveFirst = false;
@Override
public boolean onTouchEvent(MotionEvent event) {
isOntouch = true;
float swipAngle = endProcess - startProcess;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastx = event.getX();
lasty = event.getY();
double fisrt = distance(lastx, lasty, markPointX, markPointY);
double second = distance(lastx, lasty, markPointXEnd, markPointYEnd);
moveFirst = fisrt <= second;
if (swipAngle < 0) {
moveFirst = false;
}
moved(lastx, lasty, false, event, moveFirst);
return true;
case MotionEvent.ACTION_MOVE:
lastx = event.getX();
lasty = event.getY();
if (swipAngle < 0) {
moveFirst = false;
}
moved(lastx, lasty, false, event, moveFirst);
Log.e("", "");
return true;
case MotionEvent.ACTION_UP:
return true;
}
return false;
}
private boolean isOnArc(MotionEvent event) {
final float distance = distance(event.getX(), event.getY(),
xOri, yOri);
final float radius = mRectF.width() / 2f;
final float halfStrokeWidth = dotRadius * 4;
return Math.abs(distance - radius) <= halfStrokeWidth;
}
private float distance(float x1, float y1, float x2, float y2) {
return (float) Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}
public int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
private float computeCos(float x, float y) {
float width = x - this.mWidth / 2;
float height = y - this.mHeight / 2;
float slope = (float) Math.sqrt(width * width + height * height);
return height / slope;
}
/**
* @param canvas
*/
private void drawBitmpStart(Canvas canvas) {
if (moveFirst) {
if (!isOntouch) {
markPointX = (float) (Math.cos(Math.toRadians(pointDegrees)) * circleB + xOri);
markPointY = (float) (Math.sin(Math.toRadians(pointDegrees)) * circleB + yOri);
markPointXEnd = (float) (Math.cos(Math.toRadians(pointDegreesEnd)) * circleB + xOri);
markPointYEnd = (float) (Math.sin(Math.toRadians(pointDegreesEnd)) * circleB + yOri);
} else {
markPointX = (float) (xOri + circleB
* Math.cos(Math.atan2(lastx - xOri, yOri - lasty) - (Math.PI / 2)));
markPointY = (float) (yOri + circleB
* Math.sin(Math.atan2(lastx - xOri, yOri - lasty) - (Math.PI / 2)));
}
canvas.drawBitmap(dotBitmapStart, new Rect(0, 0, pointWidth, pointHeight),
new Rect((int) (markPointX - pointWidth / 2), (int) (markPointY - pointWidth / 2),
(int) (markPointX + pointWidth / 2), (int) (markPointY + pointHeight / 2)), circlePaint);
canvas.drawBitmap(dotBitmapEnd, new Rect(0, 0, pointWidth, pointHeight),
new Rect((int) (markPointXEnd - pointWidth / 2), (int) (markPointYEnd - pointWidth / 2),
(int) (markPointXEnd + pointWidth / 2), (int) (markPointYEnd + pointHeight / 2)), circlePaint);
} else {
drawBitmpEnd(canvas);
}
}
/**
* @param canvas
*/
private void drawBitmpEnd(Canvas canvas) {
if (!isOntouch) {
markPointXEnd = (float) (Math.cos(Math.toRadians(pointDegreesEnd)) * circleB + xOri);
markPointYEnd = (float) (Math.sin(Math.toRadians(pointDegreesEnd)) * circleB + yOri);
markPointX = (float) (Math.cos(Math.toRadians(pointDegrees)) * circleB + xOri);
markPointY = (float) (Math.sin(Math.toRadians(pointDegrees)) * circleB + yOri);
// canvas.drawBitmap(dotBitmapEnd, new Rect(0, 0, pointWidth, pointHeight),
// new Rect((int) (markPointXEnd - pointWidth / 2), (int) (markPointYEnd - pointWidth / 2),
// (int) (markPointXEnd + pointWidth / 2), (int) (markPointYEnd + pointHeight / 2)), circlePaint);
} else {
markPointXEnd = (float) (xOri + circleB
* Math.cos(Math.atan2(lastx - xOri, yOri - lasty) - (Math.PI / 2)));
markPointYEnd = (float) (yOri + circleB
* Math.sin(Math.atan2(lastx - xOri, yOri - lasty) - (Math.PI / 2)));
}
// matrix.setRotate(endProcess);
// dotBitmapEnd = null;
// dotBitmapEnd = ((BitmapDrawable) dotDrawableEnd).getBitmap();
// dotBitmapEnd = Bitmap.createBitmap(dotBitmapEnd, 0, 0, pointWidth, pointHeight, matrix, false);
// drawRotateBitmap(canvas,circlePaint,endProcess,markPointXEnd,markPointYEnd);
canvas.drawBitmap(dotBitmapEnd, new Rect(0, 0, pointWidth, pointHeight),
new Rect((int) (markPointXEnd - pointWidth / 2), (int) (markPointYEnd - pointWidth / 2),
(int) (markPointXEnd + pointWidth / 2), (int) (markPointYEnd + pointHeight / 2)), circlePaint);
canvas.drawBitmap(dotBitmapStart, new Rect(0, 0, pointWidth, pointHeight),
new Rect((int) (markPointX - pointWidth / 2), (int) (markPointY - pointWidth / 2),
(int) (markPointX + pointWidth / 2), (int) (markPointY + pointHeight / 2)), circlePaint);
}
/**
* 绘制自旋转位图
*
* @param canvas
* @param paint
* @param rotation 旋转度数
* @param posX 在canvas的位置坐标
* @param posY
*/
private void drawRotateBitmap(Canvas canvas, Paint paint,
float rotation, float posX, float posY) {
Bitmap bitmap = ((BitmapDrawable) dotDrawableEnd).getBitmap();
Matrix matrix = new Matrix();
float offsetX = bitmap.getWidth() / 2;
float offsetY = bitmap.getHeight() / 4;
matrix.postTranslate(-offsetX, -offsetY);
matrix.postRotate(rotation);
matrix.postTranslate(posX + offsetX, posY + offsetY);
canvas.drawBitmap(bitmap, matrix, paint);
}
private void moved(float x, float y, boolean up, MotionEvent event, boolean moveFirst) {
if (isOnArc(event) && !up) {
/*
* 根据三角函数整切定理计算得到X.Y的坐标
*/
if (moveFirst) {
float degrees = (float) ((float) ((Math.toDegrees(Math.atan2(
x - xOri, yOri - y)) + 360.0)) % 360.0);
if (degrees < 0) {
degrees += 2 * Math.PI;
}
if (endProcess < degrees) {
return;
}
if (startProcess > 0 && startProcess < 0.1) {
startProcess = 0f;
} else {
startProcess = degrees;
}
Log.e("234", "startProcess===" + startProcess);
Log.e("234", "endProcess===" + endProcess);
} else {
float degrees = (float) ((float) ((Math.toDegrees(Math.atan2(
x - xOri, yOri - y)) + 360.0)) % 360.0);
if (degrees < 0) {
degrees += 2 * Math.PI;
}
if (degrees < startProcess) {
return;
}
if (endProcess > 359.8 && endProcess < 360f) {//解决不能裁剪100%问题
endProcess = 360f;
} else {
endProcess = degrees;
}
Log.e("234", "startProcess111===" + startProcess);
Log.e("234", "endProcess==111=" + endProcess);
}
if (onProcessChangeLister != null) {
onProcessChangeLister.changeEnd(getEndproces());
onProcessChangeLister.changeStart(getStartproces());
}
invalidate();
} else {
// invalidate();
}
}
/**
* 获取进度条百分比
*/
public float getStartproces() {
return (startProcess / 360);
}
/**
* 获取进度条百分比
*/
public float getEndproces() {
return (endProcess / 360);
}
private OnProcessChangeLister onProcessChangeLister;
public interface OnProcessChangeLister {
void changeStart(float process);
void changeEnd(float preocess);
}
public void setOnProcessChangeLister(OnProcessChangeLister onProcessChangeLister) {
this.onProcessChangeLister = onProcessChangeLister;
}
public void setAngleEnd(long angle) {
this.angle = angle;
}
public int getMaxProgress() {
return maxProgress;
}
public void setProgressPercent(int progressPercent) {
this.progressPercent = progressPercent;
}
}
代码通俗易通,时间问题我没有去陪xml的值,直接写死的颜色。有需要的可以自己加顺便锻炼下写自定义view的能力。
有问题加下面的图问。