200行代码打造直播间点赞、飘心效果
来源:互联网 发布:什么是数据库事物 编辑:程序博客网 时间:2024/06/05 21:52
先上效果图:
用到了二阶贝塞尔曲线,先来学习学习。
原理: 由 P0 至 P1 的连续点 Q0,描述一条线段。
由 P1 至 P2 的连续点 Q1,描述一条线段。
由 Q0 至 Q1 的连续点 B(t),描述一条二次贝塞尔曲线。
由 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(); } }}
阅读全文
1 0
- 200行代码打造直播间点赞、飘心效果
- 用jQuery打造TabPanel效果代码
- 类似直播飘星动画效果
- 视频直播中的心形漂浮效果
- Android直播气泡效果
- RTMP直播效果实现
- android 飘心动画(直播点赞)效果
- 打造木纹画效果
- 打造高逼格耀斑效果
- 打造滑动效果
- RecyclerView:打造悬浮效果
- 从 0 到 1 打造直播 App
- 从0到1打造直播 App
- 从0到1打造直播 App
- 从0到1打造直播 App
- red5+java打造直播平台系列 初级
- 从0到1打造直播 App
- 百行代码打造高级联动特效
- Something went wrong: Unexpected error in AndroidAnnotations 4.2.0!
- 1286:aabb
- VMware桥接网络模式配置静态IP详细步骤
- MailKit/MimeKit 发送邮件时如何保持正文格式不变?MailKit/MimeKit HTml文本
- MongoDB操作
- 200行代码打造直播间点赞、飘心效果
- 【设计模式 三】工厂模式
- redis 事务之watch
- Elasticsearch的使用场景
- spring3 mvc rest返回中文乱码? 解决问题
- UGUI 学习笔记 6 Button
- 最简单 5秒跳转 Handler
- 沉浸式模式
- windows下手动安装composer