仿微信录制视频之自定义View 叁歲伎倆 2022-06-17 09:36 141阅读 0赞 最近公司一个项目需要实现仿微信拍照,然后我去看了看微信的界面: ![这里写图片描述][SouthEast] 然后我自己最后实现的界面是这样: ![这里写图片描述][SouthEast 1] 当然,这个界面不是重点,重点是这个自定义View需要实现单击实现拍照,长按实现录制视频。然后这个自定义View可以通过自定义触摸事件来完成区别。其实一开始我也不明白,为什么按下去的时候就开始调用了长按监听事件,最后还能将二者区分。最后留意到这一句代码: myHandler.sendEmptyMessageDelayed(0, animTime);//animTime默认值是200; 其中表达的意思是该信息将在200毫秒后发送出去,我们看看手指抬起后的处理: case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: startTime = 0; float upX = event.getX(); float upY = event.getY(); if(myHandler.hasMessages(0)){ myHandler.removeMessages(0); if (Math.abs(upX - downX) < dp5 && Math.abs(upY - downY) < dp5) { if(onGestureListener != null) onGestureListener.onClick(); } }else if(onGestureListener != null && closeMode){ onGestureListener.onLift(); closeButton(); } break; 如果myHandler还有这条消息没有处理(拍照的情况)就移除,并调用点击的监听事件;如果没有这条消息了(录制视频的情况)就调用手指离开的监听,并且通过closeButton()方法来初始化控件,整个手势处理就这么简单,代码如下: package cn.com.vicent.mymap; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; /** * Created by asus on 2017/4/22. */ public class RecordedButton extends View { private static final String TAG = "RecordedButton"; private int measuredWidth = -1; private Paint paint; private int colorGray; private float radius1; private float radius2; private float zoom = 0.7f;//初始化缩放比例 private int dp5; private Paint paintProgress; private int colorBlue; private float girth;//周长 private RectF oval; private int max; private OnGestureListener onGestureListener; private int animTime = 200; private float downX; private float downY; private boolean closeMode = true; public RecordedButton(Context context) { super(context); init(); } public RecordedButton(Context context, AttributeSet attrs) { super(context, attrs); init(); } public RecordedButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { dp5 = (int) getResources().getDimension(R.dimen.dp5); colorGray = getResources().getColor(R.color.gray); colorBlue = getResources().getColor(R.color.blue); paint = new Paint(); paint.setAntiAlias(true); paintProgress = new Paint(); paintProgress.setAntiAlias(true); paintProgress.setColor(colorBlue); paintProgress.setStrokeWidth(dp5); paintProgress.setStyle(Paint.Style.STROKE); } public interface OnGestureListener { void onLongClick(); void onClick(); void onLift(); void onOver(); } public void setOnGestureListener(OnGestureListener onGestureListener){ this.onGestureListener = onGestureListener; } private Handler myHandler = new Handler(){ @Override public void handleMessage(Message msg) { if(onGestureListener != null) { startAnim(0, 1-zoom); onGestureListener.onLongClick(); closeMode = true; } } }; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: myHandler.sendEmptyMessageDelayed(0, animTime); downX = event.getX(); downY = event.getY(); break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: float upX = event.getX(); float upY = event.getY(); if(myHandler.hasMessages(0)){ myHandler.removeMessages(0); if (Math.abs(upX - downX) < dp5 && Math.abs(upY - downY) < dp5) { if(onGestureListener != null) onGestureListener.onClick(); } }else if(onGestureListener != null && closeMode){ onGestureListener.onLift(); closeButton(); } break; } return true; } public void closeButton(){ if(closeMode) { closeMode = false; startAnim(1 - zoom, 0); girth = 0; invalidate(); } } private void startAnim(float start, float end){ ValueAnimator va = ValueAnimator.ofFloat(start, end).setDuration(animTime); va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float value = (float) animation.getAnimatedValue(); radius1 = measuredWidth * (zoom + value) / 2; radius2 = measuredWidth * (zoom - value) / 2.5f; invalidate(); } }); va.start(); } public void setMax(int max){ this.max = max; } public void setProgress(float progress){ float ratio = progress/max; girth = 370*ratio; invalidate(); if(ratio >= 1){ if(onGestureListener != null) onGestureListener.onOver(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if(measuredWidth == -1) { measuredWidth = getMeasuredWidth(); // radius1:radius2 = 5:4 radius1 = measuredWidth* zoom /2; radius2 = measuredWidth* zoom /2.5f; //设置绘制大小 边距2.5dp oval = new RectF(); oval.left = dp5/2; oval.top = dp5/2; oval.right = measuredWidth-dp5/2; oval.bottom = measuredWidth-dp5/2; } } @Override protected void onDraw(Canvas canvas) { //绘制外圈圆 radius1代表绘制半径 paint.setColor(colorGray); canvas.drawCircle(measuredWidth/2, measuredWidth/2, radius1, paint); //绘制内圈圆 radius2代表绘制半径 paint.setColor(Color.WHITE); canvas.drawCircle(measuredWidth/2, measuredWidth/2, radius2, paint); Log.d(TAG, "onDraw: "+radius1+" "+radius2); //绘制进度 270表示以圆的270度为起点, 绘制girth长度的弧线 canvas.drawArc(oval, 270, girth, false, paintProgress); } } 使用的也比较简单,没有一个自定义属性,所以实例化控件之后简单的设定一下录制视频的最长时间和具体的监听事件实现就好! final RecordedButton recordedButton = (RecordedButton) findViewById(R.id.btn); recordedButton.setMax(30*1000);//最长录制时间30秒 Timer timer = new Timer();//通过timer来模拟拍摄的进度 recordedButton.setOnGestureListener(new RecordedButton.OnGestureListener() { @Override public void onLongClick() { //长按监听 final long startTime = System.currentTimeMillis(); timer.schedule(new TimerTask() { @Override public void run() { runOnUiThread(new Runnable() { @Override public void run() { long progress = System.currentTimeMillis()-startTime; recordedButton.setProgress(progress);//默认的进度值是0,所以这个需要自定义 } }); } },1000,1000); } @Override public void onClick() { timer.cancel(); } @Override public void onLift() { timer.cancel(); } @Override public void onOver() { timer.cancel(); } }); 如果这里的进度值只是时间的话,其实我们还可以直接在控件里面来实现,将控件的功能封装得更好,于是修改代码如下: private boolean closeMode = false; private long startTime; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: startTime = System.currentTimeMillis(); myHandler.sendEmptyMessageDelayed(0, animTime); downX = event.getX(); downY = event.getY(); break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: startTime = 0; float upX = event.getX(); float upY = event.getY(); if(myHandler.hasMessages(0)){ myHandler.removeMessages(0); if (Math.abs(upX - downX) < dp5 && Math.abs(upY - downY) < dp5) { if(onGestureListener != null) onGestureListener.onClick(); } }else if(onGestureListener != null && closeMode){ onGestureListener.onLift(); closeButton(); } break; } return true; } private Handler myHandler = new Handler(){ @Override public void handleMessage(Message msg) { if(onGestureListener != null) { startAnim(0, 1-zoom); onGestureListener.onLongClick(); closeMode = true; if(!closeMode){ setProgress(System.currentTimeMillis()-startTime); myHandler.sendEmptyMessageDelayed(0, 50); } } } }; public void setProgress(float progress){ float ratio = progress/max; girth = 370*ratio; invalidate(); if(ratio >= 1){ if(onGestureListener != null) onGestureListener.onOver(); } } OK,这个功能就实现了! [学习的源码][Link 1] [SouthEast]: /images/20220617/9286fe2387fd4ea6917b1ef99ebeba27.png [SouthEast 1]: /images/20220617/6fd876d1e7364e3bbc69ba71e58459b0.png [Link 1]: https://github.com/Zhaoss/WeiXinRecordedDemo
还没有评论,来说两句吧...