关于android:自定义view仿写今日头条点赞动画

7次阅读

共计 8838 个字符,预计需要花费 23 分钟才能阅读完成。

前言

平时喜爱看今日头条,下面的财经、科技和 NBA 栏目都很喜爱,无心中发现他的点赞动画还不错,一下子就吸引到了我。遂即想要不本人实现一下。

最终成果比照如下:

头条:

仿写成果:

一、导读

学习的过程中发现,每个知识点都是一个小小的体系。比方 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<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/… view 是利用开发时常会接触得的货色,从应用概念原理几个方面咱们须要深学细悟、研机析理,做到死记硬背。apk 地址:https://github.com/honglei92/…

Android 高级开发零碎进阶笔记、最新面试温习笔记 PDF,我的 GitHub

文末

您的点赞珍藏就是对我最大的激励!
欢送关注我,分享 Android 干货,交换 Android 技术。
对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!

正文完
 0