效果
知识点
位移动画 随机数 估值器(TypeEvaluator )使用
思路
1.在底部中心位置初始化图片
1.1用上透明度变化和scale变化的动画
2.计算二阶贝塞尔曲线
3.添加透明度变化动画,飘到顶部时颜色最淡
4.优化
4.1加一些随机的插值器
4.2动画结束时将添加的View删除
大部分代码
1 自定义Layout
class LikesLayout extends RelativeLayout {
private static final String TAG = "LikesLayout";
//星星的图片宽高
private int mDrawableWidth = 0;
private int mDrawableHeight = 0;
// 用于产生随机数
private final Random mRandom;
// 存储图片资源id
private final int[] mImageResId;
//整个layout宽高
private int mWidth;
private int mHeight;
private final Interpolator[] mInterpolator;
public LikesLayout(Context context) {
this(context, null);
}
public LikesLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LikesLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mRandom = new Random();
// 初始化资源id
mImageResId = new int[]{R.drawable.star_blue, R.drawable.star_green, R.drawable.star_red, R.drawable.star_blue_bright, R.drawable.star_orange_light};
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.star_blue);
//假设所有星星的大小相等(如果不等要分别计算)
if (drawable != null) {
mDrawableWidth = drawable.getIntrinsicWidth();
mDrawableHeight = drawable.getIntrinsicHeight();
}
mInterpolator = new Interpolator[]{new AccelerateDecelerateInterpolator(), new AccelerateInterpolator(),
new DecelerateInterpolator(), new LinearInterpolator()};
}
// 获取控件的宽高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
}
public void addLikes() {
for (int i = 0; i < 30; i++) {
addLike();
}
}
// 1.在底部中心位置初始化图片 用上透明度变化和scale变化的动画
private void addLike() {
// 添加一个ImageView在底部
final ImageView likeIv = new ImageView(getContext());
// 给一个图片资源(随机) 这里视频有点问题 比如 mRandom.nextInt(5);取值可能是是[0,5)的整数,视频里面写道 mRandom.nextInt(mImageRes.length - 1) 其实-1是不必要的
int imageIndex = mRandom.nextInt(mImageResId.length);
likeIv.setImageResource(mImageResId[imageIndex]);
// 怎么添加到底部中心?利用RelativeLayout的LayoutParams
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(ALIGN_PARENT_BOTTOM);
params.addRule(CENTER_HORIZONTAL);
likeIv.setLayoutParams(params);
addView(likeIv);
AnimatorSet animator = getAnimator(likeIv);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// 4.2动画结束时将添加的View删除
removeView(likeIv);
}
});
animator.start();
}
private AnimatorSet getAnimator(ImageView likeIv) {
// 入场动画 添加的效果:有放大和透明度变化 (属性动画)
// 1.1用上透明度变化和scale变化的动画
AnimatorSet innerAnimator = new AnimatorSet();
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(likeIv, "alpha", 0.3f, 1.0f);
ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(likeIv, "scaleX", 0.3f, 1.0f);
ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(likeIv, "scaleY", 0.3f, 1.0f);
innerAnimator.playTogether(alphaAnimator, scaleXAnimator, scaleYAnimator);
innerAnimator.setDuration(350);
// 运行的路径动画 playSequentially 按循序执行
AnimatorSet allAnimatorSet = new AnimatorSet();
allAnimatorSet.playSequentially(innerAnimator, getBezierAnimator(likeIv));// 2.计算二阶贝塞尔曲线 本节难点
return allAnimatorSet;
}
private Animator getBezierAnimator(ImageView likeIv) {
//计算三阶贝塞尔曲线中的起点 控制点1 控制点2 终点
//起始点在屏幕底部中间位置
//控制点1取下半屏幕的随机点
//控制点2取上半屏幕的随机点
//起始点在屏幕顶部中间位置
//所有动画计算的基准是图片的左上角
PointF statPoint = new PointF(mWidth / 2 - mDrawableWidth / 2, mHeight - mDrawableHeight);
PointF controlPoint1 = new PointF(mRandom.nextInt(mWidth) - mDrawableWidth, mRandom.nextInt(mHeight / 2) + mHeight / 2);
PointF controlPoint2 = new PointF(mRandom.nextInt(mWidth) - mDrawableWidth, mRandom.nextInt(mHeight / 2));
PointF endPoint = new PointF(mRandom.nextInt(mWidth - mDrawableWidth), 0);
BezierTypeEvaluator evaluator = new BezierTypeEvaluator(controlPoint1, controlPoint2);
ValueAnimator bezierAnimator = ObjectAnimator.ofObject(evaluator, statPoint, endPoint);
// 4.1加一些随机的插值器
bezierAnimator.setInterpolator(mInterpolator[mRandom.nextInt(mInterpolator.length)]);
bezierAnimator.addUpdateListener(animation -> {
PointF pointF = (PointF) animation.getAnimatedValue();
likeIv.setX(pointF.x);
likeIv.setY(pointF.y);
// 3.添加透明度变化动画,飘到顶部时颜色最淡
float t = animation.getAnimatedFraction();
likeIv.setAlpha(1 - t);
});
bezierAnimator.setDuration(3000);
return bezierAnimator;
}
}
2 自定义估值器
这块如果不熟悉 可以先看一下上一篇博客 https://blog.csdn.net/u011109881/article/details/112549820
后记的最后一条,那里的估值器比较简单 是线性的,容易理解。
class BezierTypeEvaluator implements TypeEvaluator<PointF> {
private final PointF mControlPoint1;
private final PointF mControlPoint2;
public BezierTypeEvaluator(PointF controlPoint1, PointF controlPoint2) {
mControlPoint1 = controlPoint1;
mControlPoint2 = controlPoint2;
}
/**
* This function returns the result of linearly interpolating the start and end values, with
* <code>fraction</code> representing the proportion between the start and end values. The
* calculation is a simple parametric calculation:
* 注意下面的这段描述 计算结果result = x0 + fraction * (x1 - x0)
* 其中x0代表起始值 x1代表终点值 t代表比值
* <code>result = x0 + fraction * (x1 - x0)</code>,
* where <code>x0</code> is <code>startPoint</code>, <code>x1</code> is <code>endPoint</code>,
* and <code>fraction</code> is <code>fraction</code>.
*
* @param fraction The fraction from the starting to the ending values
* 从起点到终点的比值
* @param startPoint The start value.
* 起始点的值
* @param endPoint The end value.
* 终点的值
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
* 此函数的意义是计算某个比值fraction时 中间点的状态(位置) 其中fraction的区间为[0,1] 比如fraction也可以理解为百分比,为0.5表示一半的意思
* <p>
* <p>
* 以上的解释为通用解释
* 对于求三阶贝塞尔曲线的本方法而言
* 该方法用于计算在特定 起点,终点,控制点1,控制点2时,fraction从0变化到1,返回的PointF连接起来将构成三阶贝塞尔曲线
* 至于计算方法 直接套用百度百科的三阶贝塞尔曲线的公式即可 fraction即时百科中的t
*/
@Override
public PointF evaluate(float fraction, PointF startPoint, PointF endPoint) {
PointF pointOnBezier = new PointF();
pointOnBezier.x = (float) (Math.pow((1 - fraction), 3) * startPoint.x + 3 * mControlPoint1.x * fraction * Math.pow(1 - fraction, 2) + 3 * mControlPoint2.x * fraction * fraction * (1 - fraction) + endPoint.x * Math.pow(fraction, 3));
pointOnBezier.y = (float) (Math.pow((1 - fraction), 3) * startPoint.y + 3 * mControlPoint1.y * fraction * Math.pow(1 - fraction, 2) + 3 * mControlPoint2.y * fraction * fraction * (1 - fraction) + endPoint.y * Math.pow(fraction, 3));
return pointOnBezier;
}
}
3 Activity
public class MainActivity extends AppCompatActivity {
private LikesLayout mLikesLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLikesLayout = findViewById(R.id.likeLayout);
}
public void like(View view) {
mLikesLayout.addLikes();
}
}
4 布局
<?xml version="1.0" encoding="utf-8"?>
<com.example.likesview.LikesLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/likeLayout"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/like"
android:onClick="like"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点赞!" />
</com.example.likesview.LikesLayout>
5 星星的布局(红色为例 是使用Android studio创建的)
<vector android:height="48dp"
android:tint="#FF0000"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="48dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="@android:color/white"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" />
</vector>
后记:
视频中的随机数有问题,图片我们只看到两种颜色 视频有点问题 比如 mRandom.nextInt(5);取值可能是是[0,5)的整数,视频里面写道 mRandom.nextInt(mImageRes.length - 1) 其实-1是不必要的,正因为视频-1了 所以还有一种颜色没有出现