200行代码打造直播间点赞、飘心效果

来源:互联网 发布:什么是数据库事物 编辑:程序博客网 时间:2024/06/05 21:52

先上效果图:



  用到了二阶贝塞尔曲线,先来学习学习。
  
  

  原理: 由 P0 至 P1 的连续点 Q0,描述一条线段。 
             由 P1 至 P2 的连续点 Q1,描述一条线段。 
             由 Q0 至 Q1 的连续点 B(t),描述一条二次贝塞尔曲线。

  按照我自己的理解就是,P0和P2分别为起点和终点,P1为控制点,来控制曲线的轨迹。

 因为是很多个心,所以我用HasMap来装,因为在动画结束之后我们要把心在界面移除,所以用key来删除当前的心。因为是自定义View,所以我把曲线和动画及更新的位置都放在了内部类里面,让自行操作。了解了二阶贝塞尔曲线的原理之后,我把贝塞尔曲线的控制点放在了下图中圈中的位置,控制点是随机的。起点是最下面的中点,终点是最上面x轴随机的位置。


  下面上代码,基本都有注释。

 
 class BezierEvaluator implements TypeEvaluator<Point>, ValueAnimator.AnimatorUpdateListener {        private Point randomPoint; //随机控制点        private int x;//每次更新的曲线的x坐标        private int y;//每次更新的曲线的y坐标        public int i;//随机选择的图片样式        public String key;//用来删除动画结束的心        private Bitmap bitmap;//要画心的bitmap        public Paint paint;//画笔        public BezierEvaluator(String key) {            this.key = key;            paint = new Paint();            paint.setAntiAlias(true);            i = new Random().nextInt(5);            getImg(i);            //获得随机的bitmap            randomPoint = new Point(new Random().nextInt(mWidth / 2) + mWidth / 4, new Random().nextInt(mHeight / 2));            //获得随机控制点            Point startP = new Point(mWidth / 2, mHeight - 50);            //设置起点位置,向上偏移50            Point endP = new Point(new Random().nextInt(mWidth / 2) + mWidth / 4, 0);            //设置终点位置,让终点在上图的随机位置            ValueAnimator anim = ValueAnimator.ofObject(this, startP, endP);            //设置动画            anim.addUpdateListener(this);            anim.setDuration(5000);            anim.addListener(new AnimatorListenerAdapter() {                @Override                public void onAnimationEnd(Animator animation) {                    super.onAnimationEnd(animation);                    hashMap.remove(BezierEvaluator.this.key);                    //在hashmap中把当前删除                    randomIs.remove(BezierEvaluator.this.key);                    //删除当前key值                    bitmap.recycle();                    //回收bitmap                    bitmap = null;                    //滞空bitmap                }            });            anim.setInterpolator(new LinearInterpolator());            //为动画加入插值器            anim.start();        }        private void getImg(int i) {            switch (i) {                case 0:                    bitmap = BitmapFactory.decodeResource(getResources(),                            R.mipmap.heart1).copy(Bitmap.Config.ARGB_8888, true);                    break;                case 1:                    bitmap = BitmapFactory.decodeResource(getResources(),                            R.mipmap.heart2).copy(Bitmap.Config.ARGB_8888, true);                    break;                case 2:                    bitmap = BitmapFactory.decodeResource(getResources(),                            R.mipmap.heart3).copy(Bitmap.Config.ARGB_8888, true);                    break;                case 3:                    bitmap = BitmapFactory.decodeResource(getResources(),                            R.mipmap.heart4).copy(Bitmap.Config.ARGB_8888, true);                    break;                case 4:                    bitmap = BitmapFactory.decodeResource(getResources(),                            R.mipmap.heart5).copy(Bitmap.Config.ARGB_8888, true);                    break;            }        }        @Override        public Point evaluate(float t, Point startValue, Point endValue) {            int x = (int) ((1 - t) * (1 - t) * startValue.x + 2 * t * (1 - t) * randomPoint.x + t * t * endValue.x);            int y = (int) ((1 - t) * (1 - t) * startValue.y + 2 * t * (1 - t) * randomPoint.y + t * t * endValue.y);            //根据二阶贝塞尔曲线公式获得在屏幕运动中的x,y坐标            return new Point(x, y);        }        @Override        public void onAnimationUpdate(ValueAnimator animation) {            //根据动画的更新来获得坐标来重绘onDraw            Point point = (Point) animation.getAnimatedValue();            this.x = point.x;            this.y = point.y;            int alpha = point.y / 4;            paint.setAlpha(alpha);            //为画笔设置透明度            invalidate();        }    }
  下面是自定义view中点击事件添加心以及对外开放添加心的方法。
  
 @Override    public void onClick(View v) {        keys.add(String.valueOf(key));        hashMap.put(String.valueOf(key), new BezierEvaluator(String.valueOf(key)));        key++;    }    public void addHeart(int size) {        addSize = size;        if (size > 0) {            new Handler().postDelayed(new Runnable() {                @Override                public void run() {                    keys.add(String.valueOf(key));                    hashMap.put(String.valueOf(key), new BezierEvaluator(String.valueOf(key)));                    key++;                    addSize--;                    addHeart(addSize);                }            }, 100);        } else {            return;        }    }
  使用:
  
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <com.yogee.mycustom.view.HeartView        android:id="@+id/heartView"        android:layout_width="match_parent"        android:layout_height="match_parent" />    </LinearLayout>

  private HeartView heartView;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_six);        heartView = (HeartView) findViewById(R.id.heartView);        heartView.addHeart(500);    }

完整版代码:
package com.yogee.mycustom.view;import android.animation.Animator;import android.animation.AnimatorListenerAdapter;import android.animation.TypeEvaluator;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.Point;import android.os.Handler;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.view.View;import android.view.animation.LinearInterpolator;import com.yogee.mycustom.R;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Random;public class HeartView extends View implements View.OnClickListener {    private int mWidth;    private int mHeight;    private int key = 0;    private List<String> keys = new ArrayList<>();//key值集合    private HashMap<String, BezierEvaluator> hashMap = new HashMap<>();    private int addSize;    public HeartView(Context context) {        super(context);        setOnClickListener(this);    }    public HeartView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        setOnClickListener(this);    }    public HeartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        setOnClickListener(this);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (hashMap.size() > 0)            for (String i : keys) {                canvas.drawBitmap(hashMap.get(i).bitmap, hashMap.get(i).x, hashMap.get(i).y, hashMap.get(i).paint);            }    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        mWidth = MeasureSpec.getSize(widthMeasureSpec);        mHeight = MeasureSpec.getSize(heightMeasureSpec);        setMeasuredDimension(mWidth, mHeight);    }    @Override    public void onClick(View v) {        keys.add(String.valueOf(key));        hashMap.put(String.valueOf(key), new BezierEvaluator(String.valueOf(key)));        key++;    }    public void addHeart(int size) {        addSize = size;        if (size > 0) {            new Handler().postDelayed(new Runnable() {                @Override                public void run() {                    keys.add(String.valueOf(key));                    hashMap.put(String.valueOf(key), new BezierEvaluator(String.valueOf(key)));                    key++;                    addSize--;                    addHeart(addSize);                }            }, 100);        } else {            return;        }    }    class BezierEvaluator implements TypeEvaluator<Point>, ValueAnimator.AnimatorUpdateListener {        private Point randomPoint; //随机控制点        private int x;//每次更新的曲线的x坐标        private int y;//每次更新的曲线的y坐标        public int i;//随机选择的图片样式        public String key;//用来删除动画结束的心        private Bitmap bitmap;//要画心的bitmap        public Paint paint;//画笔        public BezierEvaluator(String key) {            this.key = key;            paint = new Paint();            paint.setAntiAlias(true);            i = new Random().nextInt(5);            getImg(i);            //获得随机的bitmap            randomPoint = new Point(new Random().nextInt(mWidth / 2) + mWidth / 4, new Random().nextInt(mHeight / 2));            //获得随机控制点            Point startP = new Point(mWidth / 2, mHeight - 50);            //设置起点位置,向上偏移50            Point endP = new Point(new Random().nextInt(mWidth / 2) + mWidth / 4, 0);            //设置终点位置,让终点在上图的随机位置            ValueAnimator anim = ValueAnimator.ofObject(this, startP, endP);            //设置动画            anim.addUpdateListener(this);            anim.setDuration(5000);            anim.addListener(new AnimatorListenerAdapter() {                @Override                public void onAnimationEnd(Animator animation) {                    super.onAnimationEnd(animation);                    hashMap.remove(BezierEvaluator.this.key);                    //在hashmap中把当前删除                    keys.remove(BezierEvaluator.this.key);                    //删除当前key值                    bitmap.recycle();                    //回收bitmap                    bitmap = null;                    //滞空bitmap                }            });            anim.setInterpolator(new LinearInterpolator());            //为动画加入插值器            anim.start();        }        private void getImg(int i) {            switch (i) {                case 0:                    bitmap = BitmapFactory.decodeResource(getResources(),                            R.mipmap.heart1).copy(Bitmap.Config.ARGB_8888, true);                    break;                case 1:                    bitmap = BitmapFactory.decodeResource(getResources(),                            R.mipmap.heart2).copy(Bitmap.Config.ARGB_8888, true);                    break;                case 2:                    bitmap = BitmapFactory.decodeResource(getResources(),                            R.mipmap.heart3).copy(Bitmap.Config.ARGB_8888, true);                    break;                case 3:                    bitmap = BitmapFactory.decodeResource(getResources(),                            R.mipmap.heart4).copy(Bitmap.Config.ARGB_8888, true);                    break;                case 4:                    bitmap = BitmapFactory.decodeResource(getResources(),                            R.mipmap.heart5).copy(Bitmap.Config.ARGB_8888, true);                    break;            }        }        @Override        public Point evaluate(float t, Point startValue, Point endValue) {            int x = (int) ((1 - t) * (1 - t) * startValue.x + 2 * t * (1 - t) * randomPoint.x + t * t * endValue.x);            int y = (int) ((1 - t) * (1 - t) * startValue.y + 2 * t * (1 - t) * randomPoint.y + t * t * endValue.y);            //根据二阶贝塞尔曲线公式获得在屏幕运动中的x,y坐标            return new Point(x, y);        }        @Override        public void onAnimationUpdate(ValueAnimator animation) {            //根据动画的更新来获得坐标来重绘onDraw            Point point = (Point) animation.getAnimatedValue();            this.x = point.x;            this.y = point.y;            int alpha = point.y / 4;            paint.setAlpha(alpha);            //为画笔设置透明度            invalidate();        }    }}

  完成。