零基础打造美拍点赞特效

1,311 阅读8分钟

这两天一直在搞Github项目主页,已经全新改版了,界面相比以前好看了很多。感兴趣的同学可以去瞧瞧。我会一直更新下去,想学习自定义View的同学可以star一下哦。GitHub地址:github.com/Elder-Wu/No…

昨天一直在研究ViewPager的源码,因为我想知道为什么当ViewPager只有两张图片的时候,无限轮播会出现空白页。后来问题找到了,是因为ViewPager默认是加载三张图片的,被选中的一张,然后左右各一张,一共三张。当ViewPager想初始化第三张图片的时候,它发现没有资源了,怎么办,只能显示空白页喽。当然,我看了一整天源码,不可能只得出这么一个结论。我认为关键性的代码都在ViewPager的populate方法中,大家感兴趣可以去看看源码。
总之,stackoverflow上找了半天,没发现可靠的解决方案,又去github上找了几个开源库,发现它们的处理方法都不是很好。如果有人觉得2乘2或者1乘3是最好的解决方案的话,我也不好说什么。


上面的东西大家就当笑话看看,也不要太较真,学海无涯,每个人的见解都不同。
下面就开始我们今天的话题:如何模仿美拍直播的视频点赞效果。
先看一下效果图:



然后看一下我制作的效果图:


Tips:如果你是个热血青年,想亲自感受下这个控件的使用效果,那你可以扫描下方二维码,直接把Demo下载到手机上并安装。里面还包含了我之前写的很多案例,而且我会一直更新下去。放心下载吧,都是技术干货!


半年前,我也想过要去实现这个动画效果,但是水平不够,所以就一直搁到现在。其实当时我也去看过人家的博客,但是很多东西都看不懂,完全没接触过,事实证明,的确有很多东西我没接触过。比如Interpolator,比如TypeEvaluator,比如Animator......

想要完成今天的动画效果,那你必须去学一下我上面说的三个东西,不仅仅是了解,还要会用。
Animator,可以看一下泓洋的文章:
blog.csdn.net/lmj62356579…
Interpolator和TypeEvaluator,可以看一下这篇文章:
www.cnblogs.com/wondertwo/p…


第一步:先定义一个容器,这个容器也就是“爱心”们活动的范围了。每当我们手指点击这个容器,容器中就会添加进来一个爱心。并且这个爱心被放在界面的底部且居中。
先不着急看代码,我们先看看,如果用布局文件来实现,应该怎么弄呢?



我就想问问你,是不是很简单。但是我这个人是特别讨厌写xml文件的,主要是因为xml文件不灵活,所以我们把这个简单的xml文件转化成Java代码,看一下我是怎么搞的。

//爱心图片大小  默认40dp
private static final int DEFAULT_PIC_SIZE = 40;

private ArrayList hearts = new ArrayList();

public BezierView(Context context) {
    this(context, null);
}

public BezierView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            addHeartView();
        }
    });
}

private RelativeLayout.LayoutParams params;

private void addHeartView() {
    if (params == null) {
        params = new RelativeLayout.LayoutParams(UIUtils.dp2px(DEFAULT_PIC_SIZE), UIUtils.dp2px(DEFAULT_PIC_SIZE));
        params.addRule(ALIGN_PARENT_BOTTOM);
        params.addRule(CENTER_HORIZONTAL);
        params.setMargins(10, 10, 10, 10);
    }
    final ImageView heart = new ImageView(getContext());
    heart.setImageResource(R.drawable.heart);
    heart.setScaleType(ImageView.ScaleType.CENTER_CROP);
    addView(heart, params);
    hearts.add(heart);
}

这样一来,当我们点击屏幕的时候,一个爱心就被添加进来了。多次点击肯定会添加多个爱心,所以我们有必要把所有爱心放到一个列表里面。
现在的你应该停止往下阅读,先试一下这个效果,成功的话,就可以继续下一步。


第二步:让爱心动起来。(如果你不知道什么是TypeEvaluator,请先去看一下我上面推荐的文章)
TypeEvaluator直接影响到爱心应该怎么去动。说的通俗一点,在11:18分,TypeEvaluator告诉爱心,你要去坐标(x,y);在11:19分,TypeEvaluator告诉爱心,你要去坐标(x,y);在11:20分,TypeEvaluator告诉爱心,你要去坐标(x,y)……依此类推,每次x和y的值都是在变动的。只能说爱心很傻,她不知道自己要去哪里,必须由别人告诉她。
理解了这个思路之后,看一下我的代码:

/**
 * 三阶贝塞尔曲线计算公式
 * B(t)=(P0)(1-t)^3+3(P1)t(1-t)^2+3(P2)t^2(1-t)+(P3)t^3  (0≤t≤1)
 *
 * @link TypeEvaluator和Interpolator教程:http://www.cnblogs.com/wondertwo/p/5327586.html
 */
private class BezierEvaluator implements TypeEvaluator {
    private PointF pointF1;
    private PointF pointF2;
    public BezierEvaluator(PointF pointF1, PointF pointF2) {
        this.pointF1 = pointF1;
        this.pointF2 = pointF2;
    }
    @Override
    public PointF evaluate(float time, PointF startValue,
                           PointF endValue) {
        float timeLeft = 1.0f - time;
        PointF point = new PointF();//结果
        point.x = timeLeft * timeLeft * timeLeft * (startValue.x)
                + 3 * timeLeft * timeLeft * time * (pointF1.x)
                + 3 * timeLeft * time * time * (pointF2.x)
                + time * time * time * (endValue.x);
        point.y = timeLeft * timeLeft * timeLeft * (startValue.y)
                + 3 * timeLeft * timeLeft * time * (pointF1.y)
                + 3 * timeLeft * time * time * (pointF2.y)
                + time * time * time * (endValue.y);
        return point;
    }
}

我在百度百科上找了一个三阶贝塞尔曲线的公式,然后通过公式我写出了计算方法。这个计算方法干嘛用的?
你只要传入一个t值,(t表示time)我就能帮你算出该时间下,爱心应该出现在哪个位置,懂了吧。

紧接着,我们要写一个属性动画,来把这个BezierEvaluator用上去,我是这么写的

//动画差值器
private Interpolator animatorInterpolator = new AccelerateDecelerateInterpolator();

private void start(final View heart) {
    //计算出显示区域的大小
    final int show_area_height = getHeight();
    final int show_area_width = getWidth();
    //起始点
    if (startPointF == null) {
        startPointF = new PointF((show_area_width - UIUtils.dp2px(DEFAULT_PIC_SIZE)) / 2, show_area_height - UIUtils.dp2px(DEFAULT_PIC_SIZE));
    }
    //控制点1
    final PointF pointF1 = new PointF(show_area_width / 2 - (random.nextInt() % show_area_width) / 3, (show_area_height / 2) + (random.nextInt() % show_area_height) / 3);
    //控制点2
    final PointF pointF2 = new PointF(show_area_width / 2 + (random.nextInt() % show_area_width) / 3, (show_area_height / 2) + (random.nextInt() % show_area_height) / 3);
    //终点
    final PointF endPointF = new PointF((System.currentTimeMillis() % show_area_width), 0);
    ValueAnimator valueAnimator = ValueAnimator.ofObject(new BezierEvaluator(pointF1, pointF2), startPointF, endPointF);
    valueAnimator.setDuration(4000);
    valueAnimator.setInterpolator(animatorInterpolator);
    valueAnimator.addListener(new AnimatorListenerAdapter(heart));
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            final PointF point = (PointF) animation.getAnimatedValue();
            heart.setX(point.x);
            heart.setY(point.y);
            //设置透明度
            heart.setAlpha((1 - animation.getAnimatedFraction()) * 2);
        }
    });
    valueAnimator.start();
}

每次动画开始前,我都会通过固定的算法生成两个控制点,再生成一个结束点,起点的位置都是固定的。比较特别的是,我这里使用的是ValueAnimator,并没有使用ObjectAnimator,为什么呢?因为我们需要监听数据的变化,然后每次数据变化我们都要进行相应的操作,我们可以看一下valueAnimator.addUpdateListener( ),里面就是不断地改变爱心的x坐标和y坐标来达到动画的效果。我在设置透明度的时候,用到了fraction,所以这句代码我不解释。不知道fraction是什么东西的同学,去看一下文章开头我给你们推荐的博客。

我的其中一行代码valueAnimator.addListener(new AnimatorListenerAdapter(heart));
AnimatorListenerAdapter是我自己写的一个类,实现了Animator.AnimatorListener接口,具体看一下我是怎么写的,在里面做了什么操作。

private class AnimatorListenerAdapter implements Animator.AnimatorListener {
    private View view;
    public AnimatorListenerAdapter(View view) {
        this.view = view;
    }
    @Override
    public void onAnimationStart(Animator animation) {
    }
    @Override
    public void onAnimationEnd(Animator animation) {
        removeView(view);
        hearts.remove(hearts.indexOf(view));
        view = null;
    }
    @Override
    public void onAnimationCancel(Animator animation) {
    }
    @Override
    public void onAnimationRepeat(Animator animation) {
    }
}

这个接口监听了动画的状态,我在动画结束的时候将爱心移除,并且赋值null,这样的话垃圾回收机制就会在合适的时间来回收资源了,不会造成内存泄漏。

写到这里,大家再结合第一步写的代码,然后调用start(final View heart)就可以让你的爱心动起来了,赶紧试一下吧。这一步涉及到的东西真的很重要很重要,也比较难,如果没有成功运行程序的同学,千万不要继续看下去。


第三步(也是最后一步):让我们的动画变得有层次感
这个时候我们就要用到Interpolator了,其实也不是很重要的一个东西。系统已经给我们提供了很多可用的子类,我在这里就不自定义了。



我用的是AccelerateDecelerateInterpolator,是一种先加速,后减速的效果。其他效果你们自己去试试,都还蛮不错的。


看一下具体的使用方法:



注意看右边的蓝色边框,那个就是爱心需要展示的区域,如果你想把展示区域变大,自己调节就好。
使用就是这么简单,不需要去写任何的Java代码,你就可以体验到贝塞尔曲线的动画效果。

还有个地方不算完美,那个不完美的地方就是,我没有给爱心换颜色,所有的都是红色,感觉看多了有点恶心。换颜色不是这个案例的核心,所以我就没有加进来。如果你能顺顺利利做出这个效果来,那随机变换爱心的颜色应该对于你来说不难。就当是给你们留了个作业,总要让你们动动脑子,其实我也懒。实在做不出来的,可以求助我,我有时间就把随机变颜色这个功能加上去。

最后,不懂的人留言吧,我会尽量给你解答。希望大家可以亲自去实践一下,把代码写出来,然后简单分析一下原因。实在懒的人可以去我的Github上拷贝一下代码,这篇博客的代码不是很完整,重点讲解思路,完整的代码在这里:github.com/Elder-Wu/No…
如果觉得我写得好,可以点个赞,然后关注一下的哦~另外,我的微信公众号也开播了,每天都会分享一篇优质的技术文章,欢迎大家来踩点。(公众号:代码也是人)



技术交流群:471395156(欢迎大家入坑)