红橙Darren视频笔记 任意控件实现拖动消失爆炸效果
效果
注意 本节内容基于上一篇博客的代码继续编写code
https://blog.csdn.net/u011109881/article/details/112427623
本节知识点
1.如何创建View的截图
2.值动画练习
3.贝塞尔曲线的使用
4.画笔使用 onDraw练习
5.View.getRowX VS View.getX
6.获取状态栏高度
7.插值器使用
8.帧动画练习
步骤分析
1.原理
将原来的View隐藏,拖动的时候利用WindowManager创建一个新View(只是一个截图),拖动的其实只是截图,我们可以将截图放在Decor View的层级(这里是我的猜测,视频里面说的是放在WindowManager上 但我不理解WindowManager是哪一层),这样就能在状态栏上面拖动
2.按下的时候将原先的View隐藏
2.1创建一个截图
2.2将创建的截图保存到DragView并让其在onDraw绘制 注意保存截图的显示位置
2.3将创建的截图和DragView一起显示到界面(在onDraw绘制)
3.移动的时候 不停绘制贝塞尔曲线以及截图
3.1 遇到问题originView的位置不对,手指点的位置不对
mOriginView.getX(), mOriginView.getY()的方式不对 这个是相对于父布局的位置 我们需要相对屏幕的位置,另外需要确保固定圆在mOriginView的中心就要加上view宽高的一半,同时还要减去图状态栏高度
4.对手指抬起做监听
4.1如果 抬起时距离不大,view回弹
4.2计算回弹轨迹,添加回弹动画
4.3利用插值器,回弹到原始位置的时候添加抖动效果
4.4去除创建的截图并显示出原先的View
4.5如果View拖动很远 则触发消失的动作。将原先的View设置为Gone,隐藏拖动的截图,播放爆炸的帧动画,播放完毕释放资源
4.6通知调用者,View被删除成功
大部分代码
工具类
class Utils {//2.1 创建截图public static Bitmap getBitmapByView(View view) {view.buildDrawingCache();Bitmap bitmap = view.getDrawingCache();return bitmap;}public static float dp2px(float dp, Context context) {return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());}//获取状态栏高度public static float getStatusBarHeight(Context context) {//获取status_bar_height资源的IDint resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");if (resourceId > 0) {return context.getResources().getDimensionPixelSize(resourceId);}//没有获取到 取0return 0;}//按照百分比在由point1 point2组成的线段上取点public static PointF getPointByPercent(PointF point1, PointF point2, float percent) {return new PointF(evaluateValue(percent, point1.x, point2.x), evaluateValue(percent, point1.y, point2.y));}//Number是int float等基本数字类型的父类public static float evaluateValue(float percent, Number start, Number end) {return start.floatValue() + (end.floatValue() - start.floatValue())* percent;}
}
Activity
public class MainActivity extends AppCompatActivity {private final String TAG = "MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);TextView textView = findViewById(R.id.textView);DragBoomView.attachToView(textView, view -> {//4.6通知到调用者,View被删除成功Toast.makeText(MainActivity.this, "原先的View1被删除", Toast.LENGTH_SHORT).show();view.setVisibility(View.GONE);});TextView textView2 = findViewById(R.id.textView2);DragBoomView.attachToView(textView2, view -> {Toast.makeText(MainActivity.this, "原先的View2被删除", Toast.LENGTH_SHORT).show();view.setVisibility(View.GONE);});TextView textView3 = findViewById(R.id.textView3);DragBoomView.attachToView(textView3, view -> {Toast.makeText(MainActivity.this, "原先的View3被删除", Toast.LENGTH_SHORT).show();view.setVisibility(View.GONE);});}
}
自定义View(包含截图+贝塞尔曲线)
class DragBoomView extends View {private static final String TAG = "DragBoomView";private PointF mFixPoint;private PointF mFingerPoint;private Paint mPaint;private float mFixPointInitRadius = 10;//固定圆初始半径private float mMinFixPointRadius = 5;//固定圆最小半径 如果比这个更小 不进行绘制private float mFixPointChangedRadius;//挪动手指后的固定圆半径private float mFingerPointRadius = 10;private DragBoomViewTouchListener mDragBoomViewTouchListener;private Bitmap mCaptureView;//截图public DragBoomView(Context context) {this(context, null);}public DragBoomView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public DragBoomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initPaint();mFixPointInitRadius = Utils.dp2px(mFixPointInitRadius, context);mFingerPointRadius = Utils.dp2px(mFingerPointRadius, context);mMinFixPointRadius = Utils.dp2px(mMinFixPointRadius, context);}public static void attachToView(View textView, DragBoomViewTouchListener.DragViewDisappearListener disappearListener) {textView.setOnTouchListener(new DragBoomViewTouchListener(textView, disappearListener));}private void initPaint() {mPaint = new Paint();mPaint.setDither(true);mPaint.setAntiAlias(true);mPaint.setColor(Color.RED);}@Overrideprotected void onDraw(Canvas canvas) {if (mFingerPoint == null || mFixPoint == null) {return;}//绘制固定圆if (isNeedShowBezier()) {//当手指离固定圆太远 不绘制固定圆canvas.drawCircle(mFixPoint.x, mFixPoint.y, mFixPointChangedRadius, mPaint);canvas.drawPath(getBezierPath(), mPaint);}//绘制跟随手指的圆canvas.drawCircle(mFingerPoint.x, mFingerPoint.y, mFingerPointRadius, mPaint);//2.3将创建的截图和DragView一起显示到界面(在onDraw绘制)if (mCaptureView != null) {canvas.drawBitmap(mCaptureView, mFingerPoint.x - mCaptureView.getWidth() / 2, mFingerPoint.y - mCaptureView.getHeight() / 2, mPaint);}}//计算两点之间的距离double distanceOfPoints(PointF point1, PointF point2) {//勾股定理return Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));}// @Override
// public boolean onTouchEvent(MotionEvent event) {
// switch (event.getAction()) {
// case MotionEvent.ACTION_DOWN:
// mFixPoint = new PointF();
// mFingerPoint = new PointF();
// mFixPoint.x = event.getX();
// mFixPoint.y = event.getY();
// mFingerPoint.x = event.getX();
// mFingerPoint.y = event.getY();
// case MotionEvent.ACTION_MOVE:
// mFingerPoint.x = event.getX();
// mFingerPoint.y = event.getY();
// break;
// case MotionEvent.ACTION_UP:
// break;
// }
// //根据手指的移动 计算两点之间的距离 根据距离改变mFixPoint的半径
// mFixPointChangedRadius = calculateFixPointRadius(distanceOfPoints(mFingerPoint, mFixPoint));
// invalidate();
// return true;
// }private float calculateFixPointRadius(double distanceOfPoints) {return mFixPointInitRadius - (float) distanceOfPoints / 20f;//除数(20f)越大,代表半径随距离变化的程度越小}private Path getBezierPath() {Path bezierPath = new Path();//计算四点坐标//先计算∠a ∠a = arctan((r1.y-r2.y)/(r1.x-r2.x))double angleA = Math.atan((mFixPoint.y - mFingerPoint.y) / (mFixPoint.x - mFingerPoint.x));//计算x y以及 x' y'float x = (float) (Math.sin(angleA) * mFixPointChangedRadius);float y = (float) (Math.cos(angleA) * mFixPointChangedRadius);float x2 = (float) (Math.sin(angleA) * mFingerPointRadius);float y2 = (float) (Math.cos(angleA) * mFingerPointRadius);//四个点坐标分别为 A1 B1 A2 B2 他们坐标表示为float A1x = mFixPoint.x + x;float A1y = mFixPoint.y - y;float B1x = mFixPoint.x - x;float B1y = mFixPoint.y + y;float A2x = mFingerPoint.x + x2;float A2y = mFingerPoint.y - y2;float B2x = mFingerPoint.x - x2;float B2y = mFingerPoint.y + y2;float controlPint1x = (mFixPoint.x + mFingerPoint.x) * 0.5f;float controlPint1y = (mFixPoint.y + mFingerPoint.y) * 0.5f;//绘制路径为A1 A2 B2 B1bezierPath.moveTo(A1x, A1y);bezierPath.quadTo(controlPint1x, controlPint1y, A2x, A2y);bezierPath.lineTo(B2x, B2y);bezierPath.quadTo(controlPint1x, controlPint1y, B1x, B1y);bezierPath.close();return bezierPath;}public void setDragViewTouchListener(DragBoomViewTouchListener dragBoomViewTouchListener) {mDragBoomViewTouchListener = dragBoomViewTouchListener;}public void setCaptureView(Bitmap bitmap) {this.mCaptureView = bitmap;}public void updatePosition(float rawX, float rawY) {if (mFingerPoint == null) {mFingerPoint = new PointF();}mFingerPoint.x = rawX;mFingerPoint.y = rawY;mFixPointChangedRadius = calculateFixPointRadius(distanceOfPoints(mFingerPoint, mFixPoint));invalidate();}//2.2将创建的截图保存到DragView并让其在onDraw绘制 注意保存截图的显示位置public void initPoints(float pointX, float pointY) {mFixPoint = new PointF(pointX, pointY);mFingerPoint = new PointF(pointX, pointY);invalidate();}private boolean isNeedShowBezier() {return mFixPointChangedRadius > mMinFixPointRadius;}public void dealWithActionUp() {if (this.isNeedShowBezier()) { //4.1如果 抬起时距离不大,view回弹playBackAnimate();} else {//4.5如果View拖动很远 则触发消失的动作。将原先的View设置为Gone,隐藏拖动的截图,播放爆炸的帧动画,播放完毕释放资源if (mDragBoomViewTouchListener != null) {//4.5如果View拖动很远 则触发消失的动作。将原先的View设置为Gone,隐藏拖动的截图,播放爆炸的帧动画,播放完毕释放资源mDragBoomViewTouchListener.dismiss(mFingerPoint);}}}private void playBackAnimate() {//4.2计算回弹轨迹,添加回弹动画//ValueAnimator 值变化的动画 getAnimatedValue由0变化到1ValueAnimator animator = ObjectAnimator.ofFloat(1);animator.setDuration(250);final PointF start = new PointF(mFingerPoint.x, mFingerPoint.y);final PointF end = new PointF(mFixPoint.x, mFixPoint.y);animator.addUpdateListener(animation -> {float percent = (float) animation.getAnimatedValue();// 0 - 1PointF pointF = Utils.getPointByPercent(start, end, percent);// 用代码更新拖拽点updatePosition(pointF.x, pointF.y);});// 4.3利用插值器,回弹到原始位置的时候添加抖动效果// 设置一个差值器 在结束的时候有一个弹动效果animator.setInterpolator(new OvershootInterpolator(3f));//3f表示晃动强度较大 数值越大效果越强animator.start();animator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {if (mDragBoomViewTouchListener != null) {//4.4去除创建的截图并显示出原先的View// 还要通知 TouchListener 移除当前View 然后显示静态的 ViewmDragBoomViewTouchListener.reset();}}});}
}
Listener
class DragBoomViewTouchListener implements View.OnTouchListener {private DragBoomView mDragView;private WindowManager mWindowManager;private Context mContext;private View mOriginView;private WindowManager.LayoutParams mParams;// 爆炸帧动画private FrameLayout mBombFrame;private ImageView mBombImage;private DragViewDisappearListener mDisappearListener;public DragBoomViewTouchListener(View mOriginView, DragViewDisappearListener disappearListener) {this.mOriginView = mOriginView;this.mContext = mOriginView.getContext();mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);mDragView = new DragBoomView(mContext);mDragView.setDragViewTouchListener(this);mParams = new WindowManager.LayoutParams();// 背景要透明mParams.format = PixelFormat.TRANSPARENT;//爆炸动画初始化mBombFrame = new FrameLayout(mContext);mBombImage = new ImageView(mContext);mBombImage.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));mBombFrame.addView(mBombImage);this.mDisappearListener = disappearListener;}@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:int[] location = new int[2];mOriginView.getLocationOnScreen(location);//3.1 遇到问题originView的位置不对,手指点的位置不对//location是view左上角相对屏幕的位置,需要确保固定圆在mOriginView的中心就要加上view宽高的一半,同时还要减去图状态栏高度mDragView.initPoints(location[0] + (mOriginView.getMeasuredWidth() / 2), location[1] + (mOriginView.getMeasuredHeight() / 2) - Utils.getStatusBarHeight(mContext));Bitmap bitmap = Utils.getBitmapByView(mOriginView);mDragView.setCaptureView(bitmap);mWindowManager.addView(mDragView, mParams);//2.按下的时候将原先的View隐藏mOriginView.setVisibility(View.INVISIBLE);case MotionEvent.ACTION_MOVE://3.移动的时候 不停绘制贝塞尔曲线以及截图//这里传的点和down的点不统一 我认为不太好mDragView.updatePosition(event.getRawX(), event.getRawY() - Utils.getStatusBarHeight(mContext));break;case MotionEvent.ACTION_UP://4.对手指抬起做监听mDragView.dealWithActionUp();break;}return true;}public void reset() {// 把创建的贝塞尔曲线以及截图删除mWindowManager.removeView(mDragView);// 把原来的View显示mOriginView.setVisibility(View.VISIBLE);}//帧动画public void dismiss(PointF pointF) {// 移除截图的ViewmWindowManager.removeView(mDragView);// 要在 mWindowManager 添加一个爆炸动画mWindowManager.addView(mBombFrame, mParams);mBombImage.setBackgroundResource(R.drawable.anim_bubble_pop);AnimationDrawable drawable = (AnimationDrawable) mBombImage.getBackground();mBombImage.setX(pointF.x - drawable.getIntrinsicWidth() / 2);mBombImage.setY(pointF.y - drawable.getIntrinsicHeight() / 2);drawable.start();// 等它执行完之后我要移除掉这个 爆炸动画也就是 mBombFramemBombImage.postDelayed(new Runnable() {@Overridepublic void run() {mWindowManager.removeView(mBombFrame);// 通知一下外面该消失if (mDisappearListener != null) {mDisappearListener.viewDismiss(mOriginView);}}}, getAnimationDrawableTime(drawable));}private long getAnimationDrawableTime(AnimationDrawable drawable) {int numberOfFrames = drawable.getNumberOfFrames();long time = 0;for (int i = 0; i < numberOfFrames; i++) {time += drawable.getDuration(i);}return time;}public interface DragViewDisappearListener {void viewDismiss(View view);}
}
帧动画
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"android:oneshot="true"><itemandroid:drawable="@drawable/pop1"android:duration="100" /><itemandroid:drawable="@drawable/pop2"android:duration="100" /><itemandroid:drawable="@drawable/pop3"android:duration="100" /><itemandroid:drawable="@drawable/pop4"android:duration="100" /><itemandroid:drawable="@drawable/pop5"android:duration="100" /></animation-list>
后记
1.原视频中将原先的view传递到自定义View并给他注册TouchListener,事实上这样有点问题,因为这样该View不再能注册其他TouchListener,如果在其他地方注册,就会导致拖动消失的功能消失。不过因为既然想要拖动该View,应该也不需要在其他地方注册TouchListener了吧?
2.在拖动的时候发现状态栏变成黑色,如下图。事实上,我不清楚创建的截图放到了什么位置,因为我使用Layout Inspector的时候没有看到创建的截图View在哪里
但是我想我们应该可以取得DecorView 因为DecorView本身是一个FrameLayout,我们可以将截图放在DecorView的第一个的位置(index从0开始计算),我们可以取得Decor View的引用 保存原来的View的引用,先删除掉原先的所有子view,然后重新按照顺序添加。当然,这只是一个思路,没有验证。
3.DragBoomViewTouchListener 和DragBoomView 职责不清,DragBoomViewTouchListener 明明是个listener 但是里面包含太多的功能,MainActivity普通的View通过DragBoomView与DragBoomViewTouchListener 建立联系,感觉耦合性较高,可以把更多的功能放到DragBoomView 中。
4.回弹动画应该可以使用translationX和translationY动画结合代替
private void playBackAnimate() {ObjectAnimator translationX = ObjectAnimator.ofFloat(this, "translationX", mFingerPoint.x - mCaptureView.getWidth() / 2, mFixPoint.x - mCaptureView.getWidth() / 2);ObjectAnimator translationY = ObjectAnimator.ofFloat(this, "translationY", mFingerPoint.y - mCaptureView.getHeight() / 2, mFixPoint.y - mCaptureView.getHeight() / 2);AnimatorSet animatorSet = new AnimatorSet();animatorSet.playTogether(translationX, translationY);animatorSet.setDuration(1000 * 2);animatorSet.start();animatorSet.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {if (mDragBoomViewTouchListener != null) {mDragBoomViewTouchListener.reset();}}});}
但是实际跑的时候发现点似乎不对,具体原因还没有找到,也不想纠结太多。但是我认为可能的原因是,贝塞尔曲线绘制的原始点和拖动点都位于View的中心,而位移动画计算的时候我们使用View的左上角进行计算,因此可能存在偏差。在本篇中,最容易出问题的是各种坐标。事实上,对于本节位置相关内容,我们需要考虑以下4点
a 是否需要加上状态栏高度
b 绘制点或者图形的时候是否需要考虑View的宽高
c 进行位移动画时应该以图像的左上角为中心进行计算。
事实上本节中
mDragView.initPoints(location[0] + (mOriginView.getMeasuredWidth() / 2), location[1] + (mOriginView.getMeasuredHeight() / 2) - Utils.getStatusBarHeight(mContext));
mDragView.updatePosition(event.getRawX(), event.getRawY() - Utils.getStatusBarHeight(mContext));
两种不同的点的计算方式很容易让人产生误会。我觉得比较好的方式是记录下手指点击的坐标与View的中心相差的差值,统一在dragView中进行处理,而不是通过外部DragBoomViewTouchListener的不同标准传到DragBoomView再对坐标进行二次纠正。
5.小bug:
如图 拖动的点强制表示为View的正中心,使用第4条中的方式有可能规避这个问题。
6.在下一节视频中,使用到了TypeEvaluator,我认为在本节中的回弹动画也可以使用TypeEvaluator代替,如下所示 而不是写两个函数getPointByPercent和evaluateValue,这样写不够通用
private void playBackAnimate() {//4.2计算回弹轨迹,添加回弹动画final PointF start = new PointF(mFingerPoint.x, mFingerPoint.y);final PointF end = new PointF(mFixPoint.x, mFixPoint.y);BezierTypeEvaluator evaluator = new BezierTypeEvaluator();ValueAnimator animator = ObjectAnimator.ofObject(evaluator,start,end);animator.setDuration(250);animator.addUpdateListener(animation -> {// float percent = (float) animation.getAnimatedValue();// 0 - 1
// PointF pointF = Utils.getPointByPercent(start, end, percent);PointF pointF = (PointF) animation.getAnimatedValue();// 用代码更新拖拽点updatePosition(pointF.x, pointF.y);});//省略其他代码}static class BezierTypeEvaluator implements TypeEvaluator<PointF>{@Overridepublic PointF evaluate(float fraction, PointF startValue, PointF endValue) {float pointX = startValue.x + (endValue.x - startValue.x) * fraction;float pointY = startValue.y + (endValue.y - startValue.y) * fraction;return new PointF(pointX, pointY);}}
code:
https://github.com/caihuijian/learn_darren_android.git
红橙Darren视频笔记 任意控件实现拖动消失爆炸效果相关推荐
- 红橙Darren视频笔记 一个控件显示两种颜色的文字 画笔的使用
需求分析 1.一行文字显示两种颜色 2.颜色变化可以从右到左或者从左到右 3.能够随着view pager切换 思路: a.继承View:需要重写onMeasure onDraw方法 b.继承Text ...
- 红橙Darren视频笔记 贝塞尔曲线实现消息拖拽粘性效果 画笔练习
效果图 要实现这样的效果 我们要先知道那段弧线是如何绘制的,实际上那段弧线就是贝塞尔曲线 一. 什么是贝塞尔曲线 https://www.jianshu.com/p/8f82db9556d2 上面有个 ...
- 红橙Darren视频笔记 ViewGroup事件分发分析 基于API27
本节目标,通过案例,先看程序运行结果,然后跟踪源码,理解为什么会有这样的输出,继而理解view group的分发机制,感觉和证明题很像呢. 考虑以下程序的运行结果: case1: public cla ...
- 红橙Darren视频笔记 UML图简介
整体架构复制自红橙原视频的课堂笔记 因为他这一课没有博客,所以没有转载链接,CSDN没有转载地址是无法作为转载类型的文章发表的,暂时标记为原创 参考链接 https://blog.csdn.net/r ...
- 红橙Darren视频笔记 代理模式 动态代理和静态代理
红橙Darren视频笔记 代理模式 动态代理和静态代理(Android API 25) 关于代理模式我之前有过相关的介绍: https://blog.csdn.net/u011109881/artic ...
- 红橙Darren视频笔记 类加载机制(API28) 自己写个热修复 查看源码网站
第一部分 类加载机制 一个Activity是如何被Android虚拟机找到的? 在之前的文章 红橙Darren视频笔记 自定义View总集篇(https://blog.csdn.net/u011109 ...
- 红橙Darren视频笔记 利用阿里巴巴AndFix进行热修复
注意 由于AndFix在2017年左右就停止更新了,在最新版本的apk上遇到很多问题,我最终也没有成功进行热修复.本节主要是学习热修复的原理 在上一篇 红橙Darren视频笔记 自己捕获异常并保存到本 ...
- 红橙Darren视频笔记 Behavior的工作原理源码分析
主要coordinatorlayout的代码来自coordinatorlayout-1.0.0-sources.jar 本文从源码介绍 CoordinatorLayout 的 behavior 怎么工 ...
- 红橙Darren视频笔记 仿QQ侧滑效果
这一篇没有什么新的内容 就是改写 红橙Darren视频笔记 仿酷狗侧滑效果 的侧滑的效果 1.去掉淡入淡出效果 2.加上黑色模板效果 效果: 去掉淡入淡出效果很简单 就是注释掉onScrollChan ...
最新文章
- 数据库开发个人总结(ADO.NET小结)
- 非程序员如何使用 Git——版本控制你的生活
- Centos 7 64位 minimal 最小化安装的系统中静默安装oracle 11g r2(无图形化安装)
- flask-bootstrap-高亮-下划线-删除线-加粗-斜体
- Leetcode题库 125.验证回文串(双指针 C实现)
- sublime text3c语言编译运行,c – Sublime text 3 – 编译程序并在终端中运行
- 作者:陈兴鹏(1963-),男,兰州大学资源环境学院教授、博士生导师。
- 【C语言笔记进阶篇】第二章:字符串函数和内存函数
- netty心跳过程中 发送消息失败_netty心跳机制和断线重连(四)
- 车速与档位匹配关系_档位与速度匹配法则 每个档位的速度范围
- springboot application.properties server.port配置小问题
- 【clickhouse】ClickHouse官方中文文档 阅读笔记
- kubenetes 1.4安装kube-UI
- Javascript第四章函数function也是数据类型第六课
- presto 使用 部署_探秘Presto+Alluxio高效云端SQL查询
- iperf命令linux,Linux iperf 用法介绍
- 推荐10款社群运营必备工具
- 随笔记录——numpy4(伪随机数生成)
- ROS从入门到精通0-2:Win10+Ubuntu双系统安装、配置、卸载保姆级图文教程
- grunt教程--初涉grunt