当前位置: 首页 > news >正文

Android音视频剪辑器自定义View实战!

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; //播控实际右边界//滑块bitmapprivate 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;//宽度左右paddingprivate 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 提供定义attrmWidthPadding = 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);}@Overrideprotected 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*/@Overridepublic 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;}@Overridepublic 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 TouchEventif (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 TouchEventif (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 TouchEventif (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 TouchEventif (getParent() != null) {getParent().requestDisallowInterceptTouchEvent(true);}break;}return true;}private boolean isTouchPlayControl(float eventX) {if (eventX > playControlLeft && eventX < playControlRight) {return true;}return false;}@Overrideprotected 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);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// draw seek bar background linemPaint.setStyle(Paint.Style.FILL);drawPlayControl(canvas);canvas.drawBitmap(mProgressBarBg, null, mProgressBarRect, mPaint);// draw seek bar active range linemProgressBarSelRect.left = percentToScreen(mPercentSelectedMinValue);mProgressBarSelRect.right = percentToScreen(mPercentSelectedMaxValue);//canvas.drawBitmap(mProgressBarSelBg, mWidthPadding, 0.5f * (getHeight() - mProgressBarHeight), mPaint);//        canvas.drawBitmap(mProgressBarSelBg, null, mProgressBarSelRect, mPaint); //原中部选中进度// draw minimum thumbdrawThumb(percentToScreen(mPercentSelectedMinValue), Thumb.MIN.equals(mPressedThumb), canvas, false);// draw maximum thumbdrawThumb(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) {//topfloat borderLeft = mProgressBarSelRect.left;float borderRight = mProgressBarSelRect.right;canvas.drawRect(borderLeft - 1, mProgressBarRect.top, borderRight + 1, mProgressBarRect.top + 10, borderPaint);//bottomcanvas.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);}//启动播放线程检查 ptspublic void startMove() {new Thread(new Runnable() {@Overridepublic 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;@Overrideprotected Parcelable onSaveInstanceState() {Bundle bundle = new Bundle();bundle.putParcelable("SUPER", super.onSaveInstanceState());bundle.putDouble("MIN", mPercentSelectedMinValue);bundle.putDouble("MAX", mPercentSelectedMaxValue);return bundle;}@Overrideprotected 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;}
}

image.png

 Android音视频剪辑器自定义View实战! - 掘金话不多说,先上一个代码完成效果。 动图好像录成横屏的了,也没找到调整反转 GIF 的位置,下面再补一张设计稿静态图吧 最近这几年音视频应用越来越广泛,随之而来的音视频相关的需求也越来越多,音视频的剪辑https://juejin.cn/post/7236635197071802424?utm_source=gold_browser_extension#heading-10

相关文章:

Android音视频剪辑器自定义View实战!

Android音视频剪辑器自定义View实战&#xff01; - 掘金 /*** Created by zhouxuming on 2023/3/30** descr 音视频剪辑器*/ public class AudioViewEditor extends View {//进度文本显示格式-数字格式public static final int HINT_FORMAT_NUMBER 0;//进度文本显示格式-时间…...

stm32_ADC电源、通道、工作模式

0、ADC功能框图 1、ADC的电源 1.1、工作电源 VSSAVSS&#xff0c;VDDAVDD&#xff0c;简单来说&#xff0c;通常stm32是3.3V&#xff0c;ADC的工作电源也是3.3V&#xff1b; 1.2、参考电压 VREF和VREF-并不一定引出&#xff0c;取决于封装&#xff0c;如果没有引出则VREF连接到…...

Vue编程式路由导航

目录 一、使用 一、使用 不使用<router-link>标签&#xff0c;利用$router中的api实现跳转&#xff0c;更灵活 <template><div><ul><li v-for"m in messageList" :key"m.id"><!-- 跳转路由并携带params参数&#xff0c…...

LVS-DR模式

目录 1、概述 2、LVS-DR模式的工作原理&#xff1a; 3、在LVS-DR模式下&#xff0c;数据包的流向分析如下&#xff1a; 4、LVS-DR是一种用于构建高可用性负载均衡集群的技术模式。LVS-DR模式具有以下特点&#xff1a; 5、LVS-DR中的ARP问题 6、配置LVS-DR需要以下几个关键…...

详细介绍生成对抗网络 (GAN) 的原理和基于Pytorch源码的实现

介绍 GAN 是一种使用 CNN(卷积神经网络)等深度学习方法进行生成建模的方法。生成建模是一种无监督学习方法,涉及自动发现和学习输入数据中的模式,以便该模型可用于从原始数据集中生成新示例。 GAN 是一种通过将问题构建为具有两个子模型的监督学习问题来训练生成模型的方…...

高性能数据处理选型

1、Redis(高性能) 2、Elasticsearch/HBase( 大数据 ) 3、MongoDB(海量数据)...

【深入理解C语言】-- 关键字2

&#x1f407; &#x1f525;博客主页&#xff1a; 云曦 &#x1f4cb;系列专栏&#xff1a;深入理解C语言 &#x1f4a8;吾生也有涯&#xff0c;而知也无涯 &#x1f49b; 感谢大家&#x1f44d;点赞 &#x1f60b;关注&#x1f4dd;评论 文章目录 前言一、关键字 - static&…...

Java进阶(3)——手动实现ArrayList 源码的初步理解分析 数组插入数据和删除数据的问题

目录 引出手动实现ArrayList定义接口MyList<T>写ArrayList的实现类增加元素删除元素 写测试类进行测试数组插入数据? 总结 引出 1.ArrayList的结构分析&#xff0c;可迭代接口&#xff0c;是List的实现&#xff1b; 2.数组增加元素和删除元素的分析&#xff0c;何时扩容…...

若依前端npm run dev启动时报错

本文主要解决问题:若依前端npm run dev启动时报错,解决办法。 目录 1、第1种解决方案(亲测有效) 2、第2种解决方案(亲测有效) Error: error:0308010C:digital envelope routines::unsupportedat new Hash (node:internal/crypto/hash:67:19)at Object.createHash (node…...

实战项目:基于主从Reactor模型实现高并发服务器

项目完整代码仿mudou库one thread one loop式并发服务器实现: 仿muduo库One Thread One Loop式主从Reactor模型实现⾼并发服务器&#xff1a;通过模拟实现的⾼并发服务器组件&#xff0c;可以简洁快速的完成⼀个⾼性能的服务器搭建。并且&#xff0c;通过组件内提供的不同应⽤层…...

iTOP-RK3568开发板ubuntu环境下安装Eclipse

eclipse 是使用 Java 语言开发的&#xff0c;一个 Java 应用程序&#xff0c;这意味着 eclipse 只能运行在 Java虚拟机上。倘若没有安装 JDK&#xff08;Java Development Kit&#xff09;&#xff0c;即使在 ubuntu 上安装了 eclipse&#xff0c;也不能运行&#xff0c;所以要…...

大气热力学

大气稳定度 大气稳定度又称为大气层结稳定度(贺德馨,2006)。大气层结指的是大气温度和湿度在垂直方向上的分布&#xff0c;对大气中污染物的扩散起着重要的作用。在静止大气中&#xff0c;假定气团受到垂直方向的扰动后&#xff0c;有一个向上的微小位移&#xff0c;如果大气层…...

【RabbitMQ】消息队列-RabbitMQ篇章

文章目录 1、RabbitMQ是什么2、Dokcer安装RabbitMQ2.1安装Dokcer2.2安装rabbitmq 3、RabbitMQ入门案例 - Simple 简单模式4、RabbitMQ的核心组成部分4.1 RabbitMQ整体架构4.2RabbitMQ的运行流程 5、RabbitMQ的模式5.1 发布订阅模式--fanout 1、RabbitMQ是什么 RabbitMQ是一个开…...

W5100S-EVB-PICO 做UDP Server进行数据回环测试(七)

前言 前面我们用W5100S-EVB-PICO 开发板在TCP Client和TCP Server模式下&#xff0c;分别进行数据回环测试&#xff0c;本章我们将用开发板在UDP Server模式下进行数据回环测试。 UDP是什么&#xff1f;什么是UDP Server&#xff1f;能干什么&#xff1f; UDP (User Dataqram …...

Redis如何处理内存溢出的情况?

当Redis的内存使用达到上限时&#xff0c;会出现内存溢出的情况。Redis提供了几种处理内存溢出的机制&#xff1a; 内存淘汰策略&#xff1a;Redis提供了多种内存淘汰策略&#xff0c;用于在内存不足时选择要移除的键。常见的淘汰策略包括&#xff1a; LRU&#xff08;Least Re…...

高效数据传输:轻松上手将Kafka实时数据接入CnosDB

本篇我们将主要介绍如何在 Ubuntu 22.04.2 LTS 环境下&#xff0c;实现一个KafkaTelegrafCnosDB 同步实时获取流数据并存储的方案。在本次操作中&#xff0c;CnosDB 版本是2.3.0&#xff0c;Kafka 版本是2.5.1&#xff0c;Telegraf 版本是1.27.1 随着越来越多的应用程序架构转…...

【探索Linux】—— 强大的命令行工具 P.3(Linux开发工具 vim)

阅读导航 前言vim简介概念特点 vim的相关指令vim命令模式(Normal mode)相关指令插入模式(Insert mode)相关指令末行模式(last line mode)相关指令 简单vim配置&#xff08;附配置链接&#xff09;温馨提示 前言 前面我们讲了C语言的基础知识&#xff0c;也了解了一些数据结构&…...

AgentBench::AI智能体发展的潜在问题一

从历史上看,几乎每一种新技术的广泛应用都会在带来新机遇的同时引发很多新问题,AI智能体也不例外。从目前的发展看,AI智能体的发展可能带来的新问题可能包括如下方面: 第一是它可能带来涉及个人数据、隐私,以及知识产权的法律纠纷的大幅增长。要产生一个优秀的AI智能体,除…...

【2023年11月第四版教材】《第5章-信息系统工程之软件工程(第二部分)》

《第5章-信息系统工程之软件工程&#xff08;第二部分&#xff09;》 1.3 软件设计1.4 软件实现&#xff3b;补充第三版教材内容&#xff3d; 1.5 部署交付 1.3 软件设计 1、结构化设计SD是一种面向数据流的方法&#xff0c;它以SRS和SA阶段所产生的DFD和数据字 典等文档为基础…...

OpenCV(二)——图像基本处理(二)

目录 2.图像的几何变换 2.1 图像平移 2.2 图像缩放 2.3 图像旋转 2.4 仿射变换 2.5 透视变换...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

聊一聊接口测试的意义有哪些?

目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开&#xff0c;首…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

为什么要创建 Vue 实例

核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

论文阅读:Matting by Generation

今天介绍一篇关于 matting 抠图的文章&#xff0c;抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法&#xff0c;已经有很多的工作和这个任务相关。这两年 diffusion 模型很火&#xff0c;大家又开始用 diffusion 模型做各种 CV 任务了&am…...