先放預(yù)覽圖
demo.png
本文主要實現(xiàn)的是按住按鈕,開始錄音同時滾動條滾動嫌术,視頻同時播放哀澈,當(dāng)松開按鈕滾動條停止錄音停止,將剛才錄音的區(qū)域顯示在滾動條上
首先整理思路度气,長按事件割按,因為setOnLongClickListener不能得到松開的時間 所以重寫系統(tǒng)OnTouchListener是最好的辦法。
先放代碼
import android.app.Activity;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
/**
* 提供View的單擊和長按事件的判斷回調(diào)
*/
public class TouchUtils {
private static String TAG = "TouchUtils";
private static long DELAYED_TIME = 20;
public static void setTouchEventListener(final View view, final OnTouchEventListener onTouchEventListener) {
setTouchEventListener(view, DELAYED_TIME, onTouchEventListener);
}
/**
* 注冊touch事件
*/
public static void setTouchEventListener(final View view, final long delayedTime, final OnTouchEventListener onTouchEventListener) {
final Handler uiHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (view.getContext() instanceof Activity) {
Activity activity = (Activity) view.getContext();
if (activity.isFinishing()) {
removeCallbacksAndMessages(null);
}else {
onTouchEventListener.onLongTouch(view);
sendEmptyMessageDelayed(1, delayedTime);
}
} else {
throw new RuntimeException("必須綁定Activity");
}
Log.i(TAG, "onLongTouch: ");
}
};
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
uiHandler.removeCallbacksAndMessages(null);
String touchTag = (String) view.getTag(Integer.MAX_VALUE - 1);
if (touchTag == null) {
Log.i(TAG, "onSingleTouch: ");
onTouchEventListener.onSingleTouch(view);
}
onTouchEventListener.onTouchEnd(view);
Log.i(TAG, "onTouchEnd: ");
}
});
/* 下面的不用管蚯嫌,值關(guān)注 OnclickListener 就可以了 */
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
view.setTag(Integer.MAX_VALUE - 1, null);
onTouchEventListener.onTouchStart(view);
Log.i(TAG, "onTouchStart: ");
} else if (event.getAction() == MotionEvent.ACTION_UP) {
uiHandler.removeCallbacksAndMessages(null);
} else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
uiHandler.removeCallbacksAndMessages(null);
}
return false;
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
view.setTag(Integer.MAX_VALUE - 1, "onLongTouch");
uiHandler.sendEmptyMessage(1);
return false;
}
});
}
public interface OnTouchEventListener {
void onTouchStart(View view);//觸發(fā)按動
void onSingleTouch(View view);//單次點擊
void onLongTouch(View view);//長按
void onTouchEnd(View view);//觸摸事件結(jié)束
}
}
這個類使用Handler實現(xiàn)了長按時間的監(jiān)聽哲虾,拿到長按的監(jiān)聽丙躏,接下來需要做的就是滾動條滾動
接下來要實現(xiàn)的就是滾動條和藍(lán)色覆蓋了
首先分析一下組成
1择示、整個滾動條分成了2個部分
第一部分是RecyclerView實現(xiàn)的視頻每一幀的圖
第二部分是藍(lán)色覆蓋條
我們把兩部分加在一起封裝成一個DubScrollView
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import com.wanyueliang.avm.R;
import com.wanyueliang.avm.config.AppFilmQuickConfig;
import com.wanyueliang.avm.model.databean.DubBean;
import com.wanyueliang.avm.ui.create.editor.dub.adapter.DubAdapter;
import com.wanyueliang.avm.utils.click_util.onFastDisableClickListener;
import com.wanyueliang.avm.utils.log.AppLog;
import com.wanyueliang.avm.widget.slider.DoubleSliderView;
import com.wanyueliang.avm.widget.slider.scroll_slider.MaterialBean;
import com.wanyueliang.avm.widget.slider.scroll_slider.RangeDoubleSliderView;
import com.wanyueliang.avm.widget.textview.TimeTextView;
import org.greenrobot.greendao.annotation.NotNull;
import java.util.List;
public class DubScrollView extends FrameLayout {
private final String TAG = getClass().getSimpleName();
private Context mContext;
/*View*/
private TimeTextView mTvCurrentTime;
private RecyclerView mRvMaterial;
private Button mBtnDelete;
private DubMarkView mDmvMarkView;
/*data*/
private float mViewWidth;//整體的寬度
private float editViewHeight;//核心數(shù)據(jù)----編輯的View的高度,recyclerView和DoubleSliderView
private float relativeDuration = 5f;//核心數(shù)據(jù)----每一份editViewHeight對應(yīng)的時間
private float mTotalTime;//核心數(shù)據(jù)----總時間
private float precisionValue = 1000f;//核心數(shù)據(jù)----精度
private RangeDoubleSliderView.Builder builder;
private List<DubBean> mEditBeans;//陰影相關(guān)
private DubBean mShowEditBean;
private float sliderWidth;//滑塊的寬度的高度,默認(rèn)為DoubleSliderView高度的三分之一 editViewHeight/3f
private float scrollCurrentTime;//當(dāng)前中心軸對應(yīng)的時間
private int mOffsetX;//記錄RecyclerView總偏移量
private DubAdapter materialAdapter;
private List<MaterialBean> mData;
private View headView;
private View footerView;
private boolean mLimitValue;//是否有限制
private boolean isRecording;//是否正在錄制配音
private float startTime;
public DubScrollView(Context context) {
this(context, null);
}
public DubScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DubScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mContext = getContext();
LayoutInflater.from(mContext).inflate(R.layout.view_dub_scroll_view_layout, this, true);
mTvCurrentTime = (TimeTextView) findViewById(R.id.tv_current_time);
mRvMaterial = (RecyclerView) findViewById(R.id.rv_material);
mRvMaterial = (RecyclerView) findViewById(R.id.rv_material);
mTvCurrentTime = (TimeTextView) findViewById(R.id.tv_current_time);
mBtnDelete = (Button) findViewById(R.id.btn_delete);
mDmvMarkView = (DubMarkView) findViewById(R.id.dmv_mark_view);
mDmvMarkView.hideSlider();
}
/*底層素材的數(shù)據(jù)*/
public void setMaterialData(List<MaterialBean> materialBeans, float totalTime) {
this.mData = materialBeans;
this.mTotalTime = totalTime;
if (materialAdapter != null) {
materialAdapter.setData(mData);
materialAdapter.notifyDataSetChanged();
}
}
/*添加的素材區(qū)域數(shù)據(jù)*/
public void setEditData(List<DubBean> editBeans) {
this.mEditBeans = editBeans;
if (mDmvMarkView != null) {
mDmvMarkView.setEditBeans(mEditBeans);
mDmvMarkView.notifyDataChange();
}
//第一次進(jìn)入晒旅,先匹配
//檢查是否有匹配到的區(qū)間
checkMatching();
}
/*更新添加的素材區(qū)域數(shù)據(jù)*/
public void notifyDataSetChange() {
if (mDmvMarkView != null) {
mDmvMarkView.notifyDataChange();
}
//第一次進(jìn)入栅盲,先匹配
//檢查是否有匹配到的區(qū)間
checkMatching();
}
public boolean isRecording() {
return isRecording;
}
public void setRecordingAndStartTime(boolean recording, float startTime) {
isRecording = recording;
this.startTime = startTime;
}
public void setBtnDeleteVisibility(int visibility) {
mBtnDelete.setVisibility(visibility);
}
public void setBtnDeleteOnClickListener(onFastDisableClickListener onClickListener) {
mBtnDelete.setOnClickListener(onClickListener);
}
/**
* 界面生成,初始化數(shù)據(jù)
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w > 0 && h > 0) {
mViewWidth = w;
editViewHeight = mRvMaterial.getMeasuredHeight();
Log.i(TAG, "onSizeChanged: editViewHeight=" + editViewHeight);
initData();
initListener();
}
}
private void initData() {
mEditBeans = AppFilmQuickConfig.getFilmDub();
materialAdapter = new DubAdapter(mContext, relativeDuration, editViewHeight);//設(shè)置底層的素材預(yù)覽圖
materialAdapter.setData(this.mData);
mDmvMarkView.setEditBeans(mEditBeans);
mTvCurrentTime.setTime(0, (long) (balanceValue(mTotalTime) * precisionValue));
mRvMaterial.setLayoutManager(new LinearLayoutManager(mContext, RecyclerView.HORIZONTAL, false));
/*在RecyclerView添加前面和后面的占位View*/
addPlaceholderView();
mRvMaterial.setAdapter(materialAdapter);
// 滑塊的寬度等于滑塊的高度/3f
sliderWidth = editViewHeight / 3f;
float totalCurrent = ((mViewWidth - sliderWidth * 2f) / editViewHeight) * relativeDuration;
int sliderMinCurrent = (int) (1 * precisionValue);//為了精準(zhǔn)度废恋,設(shè)置的數(shù)值增大10倍
int sliderTotalCurrent = (int) (totalCurrent * precisionValue + 0.5f);//為了精準(zhǔn)度谈秫,設(shè)置的數(shù)值增大10倍
builder = new RangeDoubleSliderView.Builder();
builder.setMinCurrent(sliderMinCurrent)
.setStartCurrent(sliderTotalCurrent / 2)//起點為中心軸
.setDurationCurrent(sliderTotalCurrent / 2)//持續(xù)時間
.setTotalCurrent(sliderTotalCurrent);
mDmvMarkView.setBuilder(builder);//設(shè)置DoubleSliderView的參數(shù)
mDmvMarkView.setLimitView(headView, footerView);//設(shè)置限制左右滑塊的坐標(biāo)相關(guān)的View
//第一次進(jìn)入,先匹配
//檢查是否有匹配到的區(qū)間
checkMatching();
}
/**
* 檢查是否有匹配到的區(qū)間
*/
private void checkMatching() {
if (mEditBeans != null && mEditBeans.size() > 0) {
int editSize = mEditBeans.size();
float leftSeekTo;
float rightSeekTo;
for (int i = editSize - 1; i >= 0; i--) {
DubBean dubBean = mEditBeans.get(i);
float startTime = Float.valueOf(dubBean.getOffsetStartTime());
float endTime = Float.valueOf(dubBean.getTimeLength()) + startTime;
if (startTime <= scrollCurrentTime && endTime >= scrollCurrentTime) {
leftSeekTo = startTime / relativeDuration * editViewHeight + mViewWidth / 2 - mOffsetX - sliderWidth;
rightSeekTo = leftSeekTo + (endTime - startTime) / relativeDuration * editViewHeight + sliderWidth;
mDmvMarkView.setTran(leftSeekTo, rightSeekTo);
mShowEditBean = dubBean;
break;
}
}
if (mOnScrollChangeListener != null) {
mOnScrollChangeListener.onMatchEditSelected(mShowEditBean);
}
}
}
/**
* 在RecyclerView添加前面和后面的占位View
*/
private void addPlaceholderView() {
headView = LayoutInflater.from(mContext).inflate(R.layout.item_placeholder_layout, mRvMaterial, false);
setViewWidth(headView, mViewWidth / 2f);
materialAdapter.addHeaderView(headView);
footerView = LayoutInflater.from(mContext).inflate(R.layout.item_placeholder_layout, mRvMaterial, false);
setViewWidth(footerView, mViewWidth / 2f);
materialAdapter.addFooterView(footerView);
}
/*占位View的寬度為重寬度的一半*/
private void setViewWidth(View view, float w) {
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
marginLayoutParams.width = (int) (w + 0.5f);
view.setLayoutParams(marginLayoutParams);
}
/*設(shè)置監(jiān)聽*/
private void initListener() {
mRvMaterial.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {//停止滑動的時候鱼鼓,尋找對應(yīng)的View
//檢查是否有匹配到的區(qū)間
if (mOnUseScrollChangeListener != null) {
mOnUseScrollChangeListener.onStateIdle();
}
checkMatching();
} else if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
if (mOnUseScrollChangeListener != null) {
mOnUseScrollChangeListener.onUserDragging();
}
}
if (mOnScrollChangeListener != null) {
mOnScrollChangeListener.onScrollStateChanged(recyclerView, newState);
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
mOffsetX += dx;//RecyclerView整體的滑動偏移量
//得到中心軸對應(yīng)的時間
scrollCurrentTime = balanceValue(mOffsetX / editViewHeight/* * relativeDuration*/ * precisionValue) / precisionValue;
AppLog.i(TAG + "_AVM", "onScrolled:預(yù)覽條的偏移量 mOffsetX=" + mOffsetX + "scrollCurrentTime == " + scrollCurrentTime + "startTime" + startTime);
//設(shè)置中心軸對應(yīng)的時間
mTvCurrentTime.setTime((long) (scrollCurrentTime * precisionValue), (long) (balanceValue(mTotalTime) * precisionValue));
//使ShadowDoubleSliderViewView跟隨滾動
mDmvMarkView.setCustomScrollX(scrollCurrentTime, mOffsetX, dx);
mDmvMarkView.setRecordingAndStartTime(isRecording, startTime);
if (mOnScrollChangeListener != null) {
mOnScrollChangeListener.onScrolled(recyclerView, dx, dy);
mOnScrollChangeListener.onCurrentTime(scrollCurrentTime);
}
}
});
}
public void setLimit(boolean mLimitValue) {
this.mLimitValue = mLimitValue;
}
public boolean getLimit() {
return mLimitValue;
}
/*使中心軸滾動到對應(yīng)的時間*/
public void seekTo(float currentTime) {
mRvMaterial.scrollBy((int) (-(scrollCurrentTime - currentTime) * editViewHeight / relativeDuration), 0);
}
public void smoothScrollToPosition(int position) {
mRvMaterial.smoothScrollToPosition(position);
}
/*平衡誤差*/
private int balanceValue(float value) {
if (value % 0.5f != 0) {
value += 0.5f;
} else {
//Nothing
Log.i(TAG, "balanceValue: 有等于0的value=" + value);
}
return (int) value;
}
public float getCurrentTime() {
return scrollCurrentTime;
}
public float getTotalTime() {
return mTotalTime;
}
public List<DubBean> getEditBeans() {
return mEditBeans;
}
private OnScrollChangeListener mOnScrollChangeListener;
public void setOnEditScrollChangeListener(OnScrollChangeListener onScrollChangeListener) {
this.mOnScrollChangeListener = onScrollChangeListener;
}
private OnUseScrollChangeListener mOnUseScrollChangeListener;
public void setOnUseScrollChangeListener(OnUseScrollChangeListener onUseScrollChangeListener) {
this.mOnUseScrollChangeListener = onUseScrollChangeListener;
}
/**
* 為了可能的需要拟烫,該回調(diào)結(jié)合了{(lán)@link RecyclerView.OnScrollListener} 和 {@link DoubleSliderView.OnSliderChangerListener}
* 并且實現(xiàn)了接口的方法,但是沒有做處理迄本。外部有需要的可以選擇復(fù)寫特定的方法來處理
*/
public abstract static class OnScrollChangeListener extends RecyclerView.OnScrollListener implements DoubleSliderView.OnSliderChangerListener {
//匹配到對應(yīng)的編輯區(qū)域
public abstract void onMatchEditSelected(DubBean editBean);
public abstract void onMatchEditChange(DubBean editBean);
public void onCurrentTime(float currentTime) {
}
@Override
public void onStartTouch(int touchThumb) {
}
@Override
public void onLeftSliderChange(long leftCurrent, long rightCurrent, long totalCurrent) {
}
@Override
public void onRightSliderChange(long leftCurrent, long rightCurrent, long totalCurrent) {
}
@Override
public void onStopTouch(int touchThumb) {
}
protected abstract void onEndCrop(@NotNull DubBean showEditBean);
}
public interface OnUseScrollChangeListener {
void onUserDragging();//人為滑動
void onStateIdle();//停止拖動
}
}
上面這個類更多的是處理邏輯其實可以不用去關(guān)注太多硕淑,重要的是DubMarkView
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import com.wanyueliang.avm.R;
import com.wanyueliang.avm.model.databean.DubBean;
import com.wanyueliang.avm.utils.log.AppLog;
import com.wanyueliang.avm.widget.slider.scroll_slider.RangeDoubleSliderView;
import java.util.List;
public class DubMarkView extends RangeDoubleSliderView {
private final String TAG = getClass().getSimpleName();
private Context mContext;
/*data*/
//參數(shù)
private float precisionValue = 100f;//核心數(shù)據(jù)----精度
private float mCurrentTime;//核心數(shù)據(jù)----精度
//繪制數(shù)據(jù)
private Paint mShadowPaint;//線條的畫筆顏色
private Paint mLinePaint;//線條的畫筆顏色
private float mLineSize;//線條的畫筆寬度
private int mShadowColor;//線條的畫筆顏色
protected float mViewWidth;//View的寬度
protected float mViewHeight;//View的高度
protected Rect rect = new Rect();
private List<DubBean> dubBeans;
private boolean isRecording;
private float startTime;
public DubMarkView(Context context) {
this(context, null);
}
public DubMarkView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DubMarkView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w > 0 && h > 0) {
mViewWidth = w;
mViewHeight = h;
}
}
private void initialize() {
mContext = getContext();
mShadowColor = getResources().getColor(R.color.colorPrimary);
mShadowPaint = new Paint();
mShadowPaint.setColor(mShadowColor);
mShadowPaint.setAlpha((int) (255 * 0.4));
mShadowPaint.setAntiAlias(true);
mShadowPaint.setStyle(Paint.Style.FILL);
mLineSize = 8;
int lineColor = getResources().getColor(R.color.colorPrimary);
mLinePaint = new Paint();
mLinePaint.setColor(lineColor);
mLinePaint.setStrokeWidth(mLineSize);
mLinePaint.setAlpha((int) (255 * 0.4));
mLinePaint.setAntiAlias(true);
}
public void setEditBeans(List<DubBean> dubBeans) {
this.dubBeans = dubBeans;
invalidate();
}
public boolean isRecording() {
return isRecording;
}
public void setRecordingAndStartTime(boolean recording, float startTime) {
isRecording = recording;
this.startTime = startTime;
}
public void notifyDataChange() {
invalidate();
}
private float mOffsetX;
@Override
public void setCustomScrollX(float currentTime, int offsetX, int dxValue) {
super.setCustomScrollX(currentTime, offsetX, dxValue);
mCurrentTime = currentTime;
mOffsetX = offsetX;
invalidate();
}
@Override
public void onDraw(Canvas canvas) {
//
if (isRecording) {
mShadowPaint.setColor(mShadowColor);
mShadowPaint.setAlpha((int) (255 * 0.4));
rect.left = (int) ((mViewWidth / 2f + startTime * mViewHeight) - mOffsetX);
rect.right = (int) (mViewWidth / 2f + mCurrentTime * mViewHeight - mOffsetX) + 1;
AppLog.i("DubMarkView_AVM" + "_AVM", "Recording !!!! === startTime == " + mCurrentTime + "mCurrentTime == " + mCurrentTime);
rect.top = 0;
rect.bottom = (int) mViewHeight;
canvas.drawLine(rect.left + mLineSize / 2f, 0, rect.left + mLineSize / 2f, mViewHeight, mLinePaint);
canvas.drawLine(rect.right - mLineSize / 2f, 0, rect.right - mLineSize / 2f, mViewHeight, mLinePaint);
canvas.drawRect(rect, mShadowPaint);
drawEditRect(canvas);
} else {
drawEditRect(canvas);
}
super.onDraw(canvas);
}
private void drawEditRect(Canvas canvas) {
if (dubBeans != null) {
for (int i = 0; i < dubBeans.size(); i++) {
DubBean dubBean = dubBeans.get(i);
mShadowPaint.setColor(mShadowColor);
mShadowPaint.setAlpha((int) (255 * 0.4));
rect.left = (int) (mViewWidth / 2f + (Float.valueOf(dubBean.getOffsetStartTime()) * mViewHeight) - mOffsetX);
rect.right = (int) (mViewWidth / 2f + ((Float.valueOf(dubBean.getOffsetStartTime()) + Float.valueOf(dubBean.getTimeLength())) * mViewHeight) - mOffsetX) + 1;
AppLog.i("DubMarkView_AVM" + "_AVM",
"drawEditRect === startTime == " + Float.valueOf(dubBean.getOffsetStartTime())
+ "mCurrentTime == " + (Float.valueOf(dubBean.getOffsetStartTime()) + Float.valueOf(dubBean.getTimeLength()))
);
rect.top = 0;
rect.bottom = (int) mViewHeight;
canvas.drawLine(rect.left + mLineSize / 2f, 0, rect.left + mLineSize / 2f, mViewHeight, mLinePaint);
canvas.drawLine(rect.right - mLineSize / 2f, 0, rect.right - mLineSize / 2f, mViewHeight, mLinePaint);
canvas.drawRect(rect, mShadowPaint);
}
}
}
}
核心代碼就是onDraw方法在錄制的時候通過外部傳入的當(dāng)前時間繪制兩條線然后繪制Rect畫出藍(lán)色條形圖
RecyclerView這里就不詳細(xì)描述了我們要說的是
RangeDoubleSliderView這個類更多的是實現(xiàn)了一個兩邊可滑動條,其實我們這個功能中用不上可以繼承View不用繼承RangeDoubleSliderView也沒關(guān)系