先看效果图
image.png
public class ScrollTrackView extends HorizontalScrollView {
private Handler mScrollHandler;
private OnScrollTrackListener mOnScrollTrackListener;
private OnProgressRunListener mProgressRunListener;
private int mBackgroundColor = Color.LTGRAY;
private int mForegroundColor = Color.BLUE;
private int mSpaceSize = 16;//方块中间空格大小
private int mTrackItemWidth = 6;//每个方块的宽度
private int mDelayTime = 20;//ms
private int mTrackFragmentCount = 10;
private boolean isAutoRun = true;//是否自动跑进度
private boolean isLoopRun = false;//是否循环跑进度
private int mCutDuration = 10 * 1000;//裁剪区间,也就是控件左边,跑到右边的时间
private float mSpeed = 10;
/**
* 滚动状态:
* IDLE=滚动停止
* TOUCH_SCROLL=手指拖动滚动
* FLING=滚动
*/
enum ScrollStatus {
IDLE, TOUCH_SCROLL, FLING
}
/**
* 记录当前滚动的距离
*/
private int currentX = -9999999;
/**
* 当前滚动状态
*/
private ScrollStatus scrollStatus = ScrollStatus.IDLE;
private Track track;//每个方块的高度要通过addView添加到当前View中
private boolean disableTouch;
private TrackMoveController moveController;//方块移动的控制类
private int audioDuration;
public ScrollTrackView(Context context) {
super(context);
initView(context);
}
public ScrollTrackView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ScrollTrackView);
mBackgroundColor = typedArray.getColor(R.styleable.ScrollTrackView_background_color, mBackgroundColor);
mForegroundColor = typedArray.getColor(R.styleable.ScrollTrackView_foreground_color, mForegroundColor);
//px
mSpaceSize = Math.round(typedArray.getDimension(R.styleable.ScrollTrackView_space_size, mSpaceSize));
//mSpaceSize =ViewUtil.dp2px(context,Math.round(spaceSizeDp));
mTrackItemWidth = Math.round(typedArray.getDimension(R.styleable.ScrollTrackView_track_item_width, mTrackItemWidth));
//mTrackItemWidth = ViewUtil.dp2px(context,Math.round(trackItemWidthDp));
isAutoRun = typedArray.getBoolean(R.styleable.ScrollTrackView_auto_run, isAutoRun);
mTrackFragmentCount = typedArray.getInteger(R.styleable.ScrollTrackView_track_fragment_count, mTrackFragmentCount);
mCutDuration = typedArray.getInteger(R.styleable.ScrollTrackView_cut_duration, mCutDuration);
isLoopRun = typedArray.getBoolean(R.styleable.ScrollTrackView_loop_run, isLoopRun);
typedArray.recycle();
initView(context);
}
public ScrollTrackView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(final Context context) {
track = new Track(context);
track.setBackgroundColorInt(mBackgroundColor);
track.setForegroundColor(mForegroundColor);
track.setSpaceSize(mSpaceSize);
track.setTrackFragmentCount(mTrackFragmentCount);
track.setTrackItemWidth(mTrackItemWidth);
HorizontalScrollView.LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
addView(track, lp);
setSmoothScrollingEnabled(false);
moveController = new TrackMoveController(mDelayTime, new TrackMoveController.OnProgressChangeListener() {
@Override
public void onProgressChange(int progress) {
Message msg = progressHandler.obtainMessage(1);
msg.arg1 = progress;
progressHandler.sendMessage(msg);
}
@Override
public void onProgressStart() {
if (mProgressRunListener != null) {
mProgressRunListener.onTrackStart(getStartTime());
}
}
@Override
public void onProgressEnd() {
if (mProgressRunListener != null) {
mProgressRunListener.onTrackEnd();
}
}
});
post(new Runnable() {
@Override
public void run() {
//可视的时候开始走进度
moveController.setScrollTrackViewWidth(getWidth());
mSpeed = ((getWidth() * 1f) / (mCutDuration * 1f));//根据时间和控件的宽度计算速度
float delayTime = 1f / mSpeed;//根据速度来算走每个像素点需要多久时间
moveController.setDelayTime(Math.round(delayTime));//四舍五入
moveController.setLoopRun(isLoopRun);
if (isAutoRun) {
startMove();
}
}
});
mScrollHandler = new Handler();
//滑动状态监听
mOnScrollTrackListener = new OnScrollTrackListener() {
@Override
public void onScrollChanged(ScrollStatus scrollStatus) {
switch (scrollStatus) {
case IDLE:
if (moveController != null) {
moveController.setScrollTrackStartX(getScrollX());
moveController.continueRun();
}
if (mProgressRunListener != null) {
mProgressRunListener.onTrackStartTimeChange(getStartTime());
}
break;
case FLING:
break;
case TOUCH_SCROLL:
if (moveController != null) {
moveController.pause();
}
break;
default:
break;
}
}
};
}
public void setCutDuration(int cutDuration) {
mCutDuration = cutDuration;
}
/**
* 设置循环播放
*
* @param isLoop
*/
public void setLoopRun(boolean isLoop) {
isLoopRun = isLoop;
}
public void setTrackTemplateData(float[] data) {
if (track != null && data != null) {
track.setTrackTemplateData(data);
}
}
public void setTrackFragmentCount(int count) {
if (track != null) {
track.setTrackFragmentCount(count);
}
}
public void setSpaceSize(int px) {
if (track != null) {
track.setSpaceSize(px);
}
}
public void setTrackItemWidth(int px) {
if (track != null) {
track.setTrackItemWidth(px);
}
}
//-------------scroll control-----------------
private interface OnScrollTrackListener {
void onScrollChanged(ScrollStatus scrollStatus);
}
/**
* 滚动监听runnable 方便获取滑动状态
*/
private Runnable scrollRunnable = new Runnable() {
@Override
public void run() {
if (getScrollX() == currentX) {
//滚动停止,取消监听线程
scrollStatus = ScrollStatus.IDLE;
if (mOnScrollTrackListener != null) {
mOnScrollTrackListener.onScrollChanged(scrollStatus);
}
mScrollHandler.removeCallbacks(this);
return;
} else {
//手指离开屏幕,但是view还在滚动
scrollStatus = ScrollStatus.FLING;
if (mOnScrollTrackListener != null) {
mOnScrollTrackListener.onScrollChanged(scrollStatus);
}
}
currentX = getScrollX();
//滚动监听间隔:milliseconds
mScrollHandler.postDelayed(this, 20);
}
};
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
this.scrollStatus = ScrollStatus.TOUCH_SCROLL;
mOnScrollTrackListener.onScrollChanged(scrollStatus);
mScrollHandler.removeCallbacks(scrollRunnable);
break;
case MotionEvent.ACTION_UP:
mScrollHandler.post(scrollRunnable);
break;
}
return super.onTouchEvent(ev);
}
/**
* 进度控制
*/
Handler progressHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
track.setProgress(msg.arg1);
}
}
};
/*@Override
public void fling(int velocity) {
super.fling(velocity / 1000);
}*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (disableTouch) {
return true;
}
return super.onInterceptTouchEvent(ev);
}
/**
* 开始
*/
public void startMove() {
disableTouch = true;
if (moveController != null) {
moveController.start();
}
}
/**
* 重新开始播放
*/
public void restartMove() {
disableTouch = true;
if (moveController != null) {
scrollTo(0, 0);
smoothScrollTo(0, 0);
moveController.restart();
if (mProgressRunListener != null) {
mProgressRunListener.onTrackStartTimeChange(0);
}
}
}
/**
* 停止
*/
public void stopMove() {
disableTouch = false;
if (moveController != null) {
moveController.stop();
}
}
public void pauseMove() {
disableTouch = false;
if (moveController != null) {
moveController.pause();
}
}
/**
* 轨道开始播放到轨道结束监听
*/
public interface OnProgressRunListener {
void onTrackStart(int ms);
void onTrackStartTimeChange(int ms);
void onTrackEnd();
}
public void setOnProgressRunListener(OnProgressRunListener listener) {
mProgressRunListener = listener;
}
/**
* 设置音频总时间
*/
public void setDuration(int ms) {
audioDuration = ms;
}
/**
* 获取歌曲开始时间 (毫秒)
*/
public int getStartTime() {
float rate = Math.abs(getScrollX()) / (track.getWidth() * 1f);
return (int) (audioDuration * rate);
}
public void setDraftTime(int time) {
float rate = time / audioDuration;
int scrollX = (int) (rate * track.getWidth() * 1f);
setScrollX(Math.abs(scrollX));
}
public void setProgressContinue(boolean isContinue) {
if (moveController != null) {
moveController.setProgressContinue(isContinue);
}
}
/**
* 设置进度
*
* @param percent 浮点数,当前位置在整个view 中的比例
*/
public void setRealProgress(float percent) {
if (moveController != null) {
float position = getWidth() * 1f * percent;
moveController.setCurrentProgressPosition(Math.round(position));
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopMove();
}
}
控件比较简单,这里处理的方式,主要是继承一个HorizontalScrollView,来实现左右滑动的效果,当然如果你用RecycleView也是可以的。相关注释在代码内部有提供
接下来看Track的定义
public class Track extends View {
private int trackWidth = 0;
private Paint slipPaint = null;
private Paint maskPaint = null;
private Paint mTrackPaint = null;
private Paint borderPaint = null;
private int borderWidth = DensityUtil.dp2px(3);
private int progress = 0;
private Bitmap shape = null;
private Bitmap mask = null;
private boolean isNewMask = true;
private int trackTemplateCount;
private int mBackgroundColor;
private int mForegroundColor;
private int mSpaceSize = DensityUtil.dp2px(3);
private int mTrackItemWidth = DensityUtil.dp2px(3);
private int mTrackFragmentCount;
// 25。55。50。40。30。25。30。25。55。25。30。 15。25。15。15。
//0.41F,0.9F,0.82F,0.66F,0.49F,0.41F,0.49F,0.41F,0.9F,0.41F,0.49F,0.25F,0.41F,0.25F,0.25F
private float[] mTrackTemplateData = new float[]{0.41F, 0.9F, 0.82F, 0.66F, 0.49F, 0.41F, 0.49F, 0.41F, 0.9F, 0.41F, 0.49F, 0.25F, 0.41F, 0.25F, 0.25F};//这里定义每个大的片段需要多少个小方块,我这里是15个小方块组合成一个大的方块,每个大的方块循环滚动渲染
public void setBackgroundColorInt(int backgroundColor) {
this.mBackgroundColor = backgroundColor;
this.invalidate();
}
public void setForegroundColor(int foregroundColor) {
this.mForegroundColor = foregroundColor;
this.invalidate();
}
public void setSpaceSize(int spaceSize) {
this.mSpaceSize = spaceSize;
this.invalidate();
}
public void setTrackItemWidth(int trackItemWidth) {
this.mTrackItemWidth = trackItemWidth;
this.invalidate();
}
public void setTrackFragmentCount(int trackFragmentCount) {
this.mTrackFragmentCount = trackFragmentCount;
this.invalidate();
}
public void setTrackTemplateData(float[] mTrackTemplateData) {
this.mTrackTemplateData = mTrackTemplateData;
this.invalidate();
}
public Track(Context context) {
super(context);
this.init();
}
public Track(Context paramContext, AttributeSet paramAttributeSet) {
super(paramContext, paramAttributeSet);
this.init();
}
private void init() {
this.slipPaint = new Paint();
this.slipPaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
this.slipPaint.setFilterBitmap(false);
this.maskPaint = new Paint();
this.maskPaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
this.maskPaint.setFilterBitmap(false);
this.mTrackPaint = new Paint();
this.mTrackPaint.setAntiAlias(true);
this.mTrackPaint.setStrokeWidth((float) this.mTrackItemWidth);
this.mTrackPaint.setColor(-3355444);
this.mTrackPaint.setStyle(Style.FILL);
this.mTrackPaint.setStrokeCap(Cap.ROUND);
this.borderPaint = new Paint();
this.borderPaint.setAntiAlias(true);
this.borderPaint.setStrokeWidth((float) this.borderWidth);
this.borderPaint.setColor(-16777216);
this.borderPaint.setStyle(Style.STROKE);
this.borderPaint.setStrokeCap(Cap.SQUARE);
this.trackTemplateCount = this.mTrackTemplateData.length;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 0;
int height = 0;
int modeW = MeasureSpec.getMode(widthMeasureSpec);
if (modeW == -2147483648) {
width = MeasureSpec.getSize(widthMeasureSpec);
}
if (modeW == 1073741824) {
width = widthMeasureSpec;
}
if (modeW == 0) {
this.trackWidth = this.mSpaceSize + (this.mTrackItemWidth + this.mSpaceSize) * this.trackTemplateCount * this.mTrackFragmentCount;
width = this.trackWidth;
}
int modeH = MeasureSpec.getMode(height);
if (modeH == -2147483648) {
height = MeasureSpec.getSize(heightMeasureSpec);
}
if (modeH == 1073741824) {
height = heightMeasureSpec;
}
if (modeH == 0) {
height = MeasureSpec.getSize(heightMeasureSpec);
}
this.setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
try {
this.drawTrack(canvas, this.mBackgroundColor);
int layer = canvas.saveLayer(0.0F, 0.0F, (float) this.getWidth(), (float) this.getHeight(), (Paint) null, 31);
this.drawTrack(canvas, this.mForegroundColor);
if (this.shape == null || this.shape.isRecycled()) {
this.shape = this.getShape(this.getWidth(), this.getHeight());
}
canvas.drawBitmap(this.shape, 0.0F, 0.0F, this.slipPaint);
if (this.isNewMask) {
this.mask = this.getMask(this.getWidth(), this.getHeight());
this.isNewMask = false;
}
canvas.drawBitmap(this.mask, 0.0F, 0.0F, this.maskPaint);
canvas.restoreToCount(layer);
} catch (Exception var3) {
var3.printStackTrace();
}
}
private Bitmap getShape(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
RectF localRectF = new RectF(0.0F, 0.0F, (float) width, (float) height);
Paint paint = new Paint();
paint.setAntiAlias(true);
canvas.drawRect(localRectF, paint);
return bitmap;
}
private Bitmap getMask(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(this.mForegroundColor);
canvas.drawRect(0.0F, 0.0F, (float) this.progress, (float) height, paint);
return bitmap;
}
public void setProgress(int progress) {
this.progress = progress;
this.isNewMask = true;
this.invalidate();
}
private void drawTrack(Canvas canvas, int color) {
this.mTrackPaint.setColor(color);
if (this.trackTemplateCount > 0) {
int cy = canvas.getHeight() / 2;
for (int j = 0; j < this.mTrackFragmentCount; ++j) {
for (int i = 0; i < this.trackTemplateCount; ++i) {
int x = this.mSpaceSize + (this.mTrackItemWidth + this.mSpaceSize) * i + (this.mTrackItemWidth + this.mSpaceSize) * this.trackTemplateCount * j;
canvas.drawLine((float) x, (float) cy - this.mTrackTemplateData[i] * (float) this.getHeight() / 2.0F, (float) x, (float) cy + this.mTrackTemplateData[i] * (float) this.getHeight() / 2.0F, this.mTrackPaint);
}
}
}
}
}
接下来也比较简单TrackMoveController
这个类主要是用来控制滑动的Track滚动状态
public class TrackMoveController {
private Timer mTimer;
private long mDelayTime = 10;//ms
private int mProgress = 0;
private int mScrollTrackViewWidth;
private int mScrollTrackStartX = 0;
private OnProgressChangeListener mListener;
private boolean isCanRun = true;
private boolean isLoopRun = false;
private boolean isStarted = true;
public TrackMoveController(int delayTime){
mDelayTime = delayTime;
}
public TrackMoveController(int delayTime, OnProgressChangeListener listener){
mDelayTime = delayTime;
mListener = listener;
}
public void setDelayTime(long ms){
mDelayTime = ms;
}
public void setLoopRun(boolean loop){
isLoopRun = loop;
}
public synchronized void start() {
if (mTimer == null) {
mTimer = new Timer();
mListener.onProgressStart();
mTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (isCanRun) {
if (isLoopRun) {
//移动到最右边的时候,重新从启始位置开始移动
if((mProgress-mScrollTrackStartX) >= mScrollTrackViewWidth){
mProgress = mScrollTrackStartX;
mListener.onProgressEnd();
mListener.onProgressStart();
}
if (isProgressContinue) {
mProgress ++;
}
if(mListener!=null){
mListener.onProgressChange(mProgress);
}
}else{
//onProgressStart 只执行一次
if(isStarted){
mListener.onProgressStart();
isStarted = false;
}
if(mListener!=null){
mListener.onProgressChange(mProgress);
}
if((mProgress-mScrollTrackStartX) >= mScrollTrackViewWidth){
mListener.onProgressEnd();
}else{
mProgress ++;
}
}
/*//移动到最右边的时候,重新从启始位置开始移动
if((mProgress-mScrollTrackStartX) >= mScrollTrackViewWidth){
mProgress = mScrollTrackStartX;
mListener.onProgressStart();
}
mProgress ++;
if(mListener!=null){
mListener.onProgressChange(mProgress);
}*/
}
}
}, 0,mDelayTime );//延时时间,间隔时间
}else{
isCanRun = true;
}
}
public boolean isRunning() {
return mTimer != null;
}
public synchronized void stop() {
if (isRunning()) {
mTimer.cancel();
mTimer = null;
}
}
public synchronized void pause(){
if(isRunning()){
isCanRun = false;
}
}
public synchronized void continueRun(){
isCanRun = true;
mProgress = mScrollTrackStartX;
}
public synchronized void restart(){
stop();
mScrollTrackStartX = 0;
mProgress = 0;
isCanRun = true;
start();
}
private boolean isProgressContinue = true;
//绘制到当前位置然后暂停进度
public void setProgressContinue(boolean bPause){
isProgressContinue = bPause;
}
public int getProgress(){
return mProgress;
}
public interface OnProgressChangeListener{
void onProgressChange(int progress);
void onProgressStart();
void onProgressEnd();
}
public void setOnProgressChangeListener(OnProgressChangeListener listener){
mListener = listener;
}
public void setScrollTrackViewWidth(int mScrollTrackViewWidth) {
this.mScrollTrackViewWidth = mScrollTrackViewWidth;
}
/**
* 设置当前进度条位置
* @param positionX px
*/
public void setCurrentProgressPosition(int positionX){
mProgress = mScrollTrackStartX + positionX;
}
public void setScrollTrackStartX(int x){
this.mScrollTrackStartX = x;
}
}
资源声明
<declare-styleable name="ScrollTrackView">
<attr name="background_color" format="color"></attr>
<attr name="foreground_color" format="color"></attr>
<attr name="space_size" format="dimension"></attr>
<attr name="track_item_width" format="dimension"></attr>
<attr name="track_fragment_count" format="integer" />
<attr name="auto_run" format="boolean" />
<attr name="cut_duration" format="integer" />
<attr name="loop_run" format="boolean" />
</declare-styleable>
使用方法
<com.xxx.ScrollTrackView
android:id="@+id/scroll_track"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginLeft="@dimen/dimen_7dp"
android:layout_marginRight="@dimen/dimen_7dp"
app:background_color="@color/white"
app:foreground_color="@color/pat_record_fill_music" />
调用方法
public void initAudioCrop(ScrollTrackView mScrollTrackView, String videoPath) {
mScrollTrackView.setSpaceSize(DensityUtil.dp2px(3));//设置空格宽度
mScrollTrackView.setTrackItemWidth(DensityUtil.dp2px(3));
mScrollTrackView.setLoopRun(true);
mScrollTrackView.setCutDuration(FileUtils.getMediaDuration(videoPath));//屏幕左边跑到右边持续的时间,以视频长度为准
mScrollTrackView.stopMove();
mScrollTrackView.setOnProgressRunListener(new ScrollTrackView.OnProgressRunListener() {
@Override
public void onTrackStart(int i) {
}
@Override
public void onTrackStartTimeChange(int i) {
//保留两位小数
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMaximumFractionDigits(2);
// String timeData = "从 " + nf.format((i * 1f) / 1000f) + " 秒开始";
String times = String.format(getContext().getString(R.string.patrecord_public_time), FileUtils.stringForTime(i));
mView.showAutioCutStartTime(times, i);
}
@Override
public void onTrackEnd() {
}
});
}
第一次在简书上面写这个。不是很习惯啊。这个格式弄了我老半天,等我慢慢熟悉,我再写详细点。欢迎大家提供更优化的方案让我修改。
网友评论