在最新的美拍增加了一个直播功能,看了一下其点赞的效果还是很酷炫的,就自己实现了一个类似的,效果如下:

android实现防美拍点赞效果_第1张图片
先说下实现该效果需要用到的知识点:

  • 属性动画,这里只是用到了基本的属性动画,对于属性动画的详细介绍,可以参考郭大神的博客:
    Android属性动画完全解析(上),初识属性动画的基本用法
    Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法
    Android属性动画完全解析(下),Interpolator和ViewPropertyAnimator的用法

  • 贝塞尔曲线, 贝塞尔曲线的学习可以参考
    自定义控件其实很简单5/12
    Android防360水波进度

实现思路

先简单说下实现该效果的思路:

  • 定义一个Layout,每次点赞的时候,动态创建一个ImageView,添加到该布局中,然后使用属性动画来改变其透明度和大小
  • 是当前添加的ImageView沿着三阶贝塞尔曲线的路径滑动,当然,这里为了做到随机效果,每次随机改变两个控制点的坐标

三阶贝塞尔的实现

三阶贝塞尔曲线,有四个点,P0、P1、P2、P3 ,这里P0和P3表示起始点和终止点,P1和P2表示的是两个控制点,这两个控制主要决定了该曲线的路径。如下图:
android实现防美拍点赞效果_第2张图片
公式如下:
这里写图片描述

描绘曲线路径

这里,我创建一个BezierCurveView.java用来绘制三阶贝塞尔曲线的路径

public class BezierCurveView extends View{    private static final String TAG = "BezierCurveView";    private Paint paint;    private Path path;    public BezierCurveView(Context context, AttributeSet attrs){        super(context,attrs);        init();    }    public BezierCurveView(Context context){        super(context);        init();    }    private void init(){                paint = new Paint();        paint.setColor(Color.RED);        paint.setAntiAlias(true);        paint.setStyle(Style.STROKE);        paint.setStrokeWidth(1);        path = new Path();    }    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        Log.v(TAG, "width = " + MeasureSpec.getSize(widthMeasureSpec) + "and height = " + MeasureSpec.getSize(heightMeasureSpec));    }    public void onDraw(Canvas canvas){        canvas.drawColor(Color.WHITE);        path.reset();        path.moveTo(0, 0);        // 可以看到这类的控制点的坐标:        // p1 = getMeasuredWidth(), 0        // p2 = 0, getMeasuredHeight()        path.cubicTo(getMeasuredWidth(), 0, 0, getMeasuredHeight(),getMeasuredWidth(), getMeasuredHeight());        canvas.drawPath(path, paint);    }}

代码比较简单,主要调用Path类的cubicTo方法绘制贝塞尔曲线的路劲,该类不是必须的,只是方便理解而添加的。

自定义TypeEvaluator

上面的BezierCurveView只是用来绘制一条三阶贝塞尔曲线的路径,而我们想要让某一个view滑动的路径是该曲线的话,就需要自定义一个TypeEvaluator,并且使用到了上面的公式。TypeEvaluator会接受第一步中算出来的比例因子,然后算出当前的属性的值,将其返回给ValuaAnimator。

由于需要使当前组件按照Bezier曲线的路径来滑动,这里需要的泛型类型就是PointF了。

class BezierEvaluator implements TypeEvaluator<PointF> {        @Override        public PointF evaluate(float fraction, PointF startValue,                               PointF endValue) {            final float t = fraction;            float oneMinusT = 1.0f - t;            PointF point = new PointF();            // 起始点            PointF point0 = (PointF)startValue;            // 第一个控制点坐标            PointF point1 = new PointF();            point1.set(width, 0);            // 第二个控制点坐标            PointF point2 = new PointF();            point2.set(0, height);            // 终点坐标            PointF point3 = (PointF)endValue;            point.x = oneMinusT * oneMinusT * oneMinusT * (point0.x)                    + 3 * oneMinusT * oneMinusT * t * (point1.x)                    + 3 * oneMinusT * t * t * (point2.x)                    + t * t * t * (point3.x);            point.y = oneMinusT * oneMinusT * oneMinusT * (point0.y)                    + 3 * oneMinusT * oneMinusT * t * (point1.y)                    + 3 * oneMinusT * t * t * (point2.y)                    + t * t * t * (point3.y);            return point;        }    }

为button添加属性动画

下面使用属性动画,使当前button按照曲线路径滑动。

// 获取屏幕的宽度和高度DisplayMetrics dm = new DisplayMetrics();       getWindowManager().getDefaultDisplay().getMetrics(dm);width = dm.widthPixels;height = dm.heightPixels;final FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);        fab.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                valueAnimator.start();            }        });// 创建属性动画,第一个参数就是我们创建的TypeEvaluator,后面两个分别表示起始点和终止点 valueAnimator = ValueAnimator.ofObject(new BezierEvaluator(), new PointF(0,0),new PointF(width,height));        valueAnimator.setDuration(2000);        // 为当前动画添加监听,根据BezierEvaluator计算的值,实时更新当前button的位置,这里由于我们使用的是贝塞尔曲线的路径,所有button也会按照该路径来滑动        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                PointF pointF = (PointF)animation.getAnimatedValue();                fab.setX(pointF.x);                fab.setY(pointF.y);            }        });        valueAnimator.setTarget(fab);        valueAnimator.setRepeatCount(1);        valueAnimator.setRepeatMode(ValueAnimator.REVERSE);

此时效果如下:
android实现防美拍点赞效果_第3张图片

实现点赞效果

上面一个基本的demo,其实已经实现了一个基本的点赞效果了,我们现在需要做的有:

  • 自定义一个布局,每次点赞的时候,动态创建一个ImageView,并且添加到该布局中。
  • 当ImageView添加至该布局以后,使用属性动画,控制当前ImageView的滑动轨迹按照和上面类似的曲线来滑动,控制ImageView的大小和透明度

MeipaiLayout.java

每当点击一次赞按钮的时候添加一个ImageView到该MeipaiLayout中,下面看下其属性:

private int[] likeArray = new int[] {R.drawable.ic_praise_sm1,R.drawable.ic_praise_sm2,R.drawable.ic_praise_sm3,            R.drawable.ic_praise_sm4,R.drawable.ic_praise_sm5,R.drawable.ic_praise_sm6,        R.drawable.ic_praise_big3,R.drawable.ic_praise_big4,R.drawable.ic_praise_big5,R.drawable.ic_praise_big6,R.drawable.ic_praise_big7            ,R.drawable.ic_praise_big8};    private static final String TAG = MeipaiLayout.class.getSimpleName();    // 布局的宽度和高度    private int mLayoutWidth;    private int mLayoutHeight;    // 属性动画的时间    private static final int DURATION = 3000;    // 图片的宽度和高度    private int mImageWidth;    private int mImageHeight;    // 大图片和小图片的大小    private static final int BIG_SIZE = 128;    private static final int SMALL_SIZE = 64;    private Random random = new Random();
  • 添加addImageView方法
    addImageView方法就是动态构造一个ImageView并且添加到当前MeipaiLayout中
/** * 添加点赞效果,实际就是动态为该布局中添加一个ImageView,并且使用动画来显示 */    public void addImageView() {        //随机选一个Image        final ImageView imageView = new ImageView(getContext());        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),likeArray[random.nextInt(likeArray.length)]);        imageView.setImageBitmap(bitmap);        // 获取当前图片的宽度和高度        ViewTreeObserver vto = imageView.getViewTreeObserver();        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {            @Override            public void onGlobalLayout() {                imageView.getViewTreeObserver().removeGlobalOnLayoutListener(this);                // 获取当前图片的宽度和高度                mImageWidth = imageView.getWidth();                mImageHeight = imageView.getHeight();                Log.d(TAG,"the mImageWidth is :"+mImageWidth+"===the mImageHeight is :"+mImageHeight);            }        });        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);        // 添加当前ImageView        addView(imageView,params);        // 下面定义三个动画,分别用来设置当前ImageView的透明度和大小        ObjectAnimator alpha = ObjectAnimator.ofFloat(imageView,View.ALPHA, 1f, 0f);        ObjectAnimator scaleX;        ObjectAnimator scaleY;        if (mImageWidth == BIG_SIZE) {            scaleX = ObjectAnimator.ofFloat(imageView,View.SCALE_X, 0.5f, 1.5f);            scaleY = ObjectAnimator.ofFloat(imageView,View.SCALE_Y, 0.5f, 1.5f);        } else {            scaleX = ObjectAnimator.ofFloat(imageView,View.SCALE_X, 0.5f, 1.2f);            scaleY = ObjectAnimator.ofFloat(imageView,View.SCALE_Y, 0.5f, 1.2f);        }        // 使用三阶贝塞尔曲线来控制当前ImageView的位置        ValueAnimator valueAnimator = ObjectAnimator.ofObject(new BezierEvaluator(), new PointF((mLayoutWidth - 64) / 2,mLayoutHeight - 64),new PointF(random.nextInt(mLayoutWidth - 64),random.nextInt(64)));        valueAnimator.setDuration(5000);        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                PointF pointF = (PointF)animation.getAnimatedValue();                imageView.setX(pointF.x);                imageView.setY(pointF.y);            }        });        valueAnimator.setTarget(imageView);        valueAnimator.setRepeatCount(1);        valueAnimator.setRepeatMode(ValueAnimator.REVERSE);        valueAnimator.start();        // 控制当前ImageView的大小和透明度        AnimatorSet animatorSet = new AnimatorSet();        animatorSet.setDuration(5000);        animatorSet.setInterpolator(new LinearInterpolator());        animatorSet.playTogether(alpha,scaleX,scaleY);        animatorSet.setTarget(imageView);        animatorSet.addListener(new AnimatorListenerAdapter() {            @Override            public void onAnimationEnd(Animator animation) {                super.onAnimationEnd(animation);                // 在等动画结束时候移除该view                removeView(imageView);            }        });        animatorSet.start();    }

上面最重要的就是使用到的BezierEvaluator,它就是控制当前ImageView滚动的轨迹,这类我们设置的是三阶贝塞尔曲线,并且每一个ImageView对应的两个控制点:p1和p2都是不同的,所以就会有随机滑动的效果。

BezierEvaluator.java

class BezierEvaluator implements TypeEvaluator<PointF> { private PointF point1 = new PointF(); private PointF point2 = new PointF(); public BezierEvaluator() { // 这里由于需要每一个新创建的ImageView按照不同的曲线来运动,所以通过random随机生成,这里的范围可以自己定义 point1.set(random.nextInt((mLayoutWidth - SMALL_SIZE) / 2), random.nextInt(SMALL_SIZE)); point2.set(random.nextInt((mLayoutWidth + SMALL_SIZE) / 2), random.nextInt(mLayoutHeight - SMALL_SIZE)); } @Override public PointF evaluate(float fraction, PointF startValue, PointF endValue) { final float t = fraction; float oneMinusT = 1.0f - t; PointF point = new PointF(); // p0表示起始点 PointF point0 = (PointF)startValue; // p3表示终止点 PointF point3 = (PointF)endValue; point.x = oneMinusT * oneMinusT * oneMinusT * (point0.x) + 3 * oneMinusT * oneMinusT * t * (point1.x) + 3 * oneMinusT * t * t * (point2.x) + t * t * t * (point3.x); point.y = oneMinusT * oneMinusT * oneMinusT * (point0.y) + 3 * oneMinusT * oneMinusT * t * (point1.y) + 3 * oneMinusT * t * t * (point2.y) + t * t * t * (point3.y); return point; } }

ok,此时就实现了我们想要的效果了,可是目前每一个都动画效果的速度都是一样的,我们可以添加不同的插值器。

设置不同的插值器

常见的差之器有如下九个:

  • AccelerateDecelerateInterpolator
    在动画开始与介绍的地方速率改变比较慢,在中间的时候加速
  • AccelerateInterpolator
    在动画开始的地方速率改变比较慢,然后开始加速
  • AnticipateInterpolator
    开始的时候向后然后向前甩
  • AnticipateOvershootInterpolator
    开始的时候向后然后向前甩一定值后返回最后的值
  • BounceInterpolator
    动画结束的时候弹起
  • CycleInterpolator
    动画循环播放特定的次数,速率改变沿着正弦曲线
  • DecelerateInterpolator
    在动画开始的地方快然后慢
  • LinearInterpolator
    匀速
  • OvershootInterpolator
    向前甩一定值后再回到原来位置

我们随机选择一个差之器,应用到当前的动画中。

 private Interpolator[] inteceptors = new Interpolator[]{new DecelerateInterpolator(),new LinearInterpolator()                                        ,new OvershootInterpolator()};valueAnimator.setInterpolator(inteceptors[random.nextInt(inteceptors.length)]);

ok,到这里完整的效果都已经实现,下面附上源码连接
点赞效果源码

更多相关文章

  1. Android-自定义滑动菜单(抽屉效果)
  2. Android的Activity切换动画特效库SwitchLayout,视图切换动画库,媲
  3. Android应用程序资源——Animation动画资源
  4. 使用点九图在Android Studio中实现与Axure设计图一致的阴影效果
  5. android仿网易云音乐引导页、仿书旗小说Flutter版、ViewPager切
  6. 重置Android中的帧动画播放
  7. Android中给listview/gridview设置动画(逐条加载条目动画)
  8. Android旋转动画
  9. android之Tween动画

随机推荐

  1. 详谈Android(安卓)apk打包:关于APK数字签
  2. Android(安卓)View视图系统分析和Scrolle
  3. Android XML使用
  4. Android(安卓)快速获取用户通信录联系人
  5. 除去ScrollView拉到尽头时再拉的阴影效果
  6. UVC系列1-Android盒子控制云台摄像头系列
  7. android 获得焦点(View get focus)
  8. Android 国际化,文本国际化,图片国际化
  9. iOS 和 Android(安卓)测试托管平台 FIR.i
  10. mac Android studio3.2版本安装+环境搭建