前言
平时喜欢看今日头条,上面的财经、科技和NBA栏目都很喜欢,无意中发现他的点赞动画还不错,一下子就吸引到了我。遂即想要不自己实现一下。
最终效果对比如下:
头条:
仿写效果:
一、导读
【自定义view仿写今日头条点赞动画!】学习的过程中发现,每个知识点都是一个小小的体系。比如Glide源码解析,我看到有作者写了10篇文章一个系列来解析(Glide源码解析 https://www.jianshu.com/nb/45...);又比如自定义view,扔物线凯哥也是从三个方面(绘制、布局、动画)11篇文章来叙述,Carson_Ho也是写了一个系列来描述;所以掌握一个知识点里面的知识体系还是需要下一些功夫的。
二、效果分析
- 1 点击一次会撒出五个随机表情和点击音效;
- 2 连续点击会连续撒出表情并播放音效;
- 3 长按会一直撒;
- 4 连续撒时会出现次数和标语(0-20 鼓励,20-40加油,>40太棒了);
ivThumbBottom.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
lastDownTime = System.currentTimeMillis();
//获取到 x y的坐标来确定动画撒表情的起点
x = (int) event.getRawX();
y = (int) event.getRawY();
Log.i("aaa", (System.currentTimeMillis() - lastDownTime) + "");
handler.postDelayed(mLongPressed, 100);
}
if (event.getAction() == MotionEvent.ACTION_UP) {
Log.i("aaa", (System.currentTimeMillis() - lastDownTime) + "");
if (System.currentTimeMillis() - lastDownTime < 100) {//判断为单击事件
articleThumbRl.setVisibility(View.VISIBLE);
articleThumbRl.setThumb(true, x, y, articleThumbRl);
handler.removeCallbacks(mLongPressed);
} else {//判断为长按事件后松开
handler.removeCallbacks(mLongPressed);
}
}
return true;
}
});
其中通过如下方式实现,长按循环撒表情。
final Runnable mLongPressed = new Runnable() {
@Override
public void run() {
articleThumbRl.setVisibility(View.VISIBLE);
articleThumbRl.setThumb(x, y, articleThumbRl);
handler.postDelayed(mLongPressed, 100);
}
};
3.2 setThumb方法 处理点击事件
public void setThumb(float x, float y, ArticleRl articleThumbRl) {
//这里处理音效播放
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.seekTo(0);
//重复点击时,从头开始播放
} else {
mMediaPlayer.start();
}
if (System.currentTimeMillis() - lastClickTime > 800) {//单次点击
addThumbImage(mContext, x, y, this);
lastClickTime = System.currentTimeMillis();
for (int i = getChildCount() - 5;
i < getChildCount();
i++) {
if (getChildAt(i) instanceof ThumbEmoji) {
((ThumbEmoji) getChildAt(i)).setThumb(true, articleThumbRl);
}
}
currentNumber = 0;
if (thumbNumber != null) {
removeView(thumbNumber);
thumbNumber = null;
}
} else {//连续点击
lastClickTime = System.currentTimeMillis();
Log.i(TAG, "当前动画化正在执行");
addThumbImage(mContext, x, y, this);
for (int i = getChildCount() - 5;
i < getChildCount();
i++) {
if (getChildAt(i) instanceof ThumbEmoji) {
((ThumbEmoji) getChildAt(i)).setThumb(true, articleThumbRl);
}
}
currentNumber++;
//这里添加数字连击view
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
layoutParams.setMargins(600, (int) (y) - 300, 0, 150);
if (thumbNumber == null) {
thumbNumber = new ThumbNumber(mContext);
addView(thumbNumber, layoutParams);
//第二个参数 让数字连击始终保持在最上层
}
thumbNumber.setNumber(currentNumber);
}
}
其中,数字连击view中的数字有一个颜色渐变和描边效果,颜色渐变用
LinearGradient
(扔物线课程里面有),描边用重叠绘制方式。textPaint = new Paint();
textPaint.setTextSize(TEXT_SIZE);
textPaint.setTextAlign(Paint.Align.LEFT);
textPaint.setStrokeWidth(STROKE_WIDTH);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
//这里为了做成上面和下面颜色各一半
LinearGradient mLinearGradient = new LinearGradient(0, 0, 0, 90f,
new int[]{0xFFFF9641, 0xFFFF9641, 0xFFFF9641, 0xFFFF9641, 0xFFff0000, 0xFFff0000},
null, Shader.TileMode.CLAMP);
textPaint.setShader(mLinearGradient);
//描边画笔
textPaintStroke = new Paint();
textPaintStroke.setColor(Color.BLACK);
textPaintStroke.setTextSize(TEXT_SIZE);
textPaintStroke.setTextAlign(Paint.Align.LEFT);
textPaintStroke.setStrokeWidth(4);
textPaintStroke.setStyle(Paint.Style.STROKE);
textPaintStroke.setTypeface(Typeface.DEFAULT_BOLD);
3.3 添加表情的自定义view ThumbEmoji
private void addThumbImage(Context context, float x, float y, ThumbEmoji.AnimatorListener animatorListener) {
List list = new ArrayList<>();
for (int i = 0;
i < 8;
i++) {
list.add(i);
}
Collections.shuffle(list);
//打乱顺序
for (int i = 0;
i < 5;
i++) {
LayoutParams layoutParams = new LayoutParams(100, 100);
layoutParams.setMargins((int) x, (int) y - 50, 0, 0);
ThumbEmoji articleThumb = new ThumbEmoji(context);
articleThumb.setEmojiType(list.get(i));
articleThumb.setmAnimatorListener(animatorListener);
if (getChildCount() > 1)
this.addView(articleThumb, getChildCount() - 1, layoutParams);
else {
this.addView(articleThumb, layoutParams);
}
}
}
其中这里的
addview
方法给他设置index
为 childcount-1
后,就可以让它保持在数字连击view的下方,但是我设置成1会出现bug,的原因我还得再去看看。if (getChildCount() > 1)
this.addView(articleThumb, getChildCount() - 1, layoutParams);
else {
this.addView(articleThumb, layoutParams);
}
3.4 撒花效果的动画(也就是抛物线动画)的实现 抛物线动画 分为上升和下降两部分, 上升时,x轴匀速左移或右移,y轴减速向上,表情图片宽高
从0变到100
;下降时,x变为1.2倍x
,高度变为最高处的0.8,透明度在最后1/8时间段里从1变为0
。private void showThumbDownAni(ArticleRl articleThumbRl) {
float topX = -(1080 - 200) + (float) ((2160 - 400) * Math.random());
float topY = -300 + (float) (-700 * Math.random());
//上升动画
//抛物线动画 x方向
ObjectAnimator translateAnimationX = ObjectAnimator.ofFloat(this, "translationX",
0, topX);
translateAnimationX.setDuration(DURATION);
translateAnimationX.setInterpolator(new LinearInterpolator());
//y方向
ObjectAnimator translateAnimationY = ObjectAnimator.ofFloat(this, "translationY",
0, topY);
translateAnimationY.setDuration(DURATION);
translateAnimationY.setInterpolator(new DecelerateInterpolator());
//表情图片的大小变化
ObjectAnimator translateAnimationRightLength = ObjectAnimator.ofInt(this, "rightLength",
0, 100, 100, 100, 100, 100);
translateAnimationRightLength.setDuration(DURATION);
ObjectAnimator translateAnimationBottomLength = ObjectAnimator.ofInt(this, "bottomLength",
0, 100, 100, 100, 100, 100);
translateAnimationBottomLength.setDuration(DURATION);
translateAnimationRightLength.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
//ondraw会在什么情况下执行?
}
});
//动画集合
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(translateAnimationX).with(translateAnimationY).with(translateAnimationRightLength).with(translateAnimationBottomLength);
//下降动画
//抛物线动画,原理:两个位移动画,一个横向匀速移动,一个纵向变速移动,两个动画同时执行,就有了抛物线的效果。
ObjectAnimator translateAnimationXDown = ObjectAnimator.ofFloat(this, "translationX", topX, topX * 1.2f);
translateAnimationXDown.setDuration(DURATION / 5);
translateAnimationXDown.setInterpolator(new LinearInterpolator());
ObjectAnimator translateAnimationYDown = ObjectAnimator.ofFloat(this, "translationY", topY, topY * 0.8f);
translateAnimationYDown.setDuration(DURATION / 5);
translateAnimationYDown.setInterpolator(new AccelerateInterpolator());
//透明度
ObjectAnimator alphaAnimation = ObjectAnimator.ofFloat(this, "alpha", 1f, 1f, 1f, 1f, 1f, 1f, 1f, 0f);
alphaAnimation.setDuration(DURATION / 5);
AnimatorSet animatorSetDown = new AnimatorSet();
//设置动画播放顺序
//播放上升动画
animatorSet.start();
animatorSet.addListener(new Animator.AnimatorListener() {@Override
public void onAnimationEnd(Animator animation) {
animatorSetDown.play(translateAnimationXDown).with(translateAnimationYDown).with(alphaAnimation);
animatorSetDown.start();
}
});
animatorSetDown.addListener(new Animator.AnimatorListener() {@Override
public void onAnimationEnd(Animator animation) {
articleThumbRl.removeView(ThumbEmoji.this);
mAnimatorListener.onAnimationEmojiEnd();
}
});
}
四、总结 项目github地址:https://github.com/honglei92/... view是应用开发时常会接触得的东西,从使用概念原理几个方面我们需要深学细悟、研机析理,做到融会贯通。apk地址:https://github.com/honglei92/...
Android高级开发系统进阶笔记、最新面试复习笔记PDF,我的GitHub
文末 您的点赞收藏就是对我最大的鼓励!
欢迎关注我,分享Android干货,交流Android技术。
对文章有何见解,或者有何技术问题,欢迎在评论区一起留言讨论!
推荐阅读
- Kotlin + buildSrc(更好的管理Gadle依赖!)
- 字节跳动、今日头条、阿里爸爸都在使用Flutter,你还有拒绝的理由()
- 四步,搞定一个短信验证码登录!
- Jetpack Compose有学的必要吗(未来前景将会怎样?)
- 字节内部Android笔记泄露,2960页完整版限时下载!!
- Android自定义支付密码输入框,光标问题总结!
- 如何评价性能优化(涵盖知识面太广?)
- Retrofit 妙用,拒绝重复代码!
- 启动优化 - 有向无环图