效果图

什么?非入侵式?这是啥东西?也很简单,就是即拿即用,不去为了某个效果而去重写或者自定义布局。
看到上面的效果图,或许脑海里最先想到的是重写ViewGroup的dispatchDraw方法然后将自定义的ViewGroup在加入到xml中,这确实是一种实现思路。另一种就是我说的这个'非入侵'思路,即将动画View抽出做成一个工具类,动态添加和删除。
思路:
- 自定义AnimationView,重写onDraw方法。
- 添加的顶层ViewGroup中。(顶层View其实就是
DecorView也就是FrameLayout正好将其View覆盖到最上面) - 播放具体动画
addView前需要先拿到目标View到屏幕的具体坐标值,这样才能在准确的位置展示动画
获取顶层View:
Window window = activity.getWindow();
ViewGroup container = (ViewGroup) window.getDecorView();
复制代码
获取目标View对于窗口的坐标
int[] viewXY = new int[2];
targetView.getLocationInWindow(viewXY); //下标0表示x,1表示y=>(x,y)
复制代码
绑定View
Window window = activity.getWindow();
if (window == null || activity.isFinishing()) {
return;
}
ViewGroup container = (ViewGroup) window.getDecorView();
...
if (likeAnimateView == null) {
likeAnimateView = new LikeAnimateView(window.getContext());
int[] viewXY = new int[2];
targetView.getLocationInWindow(viewXY);
int centerX = viewXY[0] + targetView.getWidth() / 2;
int centerY = viewXY[1] + targetView.getHeight() / 2;
likeAnimateView.setTargetXY(centerX, centerY);
ViewGroup.LayoutParams params = container.getLayoutParams();
ViewGroup.LayoutParams animParams = new ViewGroup.LayoutParams(params.width, params.height);
likeAnimateView.setLayoutParams(animParams);
...
container.addView(likeAnimateView);
}
复制代码
获取目标view的
绝对坐标,计算目标view的中心坐标,传入即将绑定的AnimateView,设置AnimateView大小为顶层View的大小,添加view
具体动画实现:
这个动画仔细看其实是一个发散式的效果(由内圈逐渐扩散至外圈的过程)

内圆上任意一点到外圆上任意一点到路径就是这个动画过程
分析完是一个路径的过程那么就需要用到Path和PathMeasure类配合使用
Path设置圆路径
this.mPath.addCircle(x, y, 20, Path.Direction.CW);
复制代码
PathMeasurePath路径点的坐标追踪
this.mPathMeasure.setPath(this.mPath, true);//第二个参数表示是否闭合路径
复制代码
PathMeasure.getPosTan(dis,pos,null)获取Path路径上对应坐标
this.mPathMeasure.getPosTan(discount, point, null);
复制代码
LikeInfo类结构
public static class LikeInfo {
public Bitmap mBitmap;
public int x;
public int y;
public int startX, startY;
public Path path = new Path();
public PathMeasure pathMeasure = new PathMeasure();
public Paint paint = new Paint();
public ValueAnimator valueAnimator = new ValueAnimator();
public int zWidth = 0;
public LikeInfo() {
x = 0;
y = 0;
paint.setColor(Color.RED);
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.STROKE);
}
public void setBitmap(Bitmap bitmap) {
this.mBitmap = BitmapUtil.bitmapScale(bitmap, 60, 60);
}
}
复制代码
x,y表示一张图的坐标,startX,startY开始的xy坐标主要用于计算当前坐标点与开始坐标点距离,可根据勾股定理计算斜边长度a^2 + b^2 = c^2开根号并与总路径做除法可得到一个比例,根据比例可以进行图片的透明度动画控制, 每个LikeInfo类有自己的动画控制类ValueAnimator和画笔Paint,Path和PathMeasure。每个Bitmap需要大小一致,做了图片比例缩放。
内圆部分代码:
this.mPath = new Path();
this.mPath.addCircle(x, y, 20, Path.Direction.CW);
this.mPathMeasure = new PathMeasure();
this.mPathMeasure.setPath(this.mPath, true);
float len = this.mPathMeasure.getLength();
float[] point = new float[2];
int index = 0;
//第一个初始圈
for (int i = 0; i < len; i += (len / LikeAnimationHelper.likesList.size())) {
if (index < LikeAnimationHelper.likesList.size()) {
this.mPathMeasure.getPosTan(i, point, null);
LikeAnimationHelper.LikeInfo likeInfo = LikeAnimationHelper.likesList.get(index);
likeInfo.x = (int) point[0];
likeInfo.y = (int) point[1];
likeInfo.startX = (int) point[0];
likeInfo.startY = (int) point[1];
}
index++;
}
复制代码
内圆部分
this.mPathMeasure.getLength()获取的是路径内所有点坐标的数组长度,point数组用于接收getPosTan获取点具体坐标。i += (len / LikeAnimationHelper.likesList.size())这里主要取根据动画图片list的平均分布坐标,这样可以在内圆中平均的在Canvas中的Bitmap图片绘制到内圆上
内圆绘制效果

外圆部分代码:
this.mPath.reset();
this.mPath.addCircle(x, y, 250, Path.Direction.CW);
this.mPathMeasure.setPath(this.mPath, false);
len = this.mPathMeasure.getLength();
for (int i = 0; i < len; i += (len / LikeAnimationHelper.likesList.size())) {
this.mPathMeasure.getPosTan(i, point, null);
if (index < LikeAnimationHelper.likesList.size()) {
LikeAnimationHelper.LikeInfo likeInfo = LikeAnimationHelper.likesList.get(index);
likeInfo.path.reset();//重置
likeInfo.path.moveTo(likeInfo.x, likeInfo.y);
likeInfo.path.lineTo(point[0], point[1]);
likeInfo.zWidth = LikeAnimationHelper.computeDistance(likeInfo.x, likeInfo.y, point[0], point[1]);
likeInfo.pathMeasure.setPath(likeInfo.path, false);
likeInfo.valueAnimator.setFloatValues(0, likeInfo.pathMeasure.getLength());
likeInfo.valueAnimator.setDuration(LikeAnimationHelper.getRandomInt(200,1000));
likeInfo.valueAnimator.setInterpolator(new DecelerateInterpolator());
likeInfo.valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
float[] pos = new float[2];
likeInfo.pathMeasure.getPosTan(value, pos, null);
likeInfo.x = (int) pos[0];
likeInfo.y = (int) pos[1];
int bW = LikeAnimationHelper.computeDistance(likeInfo.startX, likeInfo.startY, likeInfo.x, likeInfo.y);
//当前路程 / 总路程 = 比例
float fit = (bW * 1.0f) / (likeInfo.zWidth * 1.0f);
//比例 * 255 = 透明度
int alpha = (int) Math.min((fit) * 255, 255);
//设置画笔透明度
likeInfo.paint.setAlpha(255 - alpha);
invalidate();
}
});
}
index++;
}
复制代码
前几行不用多说,和内圆部分代码相似,获取平均分布坐标点,for循环里面给每个动画对象计算距离和设置动画其中
moveTo和lineTo绘制内圆上一点到外圆上一点所形成的路径,zWidth代表其总路径长度(勾股定理可得出斜边长度),将路径添加到PathMeasure中后续在动画回调addUpdateListener中通过getPosTan获取当前路径每个坐标,然后依次将坐标设置到LikeInfo中的x,y上,就完成了整体移动的动画操作。
likeInfo.valueAnimator.setDuration(LikeAnimationHelper.getRandomInt(200,1000))设置setDuration这里做了一个随机执行时间到处理,也就在动画中可以看作为打散效果,若用固定的数值则是一个内圆扩散的效果
外圆及其路径效果图:

问题:如何确定动画全部执行完了?
valueAnimator中有一个addListener方法里面有一个回调onAnimationEnd
likeInfo.valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
endIndex += 1;
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
复制代码
当
endIndex >= LikeAnimationHelper.likesList.size()时表示动画已经执行完毕
private Thread mThread = new Thread() {
@Override
public void run() {
super.run();
while (endIndex < LikeAnimationHelper.likesList.size()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
post(new Runnable() {
@Override
public void run() {
//结束接口回调
iLikeAniationListener.onEnd();
}
});
}
};
复制代码
启动一个线程,每100毫秒检查一次即可
源码地址
LikeAnimationHelper工具类方法startAnimation(Activity activity, View targetView)参数1.activity 2.目标ViewLikeAnimateView动画View



















![[02/27][官改] Simplicity@MIX2 ROM更新-一一网](https://www.proyy.com/wp-content/uploads/2020/02/3168457341.jpg)



![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)