美文网首页
自定义view仿写今日头条点赞动画

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

作者: honglei92 | 来源:发表于2021-03-03 19:04 被阅读0次

    前言

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


    20210218120920417.gif

    仿写效果:


    whl.gif

    一、导读

    学习的过程中发现,每个知识点都是一个小小的体系。比如Glide源码解析,我看到有作者写了10篇文章一个系列来解析(Glide源码解析 https://www.jianshu.com/nb/45157164);又比如自定义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<Integer> 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/toutiaothumb
    view是应用开发时常会接触得的东西,从使用概念原理几个方面我们需要深学细悟、研机析理,做到融会贯通。
    apk地址:https://github.com/honglei92/toutiaothumb/blob/master/app/release/app-release.apk

    五、参考文献

    [1]https://mp.weixin.qq.com/s/PlNtRiowKe9jUXhzPA-CNg
    [2]https://github.com/arvinljw/ThumbUpSample
    [3]https://mp.weixin.qq.com/s/JjeYAESAI8NnwFdJJUXqQA
    [4]https://rengwuxian.com/105.html

    相关文章

      网友评论

          本文标题:自定义view仿写今日头条点赞动画

          本文链接:https://www.haomeiwen.com/subject/dhlcqltx.html