自定义view仿写今日头条点赞动画!

前言 平时喜欢看今日头条,上面的财经、科技和NBA栏目都很喜欢,无意中发现他的点赞动画还不错,一下子就吸引到了我。遂即想要不自己实现一下。
最终效果对比如下:
头条:

仿写效果:

一、导读 【自定义view仿写今日头条点赞动画!】学习的过程中发现,每个知识点都是一个小小的体系。比如Glide源码解析,我看到有作者写了10篇文章一个系列来解析(Glide源码解析 https://www.jianshu.com/nb/45...);又比如自定义view,扔物线凯哥也是从三个方面(绘制、布局、动画)11篇文章来叙述,Carson_Ho也是写了一个系列来描述;所以掌握一个知识点里面的知识体系还是需要下一些功夫的。
二、效果分析

  • 1 点击一次会撒出五个随机表情和点击音效;
  • 2 连续点击会连续撒出表情并播放音效;
  • 3 长按会一直撒;
  • 4 连续撒时会出现次数和标语(0-20 鼓励,20-40加油,>40太棒了);
三、实现过程 3.1 外层布局 因为今日头条里面底部评论框和资讯列表页都会有点赞按钮,那么点赞效果的表情机会满屏幕都存在,所以最外层继承了RelativeLayout。然后宽高都设置match_parent。在点击按钮的时候触发OnTouch事件:
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方法给他设置indexchildcount-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技术。
对文章有何见解,或者有何技术问题,欢迎在评论区一起留言讨论!

    推荐阅读