Android音视频剪辑器自定义View实战! - 掘金
/**
* Created by zhouxuming on 2023/3/30
*
* @descr 音视频剪辑器
*/
public class AudioViewEditor extends View {
//进度文本显示格式-数字格式
public static final int HINT_FORMAT_NUMBER = 0;
//进度文本显示格式-时间格式
public static final int HINT_FORMAT_TIME = 1;
private final Paint mPaint = new Paint();
//空间最小宽度
private final int MIN_WIDTH = 200;
private final float playControlLeft = 10; //播控实际左边界
private final float playControlRight = 80; //播控实际右边界
//滑块bitmap
private Bitmap mThumbImage;
//progress bar 选中背景
// private Bitmap mProgressBarSelBg;
private Bitmap mMaxThumbImage;
private Bitmap mMinThumbImage;
//progress bar 背景
private Bitmap mProgressBarBg;
private float mThumbWidth;
private float mThumbHalfWidth; //触摸响应宽度的一半
private float mThumbHalfHeight;
//seekbar 进度条高度
private float mProgressBarHeight;
//宽度左右padding
private float mWidthPadding;
//最小值(绝对)
private float mAbsoluteMinValue;
//最大值(绝对)
private float mAbsoluteMaxValue;
//已选标准(占滑动条百分比)最小值
private double mPercentSelectedMinValue = 0d;
//已选标准(占滑动条百分比)最大值
private double mPercentSelectedMaxValue = 1d;
//当前事件处理的thumb滑块
private Thumb mPressedThumb = null;
//滑块事件
private ThumbListener mThumbListener;
private RectF mProgressBarRect;
private RectF mProgressBarSelRect;
//是否可以滑动
private boolean mIsEnable = true;
//最大值和最小值之间要求的最小范围绝对值
private float mBetweenAbsoluteValue;
//文字格式
private int mProgressTextFormat;
//文本高度
private int mWordHeight;
//文本字体大小
private float mWordSize;
private float mStartMinPercent;
private float mStartMaxPercent;
private boolean fixedMode; //固定模式
private Paint cursorPaint;
private Paint borderPaint;
//播控按钮部分逻辑
private Paint playControlPaint;
private boolean isPlay = true; //播控状态
private Bitmap playResumeBitmap;
private Bitmap playPauseBitmap;
private PlayerControlListener playerControlListener;
private float cur;// 光标坐标
private float pre;// 100 份每一份的偏移量
private float min;//起始坐标
private float max;//最大坐标
private boolean isFirst = true;
public AudioViewEditor(Context context) {
super(context);
}
public AudioViewEditor(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.AudioViewEditor, 0, 0);
mAbsoluteMinValue = a.getFloat(R.styleable.AudioViewEditor_absoluteMin, (float) 0.0);
mAbsoluteMaxValue = a.getFloat(R.styleable.AudioViewEditor_absolutemMax, (float) 100.0);
mStartMinPercent = a.getFloat(R.styleable.AudioViewEditor_startMinPercent, 0);
mStartMaxPercent = a.getFloat(R.styleable.AudioViewEditor_startMaxPercent, 1);
mThumbImage = BitmapFactory.decodeResource(getResources(), a.getResourceId(R.styleable.AudioViewEditor_thumbImage, R.drawable.drag_left_bar));
mMaxThumbImage = BitmapFactory.decodeResource(getResources(), R.drawable.drag_right_bar);
mProgressBarBg = BitmapFactory.decodeResource(getResources(), a.getResourceId(R.styleable.AudioViewEditor_progressBarBg, R.drawable.seekbar_bg));
playPauseBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.play_control_pause);
playResumeBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.play_control_resume);
// mProgressBarSelBg = BitmapFactory.decodeResource(getResources(), a.getResourceId(R.styleable.CustomRangeSeekBar_progressBarSelBg, R.mipmap.seekbar_sel_bg));
mBetweenAbsoluteValue = a.getFloat(R.styleable.AudioViewEditor_betweenAbsoluteValue, 0);
mProgressTextFormat = a.getInt(R.styleable.AudioViewEditor_progressTextFormat, HINT_FORMAT_NUMBER);
mWordSize = a.getDimension(R.styleable.AudioViewEditor_progressTextSize, dp2px(context, 16));
mPaint.setTextSize(mWordSize);
mThumbWidth = mThumbImage.getWidth();
mThumbHalfWidth = 0.5f * mThumbWidth;
mThumbHalfHeight = 0.5f * mThumbImage.getHeight();
// mProgressBarHeight = 0.3f * mThumbHalfHeight;
mProgressBarHeight = mThumbImage.getHeight();
//TOOD 提供定义attr
mWidthPadding = mThumbHalfHeight;
mWidthPadding += playControlRight;//为了加左右侧播控按钮, 特地添加出来的空间
Paint.FontMetrics metrics = mPaint.getFontMetrics();
mWordHeight = (int) (metrics.descent - metrics.ascent);
/*光标*/
cursorPaint = new Paint();
cursorPaint.setAntiAlias(true);
cursorPaint.setColor(Color.WHITE);
borderPaint = new Paint();
borderPaint.setAntiAlias(true);
borderPaint.setColor(Color.parseColor("#DBAE6A"));
playControlPaint = new Paint();
playControlPaint.setAntiAlias(true);
playControlPaint.setColor(Color.parseColor("#1E1F21"));
restorePercentSelectedMinValue();
restorePercentSelectedMaxValue();
a.recycle();
}
/**
* 格式化毫秒->00:00
*/
private static String formatSecondTime(int millisecond) {
if (millisecond == 0) {
return "00:00";
}
int second = millisecond / 1000;
int m = second / 60;
int s = second % 60;
if (m >= 60) {
int hour = m / 60;
int minute = m % 60;
return hour + ":" + (minute > 9 ? minute : "0" + minute) + ":" + (s > 9 ? s : "0" + s);
} else {
return (m > 9 ? m : "0" + m) + ":" + (s > 9 ? s : "0" + s);
}
}
/**
* 将dip或dp值转换为px值,保证尺寸大小不变
*
* @param dipValue (DisplayMetrics类中属性density)
* @return
*/
public static int dp2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
/**
* 还原min滑块到初始值
*/
public void restorePercentSelectedMinValue() {
setPercentSelectedMinValue(mStartMinPercent);
}
/**
* 还原max滑块到初始值
*/
public void restorePercentSelectedMaxValue() {
setPercentSelectedMaxValue(mStartMaxPercent);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mProgressBarRect = new RectF(mWidthPadding, mWordHeight + 0.5f * (h - mWordHeight - mProgressBarHeight),
w - mWidthPadding, mWordHeight + 0.5f * (h - mWordHeight + mProgressBarHeight));
mProgressBarSelRect = new RectF(mProgressBarRect);
}
/**
* 设置seekbar 是否接收事件
*
* @param enabled
*/
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
this.mIsEnable = enabled;
}
/**
* 返回被选择的最小值(绝对值)
*
* @return The currently selected min value.
*/
public float getSelectedAbsoluteMinValue() {
return percentToAbsoluteValue(mPercentSelectedMinValue);
}
/**
* 设置被选择的最小值(绝对值)
*
* @param value 最小值的绝对值
* return 如果最小值与最大值的最小间距达到阈值返回false,正常返回true
*/
public boolean setSelectedAbsoluteMinValue(float value) {
boolean status = true;
if (0 == (mAbsoluteMaxValue - mAbsoluteMinValue)) {
setPercentSelectedMinValue(0d);
} else {
float maxValue = percentToAbsoluteValue(mPercentSelectedMaxValue);
if (mBetweenAbsoluteValue > 0 && maxValue - value <= mBetweenAbsoluteValue) {
value = new Float(maxValue - mBetweenAbsoluteValue);
status = false;
}
if (maxValue - value <= 0) {
status = false;
value = maxValue;
}
setPercentSelectedMinValue(absoluteValueToPercent(value));
}
return status;
}
public float getAbsoluteMaxValue() {
return mAbsoluteMaxValue;
}
public void setAbsoluteMaxValue(double maxvalue) {
this.mAbsoluteMaxValue = new Float(maxvalue);
}
/**
* 返回被选择的最大值(绝对值).
*/
public float getSelectedAbsoluteMaxValue() {
return percentToAbsoluteValue(mPercentSelectedMaxValue);
}
/**
* 设置被选择的最大值(绝对值)
*
* @param value
*/
public boolean setSelectedAbsoluteMaxValue(float value) {
boolean status = true;
if (0 == (mAbsoluteMaxValue - mAbsoluteMinValue)) {
setPercentSelectedMaxValue(1d);
} else {
float minValue = percentToAbsoluteValue(mPercentSelectedMinValue);
if (mBetweenAbsoluteValue > 0 && value - minValue <= mBetweenAbsoluteValue) {
value = new Float(minValue + mBetweenAbsoluteValue);
status = false;
}
if (value - minValue <= 0) {
status = false;
value = minValue;
}
setPercentSelectedMaxValue(absoluteValueToPercent(value));
}
return status;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!mIsEnable)
return true;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (isTouchPlayControl(event.getX())) {
isPlay = !isPlay;
playerControlListener.onPlayerControl(isPlay);
invalidate();
return true;
}
if (mPressedThumb == null && isInCursorRange(event.getX(), cur)) {
// if (mThumbListener != null){
// mThumbListener.onCursor(cur);
// }
} else {
mPressedThumb = evalPressedThumb(event.getX());
if (Thumb.MIN.equals(mPressedThumb)) {
if (mThumbListener != null)
mThumbListener.onClickMinThumb(getSelectedAbsoluteMaxValue(), getSelectedAbsoluteMinValue());
}
if (Thumb.MAX.equals(mPressedThumb)) {
if (mThumbListener != null)
mThumbListener.onClickMaxThumb();
}
}
invalidate();
//Intercept parent TouchEvent
if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
case MotionEvent.ACTION_MOVE:
if (mPressedThumb == null && isInCursorRange(event.getX(), cur)) {
isMoving = true;
float eventX = event.getX();
if (eventX >= percentToScreen(mPercentSelectedMaxValue)) {
eventX = percentToScreen(mPercentSelectedMaxValue);
} else if (eventX <= percentToScreen(mPercentSelectedMinValue)) {
eventX = percentToScreen(mPercentSelectedMinValue);
}
cur = eventX;
if (mThumbListener != null) {
mThumbListener.onCursorMove(percentToAbsoluteValue(screenToPercent(cur)));
}
invalidate();
} else if (mPressedThumb != null) {
float eventX = event.getX();
float maxValue = percentToAbsoluteValue(mPercentSelectedMaxValue);
float minValue = percentToAbsoluteValue(mPercentSelectedMinValue);
float eventValue = percentToAbsoluteValue(screenToPercent(eventX));
if (Thumb.MIN.equals(mPressedThumb)) {
minValue = eventValue;
if (mBetweenAbsoluteValue > 0 && maxValue - minValue <= mBetweenAbsoluteValue) {
minValue = new Float((maxValue - mBetweenAbsoluteValue));
}
// setPercentSelectedMinValue(screenToPercent(event.getX()));
if (isFixedMode()) {
mPercentSelectedMaxValue = Math.max(0d, Math.min(1d, Math.max(absoluteValueToPercent(eventValue + (maxValue - minValue)), mPercentSelectedMinValue)));
}
if (cur <= percentToScreen(mPercentSelectedMinValue)) {//防止光标静态越界
cur = percentToScreen(mPercentSelectedMinValue);
}
setPercentSelectedMinValue(absoluteValueToPercent(minValue));
if (mThumbListener != null)
mThumbListener.onMinMove(getSelectedAbsoluteMaxValue(), getSelectedAbsoluteMinValue());
} else if (Thumb.MAX.equals(mPressedThumb)) {
maxValue = eventValue;
if (mBetweenAbsoluteValue > 0 && maxValue - minValue <= mBetweenAbsoluteValue) {
maxValue = new Float(minValue + mBetweenAbsoluteValue);
}
// setPercentSelectedMaxValue(screenToPercent(event.getX()));
if (isFixedMode()) {
mPercentSelectedMinValue = Math.max(0d, Math.min(1d, Math.min(absoluteValueToPercent(eventValue - (maxValue - minValue)), mPercentSelectedMaxValue)));
}
if (cur >= percentToScreen(mPercentSelectedMaxValue)) {//防止光标静态越界
cur = percentToScreen(mPercentSelectedMaxValue);
}
setPercentSelectedMaxValue(absoluteValueToPercent(maxValue));
if (mThumbListener != null)
mThumbListener.onMaxMove(getSelectedAbsoluteMaxValue(), getSelectedAbsoluteMinValue());
}
}
//Intercept parent TouchEvent
if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
case MotionEvent.ACTION_UP:
if (isMoving) {
if (mThumbListener != null) {
mThumbListener.onCursorUp(percentToAbsoluteValue(screenToPercent(cur)));
}
isMoving = false;
}
if (Thumb.MIN.equals(mPressedThumb)) {
if (mThumbListener != null)
mThumbListener.onUpMinThumb(getSelectedAbsoluteMaxValue(), getSelectedAbsoluteMinValue());
}
if (Thumb.MAX.equals(mPressedThumb)) {
if (mThumbListener != null)
mThumbListener.onUpMaxThumb(getSelectedAbsoluteMaxValue(), getSelectedAbsoluteMinValue());
}
//Intercept parent TouchEvent
if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
case MotionEvent.ACTION_CANCEL:
if (Thumb.MIN.equals(mPressedThumb)) {
if (mThumbListener != null)
mThumbListener.onUpMinThumb(getSelectedAbsoluteMaxValue(), getSelectedAbsoluteMinValue());
}
if (Thumb.MAX.equals(mPressedThumb)) {
if (mThumbListener != null)
mThumbListener.onUpMaxThumb(getSelectedAbsoluteMaxValue(), getSelectedAbsoluteMinValue());
}
mPressedThumb = null;
//Intercept parent TouchEvent
if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
return true;
}
private boolean isTouchPlayControl(float eventX) {
if (eventX > playControlLeft && eventX < playControlRight) {
return true;
}
return false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MIN_WIDTH;
if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
width = MeasureSpec.getSize(widthMeasureSpec);
}
int height = mThumbImage.getHeight() + mWordHeight * 2;
if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw seek bar background line
mPaint.setStyle(Paint.Style.FILL);
drawPlayControl(canvas);
canvas.drawBitmap(mProgressBarBg, null, mProgressBarRect, mPaint);
// draw seek bar active range line
mProgressBarSelRect.left = percentToScreen(mPercentSelectedMinValue);
mProgressBarSelRect.right = percentToScreen(mPercentSelectedMaxValue);
//canvas.drawBitmap(mProgressBarSelBg, mWidthPadding, 0.5f * (getHeight() - mProgressBarHeight), mPaint);
// canvas.drawBitmap(mProgressBarSelBg, null, mProgressBarSelRect, mPaint); //原中部选中进度
// draw minimum thumb
drawThumb(percentToScreen(mPercentSelectedMinValue), Thumb.MIN.equals(mPressedThumb), canvas, false);
// draw maximum thumb
drawThumb(percentToScreen(mPercentSelectedMaxValue), Thumb.MAX.equals(mPressedThumb), canvas, true);
mPaint.setColor(Color.rgb(255, 165, 0));
mPaint.setAntiAlias(true);
// mPaint.setTextSize(DensityUtils.dp2px(getContext(), 16));
drawThumbMinText(percentToScreen(mPercentSelectedMinValue), getSelectedAbsoluteMinValue(), canvas);
drawThumbMaxText(percentToScreen(mPercentSelectedMaxValue), getSelectedAbsoluteMaxValue(), canvas);
drawBorder(canvas);
drawCursor(canvas);
}
private void drawPlayControl(Canvas canvas) {
canvas.drawRoundRect(playControlLeft, mProgressBarRect.top, playControlRight + mThumbWidth + mThumbHalfWidth, mProgressBarRect.bottom, 5, 5, playControlPaint);
Bitmap targetBitmap = isPlay ? playPauseBitmap : playResumeBitmap;
//x轴距离未计算准确 y轴正确
canvas.drawBitmap(targetBitmap, (playControlLeft + (playControlRight - playControlLeft) / 2) - mThumbHalfWidth + (targetBitmap.getWidth() >> 1), mProgressBarRect.top + (mProgressBarRect.bottom - mProgressBarRect.top) / 2 - (targetBitmap.getHeight() >> 1), playControlPaint);
}
private void drawBorder(Canvas canvas) {
//top
float borderLeft = mProgressBarSelRect.left;
float borderRight = mProgressBarSelRect.right;
canvas.drawRect(borderLeft - 1, mProgressBarRect.top, borderRight + 1, mProgressBarRect.top + 10, borderPaint);
//bottom
canvas.drawRect(borderLeft - 1, mProgressBarRect.bottom, borderRight + 1, mProgressBarRect.bottom - 10, borderPaint);
}
private void drawCursor(Canvas canvas) {
min = percentToScreen(mPercentSelectedMinValue);//开始坐标
max = percentToScreen(mPercentSelectedMaxValue);//终点坐标
pre = (getWidth() - 2 * mWidthPadding) / 1000; //每一份的坐标
if (isFirst) {
cur = min;
isFirst = false;
}
canvas.drawRect(cur - 2, mProgressBarRect.top + 5, cur + 2, mProgressBarRect.bottom - 5, cursorPaint);
}
//启动播放线程检查 pts
public void startMove() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
if (isPlay) {
long pts = playerCallback != null ? playerCallback.getCurrentPosition() : 0;
updatePTS(pts);
}
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
/**
* 根据播放器 pts 控制游标进度
*
* @param pts
*/
public void updatePTS(float pts) {
if (isMoving) {
return;
}
if (pts > 0) {
double v = absoluteValueToPercent(pts);
cur = percentToScreen(v);
if (cur >= max || cur < min) {
cur = min;
}
invalidate();
}
}
public boolean isPlay() {
return isPlay;
}
public void setPlay(boolean play) {
isPlay = play;
}
private boolean isMoving = false;
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable("SUPER", super.onSaveInstanceState());
bundle.putDouble("MIN", mPercentSelectedMinValue);
bundle.putDouble("MAX", mPercentSelectedMaxValue);
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable parcel) {
Bundle bundle = (Bundle) parcel;
super.onRestoreInstanceState(bundle.getParcelable("SUPER"));
mPercentSelectedMinValue = bundle.getDouble("MIN");
mPercentSelectedMaxValue = bundle.getDouble("MAX");
}
/**
* Draws the "normal" resp. "pressed" thumb image on specified x-coordinate.
*
* @param screenCoord The x-coordinate in screen space where to draw the image.
* @param pressed Is the thumb currently in "pressed" state?
* @param canvas The canvas to draw upon.
*/
private void drawThumb(float screenCoord, boolean pressed, Canvas canvas, boolean isMax) {
//基准点 bar 居中位置
// canvas.drawBitmap(isMax ? mMaxThumbImage : mThumbImage, screenCoord - mThumbHalfWidth, (mWordHeight + 0.5f * (getHeight() - mWordHeight) - mThumbHalfHeight), mPaint);
//基准点顶两边位置
canvas.drawBitmap(isMax ? mMaxThumbImage : mThumbImage, isMax ? screenCoord : screenCoord - mThumbHalfWidth * 2, (mWordHeight + 0.5f * (getHeight() - mWordHeight) - mThumbHalfHeight), mPaint);
}
/**
* 画min滑块值text
*
* @param screenCoord
* @param value
* @param canvas
*/
private void drawThumbMinText(float screenCoord, Number value, Canvas canvas) {
String progress = getProgressStr(value.intValue());
float progressWidth = mPaint.measureText(progress);
canvas.drawText(progress, (screenCoord - progressWidth / 2) - mThumbHalfWidth, mWordSize, mPaint);
}
/**
* 画max滑块值text
*
* @param screenCoord
* @param value
* @param canvas
*/
private void drawThumbMaxText(float screenCoord, Number value, Canvas canvas) {
String progress = getProgressStr(value.intValue());
float progressWidth = mPaint.measureText(progress);
canvas.drawText(progress, (screenCoord - progressWidth / 2) + mThumbHalfWidth, mWordSize
, mPaint);
}
/**
* 根据touchX, 判断是哪一个thumb(Min or Max)
*
* @param touchX 触摸的x在屏幕中坐标(相对于容器)
*/
private Thumb evalPressedThumb(float touchX) {
Thumb result = null;
boolean minThumbPressed = isInThumbRange(touchX, mPercentSelectedMinValue, false);
boolean maxThumbPressed = isInThumbRange(touchX, mPercentSelectedMaxValue, true);
if (minThumbPressed && maxThumbPressed) {
// if both thumbs are pressed (they lie on top of each other), choose the one with more room to drag. this avoids "stalling" the thumbs in a corner, not being able to drag them apart anymore.
result = (touchX / getWidth() > 0.5f) ? Thumb.MIN : Thumb.MAX;
} else if (minThumbPressed) {
result = Thumb.MIN;
} else if (maxThumbPressed) {
result = Thumb.MAX;
}
return result;
}
/**
* 判断touchX是否在滑块点击范围内
*
* @param touchX 需要被检测的 屏幕中的x坐标(相对于容器)
* @param percentThumbValue 需要检测的滑块x坐标百分比值(滑块x坐标)
*/
private boolean isInThumbRange(float touchX, double percentThumbValue, boolean isMax) {
if (isMax) {
return Math.abs(touchX - mThumbHalfWidth - percentToScreen(percentThumbValue)) <= mThumbHalfWidth;
} else {
return Math.abs(touchX + mThumbHalfWidth - percentToScreen(percentThumbValue)) <= mThumbHalfWidth;
}
// return Math.abs(touchX - percentToScreen(percentThumbValue)) <= mThumbHalfWidth; //居中基准时
}
/**
* 判断用户是否触碰光标
*
* @param touchX 需要被检测的 屏幕中的x坐标(相对于容器)
* @param cursorX 光标x坐标(滑块x坐标)
* @return
*/
private boolean isInCursorRange(float touchX, float cursorX) {
return Math.abs(touchX - cursorX) <= mThumbHalfWidth;
}
/**
* 设置已选择最小值的百分比值
*/
public void setPercentSelectedMinValue(double value) {
mPercentSelectedMinValue = Math.max(0d, Math.min(1d, Math.min(value, mPercentSelectedMaxValue)));
invalidate();
}
/**
* 设置已选择最大值的百分比值
*/
public void setPercentSelectedMaxValue(double value) {
mPercentSelectedMaxValue = Math.max(0d, Math.min(1d, Math.max(value, mPercentSelectedMinValue)));
invalidate();
}
/**
* 进度值,从百分比到绝对值
*
* @return
*/
@SuppressWarnings("unchecked")
private float percentToAbsoluteValue(double normalized) {
return (float) (mAbsoluteMinValue + normalized * (mAbsoluteMaxValue - mAbsoluteMinValue));
}
/**
* 进度值,从绝对值到百分比
*/
private double absoluteValueToPercent(float value) {
if (0 == mAbsoluteMaxValue - mAbsoluteMinValue) {
// prevent division by zero, simply return 0.
return 0d;
}
return (value - mAbsoluteMinValue) / (mAbsoluteMaxValue - mAbsoluteMinValue);
}
/**
* 进度值,从百分比值转换到屏幕中坐标值
*/
private float percentToScreen(double percentValue) {
return (float) (mWidthPadding + percentValue * (getWidth() - 2 * mWidthPadding));
}
/**
* 进度值,转换屏幕像素值到百分比值
*/
private double screenToPercent(float screenCoord) {
int width = getWidth();
if (width <= 2 * mWidthPadding) {
// prevent division by zero, simply return 0.
return 0d;
} else {
double result = (screenCoord - mWidthPadding) / (width - 2 * mWidthPadding);
return Math.min(1d, Math.max(0d, result));
}
}
public void setThumbListener(ThumbListener mThumbListener) {
this.mThumbListener = mThumbListener;
}
private String getProgressStr(int progress) {
String progressStr;
if (mProgressTextFormat == HINT_FORMAT_TIME) {
progressStr = formatSecondTime(progress);
} else {
progressStr = String.valueOf(progress);
}
return progressStr;
}
public boolean isFixedMode() {
return fixedMode;
}
public void setFixedMode(boolean fixedMode) {
this.fixedMode = fixedMode;
}
/**
* 重置总时长-单位秒
*
* @param totalSecond
*/
public void resetTotalSecond(int totalSecond) {
if (totalSecond > 0 && totalSecond < 12000) {
mAbsoluteMaxValue = totalSecond * 1000;
mAbsoluteMinValue = 0.0f;
mProgressTextFormat = HINT_FORMAT_TIME;
invalidate();
}
}
/**
* 重置总时长-单位毫秒
*
* @param totalMillisecond
*/
public void resetTotalMillisecond(float totalMillisecond) {
if (totalMillisecond > 0 && totalMillisecond < 1200000) {
mAbsoluteMaxValue = totalMillisecond;
mAbsoluteMinValue = 0.0f;
mProgressTextFormat = HINT_FORMAT_TIME;
invalidate();
}
}
public void setPlayerControlListener(PlayerControlListener playerControlListener) {
this.playerControlListener = playerControlListener;
}
/**
* Thumb枚举, 最大或最小
*/
private enum Thumb {
MIN, MAX
}
public interface PlayerControlListener {
void onPlayerControl(boolean isPlay);
}
/**
* 游标以及滑块事件
*/
public interface ThumbListener {
void onClickMinThumb(Number max, Number min);
void onClickMaxThumb();
void onUpMinThumb(Number max, Number min);
void onUpMaxThumb(Number max, Number min);
void onMinMove(Number max, Number min);
void onMaxMove(Number max, Number min);
void onCursorMove(Number cur);
void onCursorUp(Number cur);
}
public interface IPlayerCallback {
long getCurrentPosition();
}
private IPlayerCallback playerCallback = null;
public void setPlayerCallback(IPlayerCallback playerCallback) {
this.playerCallback = playerCallback;
}
public void release() {
isPlay = false;
}
}
Android音视频剪辑器自定义View实战! - 掘金话不多说,先上一个代码完成效果。 动图好像录成横屏的了,也没找到调整反转 GIF 的位置,下面再补一张设计稿静态图吧 最近这几年音视频应用越来越广泛,随之而来的音视频相关的需求也越来越多,音视频的剪辑https://juejin.cn/post/7236635197071802424?utm_source=gold_browser_extension#heading-10