日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

Android 用于视频回放显示时间刻度的一个自定义View

發(fā)布時(shí)間:2023/12/20 Android 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android 用于视频回放显示时间刻度的一个自定义View 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

實(shí)現(xiàn)效果:

?

View的主要代碼

public class TimeRuleView extends View {private static final boolean LOG_ENABLE = BuildConfig.DEBUG;public static final int MAX_TIME_VALUE = 24 * 3600;private int bgColor;/*** 刻度顏色*/private int gradationColor;/*** 時(shí)間塊的高度*/private float partHeight;/*** 時(shí)間塊的顏色*/private int partColor;/*** 刻度寬度*/private float gradationWidth;/*** 秒、分、時(shí)刻度的長(zhǎng)度*/private float secondLen;private float minuteLen;private float hourLen;/*** 刻度數(shù)值顏色、大小、與時(shí)刻度的距離*/private int gradationTextColor;private float gradationTextSize;private float gradationTextGap;/*** 當(dāng)前時(shí)間,單位:s*/private @IntRange(from = 0, to = MAX_TIME_VALUE)int currentTime;/*** 指針顏色*/private int indicatorColor;/*** 指針上三角形的邊長(zhǎng)*/private float indicatorTriangleSideLen;/*** 指針的寬度*/private float indicatorWidth;/*** 最小單位對(duì)應(yīng)的單位秒數(shù)值,一共四級(jí): 10s、1min、5min、15min* 與 {@link #mPerTextCounts} 和 {@link #mPerCountScaleThresholds} 對(duì)應(yīng)的索引值* <p>* 可以組合優(yōu)化成數(shù)組*/private static int[] mUnitSeconds = {10, 10, 10, 10,60, 60,5 * 60, 5 * 60,15 * 60, 15 * 60, 15 * 60, 15 * 60, 15 * 60, 15 * 60};/*** 數(shù)值顯示間隔。一共13級(jí),第一級(jí)最大值,不包括*/@SuppressWarnings("all")private static int[] mPerTextCounts = {60, 60, 2 * 60, 4 * 60, // 10s/unit: 最大值, 1min, 2min, 4min5 * 60, 10 * 60, // 1min/unit: 5min, 10min20 * 60, 30 * 60, // 5min/unit: 20min, 30min3600, 2 * 3600, 3 * 3600, 4 * 3600, 5 * 3600, 6 * 3600 // 15min/unit};/*** 與 {@link #mPerTextCounts} 對(duì)應(yīng)的閾值,在此閾值與前一個(gè)閾值之間,則使用此閾值對(duì)應(yīng)的間隔數(shù)值* 如:1.5f 代表 4*60 對(duì)應(yīng)的閾值,如果 mScale >= 1.5f && mScale < 1.8f,則使用 4*60* <p>* 這些數(shù)值,都是估算出來(lái)的*/@SuppressWarnings("all")private float[] mPerCountScaleThresholds = {6f, 3.6f, 1.8f, 1.5f, // 10s/unit: 最大值, 1min, 2min, 4min0.8f, 0.4f, // 1min/unit: 5min, 10min0.25f, 0.125f, // 5min/unit: 20min, 30min0.07f, 0.04f, 0.03f, 0.025f, 0.02f, 0.015f // 15min/unit: 1h, 2h, 3h, 4h, 5h, 6h};/*** 默認(rèn)mScale為1*/private float mScale = 1;/*** 1s對(duì)應(yīng)的間隔,比較好估算*/private final float mOneSecondGap = dp2px(12) / 60f;/*** 當(dāng)前最小單位秒數(shù)值對(duì)應(yīng)的間隔*/private float mUnitGap = mOneSecondGap * 60;/*** 默認(rèn)索引值*/private int mPerTextCountIndex = 4;/*** 一格代表的秒數(shù)。默認(rèn)1min*/private int mUnitSecond = mUnitSeconds[mPerTextCountIndex];/*** 數(shù)值文字寬度的一半:時(shí)間格式為“00:00”,所以長(zhǎng)度固定*/private final float mTextHalfWidth;private final int SCROLL_SLOP;private final int MIN_VELOCITY;private final int MAX_VELOCITY;/*** 當(dāng)前時(shí)間與 00:00 的距離值*/private float mCurrentDistance;private Paint mPaint;private TextPaint mTextPaint;private Path mTrianglePath;private Scroller mScroller;private VelocityTracker mVelocityTracker;/*** 縮放手勢(shì)檢測(cè)器*/private ScaleGestureDetector mScaleGestureDetector;private int mWidth, mHeight;private int mHalfWidth;private int mInitialX;private int mLastX, mLastY;private boolean isMoving;private boolean isScaling;private List<TimePart> mTimePartList;private OnTimeChangedListener mListener;public interface OnTimeChangedListener {void onTimeChanged(int newTimeValue);}/*** 時(shí)間片段*/public static class TimePart {/*** 起始時(shí)間,單位:s,取值范圍∈[0, 86399]* 0 —— 00:00:00* 86399 —— 23:59:59*/public int startTime;/*** 結(jié)束時(shí)間,必須大于{@link #startTime}*/public int endTime;}public TimeRuleView(Context context) {this(context, null);}public TimeRuleView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public TimeRuleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initAttrs(context, attrs);init(context);initScaleGestureDetector(context);mTextHalfWidth = mTextPaint.measureText("00:00") * .5f;ViewConfiguration viewConfiguration = ViewConfiguration.get(context);SCROLL_SLOP = viewConfiguration.getScaledTouchSlop();MIN_VELOCITY = viewConfiguration.getScaledMinimumFlingVelocity();MAX_VELOCITY = viewConfiguration.getScaledMaximumFlingVelocity();calculateValues();}private void initAttrs(Context context, AttributeSet attrs) {TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TimeRuleView);bgColor = ta.getColor(R.styleable.TimeRuleView_zjun_bgColor, Color.parseColor("#EEEEEE"));gradationColor = ta.getColor(R.styleable.TimeRuleView_zjun_gradationColor, Color.GRAY);partHeight = ta.getDimension(R.styleable.TimeRuleView_trv_partHeight, dp2px(20));partColor = ta.getColor(R.styleable.TimeRuleView_trv_partColor, Color.parseColor("#F58D24"));gradationWidth = ta.getDimension(R.styleable.TimeRuleView_trv_gradationWidth, 1);secondLen = ta.getDimension(R.styleable.TimeRuleView_trv_secondLen, dp2px(3));minuteLen = ta.getDimension(R.styleable.TimeRuleView_trv_minuteLen, dp2px(5));hourLen = ta.getDimension(R.styleable.TimeRuleView_trv_hourLen, dp2px(10));gradationTextColor = ta.getColor(R.styleable.TimeRuleView_trv_gradationTextColor, Color.GRAY);gradationTextSize = ta.getDimension(R.styleable.TimeRuleView_trv_gradationTextSize, sp2px(12));gradationTextGap = ta.getDimension(R.styleable.TimeRuleView_trv_gradationTextGap, dp2px(2));currentTime = ta.getInt(R.styleable.TimeRuleView_trv_currentTime, 0);indicatorTriangleSideLen = ta.getDimension(R.styleable.TimeRuleView_trv_indicatorTriangleSideLen, dp2px(15));indicatorWidth = ta.getDimension(R.styleable.TimeRuleView_zjun_indicatorLineWidth, dp2px(1));indicatorColor = ta.getColor(R.styleable.TimeRuleView_zjun_indicatorLineColor, Color.RED);ta.recycle();}private void calculateValues() {mCurrentDistance = Float.parseFloat(currentTime+"")/Float.parseFloat(mUnitSecond+"") * mUnitGap;logD(mCurrentDistance+"");}private void init(Context context) {mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);mTextPaint.setTextSize(gradationTextSize);mTextPaint.setColor(gradationTextColor);mTrianglePath = new Path();mScroller = new Scroller(context);}private void initScaleGestureDetector(Context context) {mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.OnScaleGestureListener() {/*** 縮放被觸發(fā)(會(huì)調(diào)用0次或者多次),* 如果返回 true 則表示當(dāng)前縮放事件已經(jīng)被處理,檢測(cè)器會(huì)重新積累縮放因子* 返回 false 則會(huì)繼續(xù)積累縮放因子。*/@Overridepublic boolean onScale(ScaleGestureDetector detector) {final float scaleFactor = detector.getScaleFactor();logD("onScale...focusX=%f, focusY=%f, scaleFactor=%f",detector.getFocusX(), detector.getFocusY(), scaleFactor);final float maxScale = mPerCountScaleThresholds[0];final float minScale = mPerCountScaleThresholds[mPerCountScaleThresholds.length - 1];if (scaleFactor > 1 && mScale >= maxScale) {// 已經(jīng)放大到最大值return true;} else if (scaleFactor < 1 && mScale <= minScale) {// 已經(jīng)縮小到最小值return true;}mScale *= scaleFactor;mScale = Math.max(minScale, Math.min(maxScale, mScale));mPerTextCountIndex = findScaleIndex(mScale);mUnitSecond = mUnitSeconds[mPerTextCountIndex];mUnitGap = mScale * mOneSecondGap * mUnitSecond;logD("onScale: mScale=%f, mPerTextCountIndex=%d, mUnitSecond=%d, mUnitGap=%f",mScale, mPerTextCountIndex, mUnitSecond, mUnitGap);mCurrentDistance = (float) currentTime / mUnitSecond * mUnitGap;invalidate();return true;}@Overridepublic boolean onScaleBegin(ScaleGestureDetector detector) {logD("onScaleBegin...");isScaling = true;return true;}@Overridepublic void onScaleEnd(ScaleGestureDetector detector) {isScaling = false;logD("onScaleEnd...");}});// 調(diào)整最小跨度值。默認(rèn)值27mm(>=sw600dp的32mm),太大了,效果不好Class clazz = ScaleGestureDetector.class;int newMinSpan = ViewConfiguration.get(context).getScaledTouchSlop();try {@SuppressLint("SoonBlockedPrivateApi") Field mMinSpanField = clazz.getDeclaredField("mMinSpan");mMinSpanField.setAccessible(true);mMinSpanField.set(mScaleGestureDetector, newMinSpan);mMinSpanField.setAccessible(false);} catch (NoSuchFieldException | IllegalAccessException e) {e.printStackTrace();}}/*** 二分法查找縮放值對(duì)應(yīng)的索引值*/private int findScaleIndex(float scale) {final int size = mPerCountScaleThresholds.length;int min = 0;int max = size - 1;int mid = (min + max) >> 1;while (!(scale >= mPerCountScaleThresholds[mid] && scale < mPerCountScaleThresholds[mid - 1])) {if (scale >= mPerCountScaleThresholds[mid - 1]) {// 因?yàn)橹低^(qū),index往大取,所以不能為mid -1max = mid;} else {min = mid + 1;}mid = (min + max) >> 1;if (min >= max) {break;}if (mid == 0) {break;}}return mid;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {mWidth = MeasureSpec.getSize(widthMeasureSpec);mHeight = MeasureSpec.getSize(heightMeasureSpec);// 只處理wrap_content的高度,設(shè)置為80dpif (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {mHeight = dp2px(60);}mHalfWidth = mWidth >> 1;setMeasuredDimension(mWidth, mHeight);}@Overridepublic boolean onTouchEvent(MotionEvent event) {final int actionIndex = event.getActionIndex();int pointerId = event.getPointerId(actionIndex);final int actionMasked = event.getActionMasked();final int action = event.getAction();final int pointerCount = event.getPointerCount();logD("onTouchEvent: isScaling=%b, actionIndex=%d, pointerId=%d, actionMasked=%d, action=%d, pointerCount=%d",isScaling, actionIndex, pointerId, actionMasked, action, pointerCount);final int x = (int) event.getX();final int y = (int) event.getY();mScaleGestureDetector.onTouchEvent(event);if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(event);switch (actionMasked) {case MotionEvent.ACTION_DOWN:isMoving = false;mInitialX = x;if (!mScroller.isFinished()) {mScroller.forceFinished(true);}break;case MotionEvent.ACTION_POINTER_DOWN:// 只要第二手指按下,就禁止滑動(dòng)isScaling = true;isMoving = false;break;case MotionEvent.ACTION_MOVE:if (isScaling) {break;}int dx = x - mLastX;if (!isMoving) {final int dy = y - mLastY;if (Math.abs(x - mInitialX) <= SCROLL_SLOP || Math.abs(dx) <= Math.abs(dy)) {break;}isMoving = true;}mCurrentDistance -= dx;computeTime();break;case MotionEvent.ACTION_UP:if (isScaling || !isMoving) {break;}mVelocityTracker.computeCurrentVelocity(1000, MAX_VELOCITY);final int xVelocity = (int) mVelocityTracker.getXVelocity();if (Math.abs(xVelocity) >= MIN_VELOCITY) {// 慣性滑動(dòng)final int maxDistance = (int) (MAX_TIME_VALUE / mUnitGap * mUnitGap);mScroller.fling((int) mCurrentDistance, 0, -xVelocity, 0, 0, maxDistance, 0, 0);invalidate();}break;case MotionEvent.ACTION_POINTER_UP:// 兩個(gè)中的有一個(gè)手指被抬起,允許滑動(dòng)。同時(shí)把未抬起的手機(jī)當(dāng)前位置賦給初始XisScaling = false;int restIndex = actionIndex == 0 ? 1 : 0;mInitialX = (int) event.getX(restIndex);break;default:break;}mLastX = x;mLastY = y;return true;}private void computeTime() {// 不用轉(zhuǎn)float,肯定能整除float maxDistance = MAX_TIME_VALUE / mUnitSecond * mUnitGap;// 限定范圍mCurrentDistance = Math.min(maxDistance, Math.max(0, mCurrentDistance));currentTime = (int) (mCurrentDistance / mUnitGap * mUnitSecond);if (mListener != null) {mListener.onTimeChanged(currentTime);}invalidate();}@Overrideprotected void onDraw(Canvas canvas) {// 背景canvas.drawColor(bgColor);// 刻度drawRule(canvas);// 時(shí)間段drawTimeParts(canvas);// 當(dāng)前時(shí)間指針drawTimeIndicator(canvas);}@Overridepublic void computeScroll() {if (mScroller.computeScrollOffset()) {mCurrentDistance = mScroller.getCurrX();computeTime();}}/*** 繪制刻度*/private void drawRule(Canvas canvas) {// 移動(dòng)畫(huà)布坐標(biāo)系canvas.save();canvas.translate(0, partHeight);mPaint.setColor(gradationColor);mPaint.setStrokeWidth(gradationWidth);// 刻度int start = 0;float offset = mHalfWidth - mCurrentDistance;final int perTextCount = mPerTextCounts[mPerTextCountIndex];while (start <= MAX_TIME_VALUE) {// 刻度if (start % 3600 == 0) {// 時(shí)刻度canvas.drawLine(offset, 0, offset, hourLen, mPaint);} else if (start % 60 == 0) {// 分刻度canvas.drawLine(offset, 0, offset, minuteLen, mPaint);} else {// 秒刻度canvas.drawLine(offset, 0, offset, secondLen, mPaint);}// 時(shí)間數(shù)值if (start % perTextCount == 0) {String text = formatTimeHHmm(start);canvas.drawText(text, offset - mTextHalfWidth, hourLen + gradationTextGap + gradationTextSize + 10, mTextPaint);}start += mUnitSecond;offset += mUnitGap;}canvas.restore();}/*** 繪制當(dāng)前時(shí)間指針*/private void drawTimeIndicator(Canvas canvas) {// 指針mPaint.setColor(indicatorColor);mPaint.setStrokeWidth(indicatorWidth);canvas.drawLine(mHalfWidth, 0, mHalfWidth, mHeight * 2 / 3, mPaint);// 正三角形if (mTrianglePath.isEmpty()) {//final float halfSideLen = indicatorTriangleSideLen * .5f;mTrianglePath.moveTo(mHalfWidth - halfSideLen, 0);mTrianglePath.rLineTo(indicatorTriangleSideLen, 0);mTrianglePath.rLineTo(-halfSideLen, (float) (Math.sin(Math.toRadians(60)) * halfSideLen));mTrianglePath.close();}mPaint.setStrokeWidth(1);mPaint.setStyle(Paint.Style.FILL);canvas.drawPath(mTrianglePath, mPaint);mPaint.setStyle(Paint.Style.STROKE);}/*** 繪制時(shí)間段*/private void drawTimeParts(Canvas canvas) {if (mTimePartList == null) {return;}// 不用矩形,直接使用直線繪制mPaint.setStrokeWidth(partHeight);mPaint.setColor(partColor);float start, end;final float halfPartHeight = partHeight * .5f + 30;final float secondGap = mUnitGap / mUnitSecond;for (int i = 0, size = mTimePartList.size(); i < size; i++) {TimePart timePart = mTimePartList.get(i);start = mHalfWidth - mCurrentDistance + timePart.startTime * secondGap;end = mHalfWidth - mCurrentDistance + timePart.endTime * secondGap;canvas.drawLine(start, halfPartHeight, end, halfPartHeight, mPaint);}}/*** 格式化時(shí)間 HH:mm** @param timeValue 具體時(shí)間值* @return 格式化后的字符串,eg:3600 to 01:00*/public static String formatTimeHHmm(@IntRange(from = 0, to = MAX_TIME_VALUE) int timeValue) {if (timeValue < 0) {timeValue = 0;}int hour = timeValue / 3600;int minute = timeValue % 3600 / 60;StringBuilder sb = new StringBuilder();if (hour < 10) {sb.append('0');}sb.append(hour).append(':');if (minute < 10) {sb.append('0');}sb.append(minute);return sb.toString();}/*** 格式化時(shí)間 HH:mm:ss** @param timeValue 具體時(shí)間值* @return 格式化后的字符串,eg:3600 to 01:00:00*/public static String formatTimeHHmmss(@IntRange(from = 0, to = MAX_TIME_VALUE) int timeValue) {int hour = timeValue / 3600;int minute = timeValue % 3600 / 60;int second = timeValue % 3600 % 60;StringBuilder sb = new StringBuilder();if (hour < 10) {sb.append('0');}sb.append(hour).append(':');if (minute < 10) {sb.append('0');}sb.append(minute);sb.append(':');if (second < 10) {sb.append('0');}sb.append(second);return sb.toString();}private int dp2px(float dp) {return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());}private int sp2px(float sp) {return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());}@SuppressWarnings("all")private void logD(String format, Object... args) {if (LOG_ENABLE) {Log.d("MoneySelectRuleView", String.format("zjun@" + format, args));}}/*** 設(shè)置時(shí)間變化監(jiān)聽(tīng)事件** @param listener 監(jiān)聽(tīng)回調(diào)*/public void setOnTimeChangedListener(OnTimeChangedListener listener) {this.mListener = listener;}/*** 設(shè)置時(shí)間塊(段)集合** @param timePartList 時(shí)間塊集合*/public void setTimePartList(List<TimePart> timePartList) {this.mTimePartList = timePartList;postInvalidate();}/*** 設(shè)置當(dāng)前時(shí)間** @param currentTime 當(dāng)前時(shí)間*/public void setCurrentTime(@IntRange(from = 0, to = MAX_TIME_VALUE) int currentTime) {this.currentTime = currentTime;calculateValues();postInvalidate();}}

屬性:

<!--背景色--><attr name="zjun_bgColor" format="reference|color" /><!--刻度顏色--><attr name="zjun_gradationColor" format="reference|color" /><!--中間指針線顏色--><attr name="zjun_indicatorLineColor" format="reference|color" /><!--中間指針線寬度--><attr name="zjun_indicatorLineWidth" format="reference|dimension" /><!--時(shí)間尺控件--><declare-styleable name="TimeRuleView"><attr name="zjun_bgColor" /><!--刻度顏色--><attr name="zjun_gradationColor" /><!--時(shí)間塊的高度--><attr name="trv_partHeight" format="dimension|reference" /><!--時(shí)間塊的顏色--><attr name="trv_partColor" format="color|reference" /><!--刻度寬度--><attr name="trv_gradationWidth" format="dimension|reference" /><!--秒刻度的長(zhǎng)度--><attr name="trv_secondLen" format="dimension|reference" /><!--分刻度的長(zhǎng)度--><attr name="trv_minuteLen" format="dimension|reference" /><!--時(shí)刻度的長(zhǎng)度--><attr name="trv_hourLen" format="dimension|reference" /><!--刻度數(shù)值顏色--><attr name="trv_gradationTextColor" format="color|reference" /><!--刻度數(shù)值大小--><attr name="trv_gradationTextSize" format="dimension|reference" /><!--刻度數(shù)值與時(shí)刻度的距離--><attr name="trv_gradationTextGap" format="dimension|reference" /><!--當(dāng)前時(shí)間,單位:s--><attr name="trv_currentTime" format="integer|reference" /><!--中間指針線顏色--><attr name="zjun_indicatorLineColor" /><!--中間指針線寬度--><attr name="zjun_indicatorLineWidth" /><!--中間指針上三角形的邊長(zhǎng)--><attr name="trv_indicatorTriangleSideLen" format="dimension|reference" /></declare-styleable>

總結(jié)

以上是生活随笔為你收集整理的Android 用于视频回放显示时间刻度的一个自定义View的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。