当前位置:主页 > 移动开发 > Android代码 >

android实现直播点赞飘心动画效果

时间:2022-02-07 10:11:23 | 栏目:Android代码 | 点击:

前段时间在写直播的时候,需要观众在看直播的时候点赞的效果,在此参照了腾讯大神写的点赞(飘心动画效果)。下面是效果图:

1.自定义飘心动画的属性

在attrs.xml 中增加自定义的属性

<!-- 飘心动画自定义的属性 -->
 <declare-styleable name="HeartLayout">
 <attr name="initX" format="dimension"/>
 <attr name="initY" format="dimension"/>
 <attr name="xRand" format="dimension"/>
 <attr name="animLengthRand" format="dimension"/>
 <attr name="xPointFactor" format="dimension"/>
 <attr name="animLength" format="dimension"/>
 <attr name="heart_width" format="dimension"/>
 <attr name="heart_height" format="dimension"/>
 <attr name="bezierFactor" format="integer"/>
 <attr name="anim_duration" format="integer"/>
 </declare-styleable>

2.定义飘心默认值

2.1 dimens.xml

<!-- 飘星 -->
 <dimen name="heart_anim_bezier_x_rand">50.0dp</dimen>
 <dimen name="heart_anim_init_x">50.0dp</dimen>
 <dimen name="heart_anim_init_y">25.0dp</dimen>
 <dimen name="heart_anim_length">400.0dp</dimen>
 <dimen name="heart_anim_length_rand">350.0dp</dimen>
 <dimen name="heart_anim_x_point_factor">30.0dp</dimen>

 <dimen name="heart_size_height">27.3dp</dimen>
 <dimen name="heart_size_width">32.5dp</dimen>

2.2 integers.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

 <integer name="heart_anim_bezier_factor">6</integer>
 <integer name="anim_duration">3000</integer>

</resources>

3.定义飘心动画控制器

3.1 AbstractPathAnimator.java

public abstract class AbstractPathAnimator {
 private final Random mRandom;
 protected final Config mConfig;


 public AbstractPathAnimator(Config config) {
 mConfig = config;
 mRandom = new Random();
 }

 public float randomRotation() {
 return mRandom.nextFloat() * 28.6F - 14.3F;
 }

 public Path createPath(AtomicInteger counter, View view, int factor) {
 Random r = mRandom;
 int x = r.nextInt(mConfig.xRand);
 int x2 = r.nextInt(mConfig.xRand);
 int y = view.getHeight() - mConfig.initY;
 int y2 = counter.intValue() * 15 + mConfig.animLength * factor + r.nextInt(mConfig.animLengthRand);
 factor = y2 / mConfig.bezierFactor;
 x = mConfig.xPointFactor + x;
 x2 = mConfig.xPointFactor + x2;
 int y3 = y - y2;
 y2 = y - y2 / 2;
 Path p = new Path();
 p.moveTo(mConfig.initX, y);
 p.cubicTo(mConfig.initX, y - factor, x, y2 + factor, x, y2);
 p.moveTo(x, y2);
 p.cubicTo(x, y2 - factor, x2, y3 + factor, x2, y3);
 return p;
 }

 public abstract void start(View child, ViewGroup parent);

 public static class Config {
 public int initX;
 public int initY;
 public int xRand;
 public int animLengthRand;
 public int bezierFactor;
 public int xPointFactor;
 public int animLength;
 public int heartWidth;
 public int heartHeight;
 public int animDuration;

 static public Config fromTypeArray(TypedArray typedArray, float x, float y, int pointx, int heartWidth, int heartHeight) {
  Config config = new Config();
  Resources res = typedArray.getResources();
  config.initX = (int) typedArray.getDimension(R.styleable.HeartLayout_initX,
   x);
  config.initY = (int) typedArray.getDimension(R.styleable.HeartLayout_initY,
   y);
  config.xRand = (int) typedArray.getDimension(R.styleable.HeartLayout_xRand,
   res.getDimensionPixelOffset(R.dimen.heart_anim_bezier_x_rand));
  config.animLength = (int) typedArray.getDimension(R.styleable.HeartLayout_animLength,
   res.getDimensionPixelOffset(R.dimen.heart_anim_length));//动画长度
  config.animLengthRand = (int) typedArray.getDimension(R.styleable.HeartLayout_animLengthRand,
   res.getDimensionPixelOffset(R.dimen.heart_anim_length_rand));
  config.bezierFactor = typedArray.getInteger(R.styleable.HeartLayout_bezierFactor,
   res.getInteger(R.integer.heart_anim_bezier_factor));
  config.xPointFactor = pointx;
//  config.heartWidth = (int) typedArray.getDimension(R.styleable.HeartLayout_heart_width,
//   res.getDimensionPixelOffset(R.dimen.heart_size_width));//动画图片宽度
//  config.heartHeight = (int) typedArray.getDimension(R.styleable.HeartLayout_heart_height,
//   res.getDimensionPixelOffset(R.dimen.heart_size_height));//动画图片高度
  config.heartWidth = heartWidth;
  config.heartHeight = heartHeight;
  config.animDuration = typedArray.getInteger(R.styleable.HeartLayout_anim_duration,
   res.getInteger(R.integer.anim_duration));//持续期
  return config;
 }
 }
}

3.2 PathAnimator.java

/**
 * 飘心路径动画器
 */
public class PathAnimator extends AbstractPathAnimator {
 private final AtomicInteger mCounter = new AtomicInteger(0);
 private Handler mHandler;

 public PathAnimator(Config config) {
 super(config);
 mHandler = new Handler(Looper.getMainLooper());
 }

 @Override
 public void start(final View child, final ViewGroup parent) {
 parent.addView(child, new ViewGroup.LayoutParams(mConfig.heartWidth, mConfig.heartHeight));
 FloatAnimation anim = new FloatAnimation(createPath(mCounter, parent, 2), randomRotation(), parent, child);
 anim.setDuration(mConfig.animDuration);
 anim.setInterpolator(new LinearInterpolator());
 anim.setAnimationListener(new Animation.AnimationListener() {
  @Override
  public void onAnimationEnd(Animation animation) {
  mHandler.post(new Runnable() {
   @Override
   public void run() {
   parent.removeView(child);
   }
  });
  mCounter.decrementAndGet();
  }

  @Override
  public void onAnimationRepeat(Animation animation) {

  }

  @Override
  public void onAnimationStart(Animation animation) {
  mCounter.incrementAndGet();
  }
 });
 anim.setInterpolator(new LinearInterpolator());
 child.startAnimation(anim);
 }

 static class FloatAnimation extends Animation {
 private PathMeasure mPm;
 private View mView;
 private float mDistance;
 private float mRotation;

 public FloatAnimation(Path path, float rotation, View parent, View child) {
  mPm = new PathMeasure(path, false);
  mDistance = mPm.getLength();
  mView = child;
  mRotation = rotation;
  parent.setLayerType(View.LAYER_TYPE_HARDWARE, null);
 }

 @Override
 protected void applyTransformation(float factor, Transformation transformation) {
  Matrix matrix = transformation.getMatrix();
  mPm.getMatrix(mDistance * factor, matrix, PathMeasure.POSITION_MATRIX_FLAG);
  mView.setRotation(mRotation * factor);
  float scale = 1F;
  if (3000.0F * factor < 200.0F) {
  scale = scale(factor, 0.0D, 0.06666667014360428D, 0.20000000298023224D, 1.100000023841858D);
  } else if (3000.0F * factor < 300.0F) {
  scale = scale(factor, 0.06666667014360428D, 0.10000000149011612D, 1.100000023841858D, 1.0D);
  }
  mView.setScaleX(scale);
  mView.setScaleY(scale);
  transformation.setAlpha(1.0F - factor);
 }
 }

 private static float scale(double a, double b, double c, double d, double e) {
 return (float) ((a - b) / (c - b) * (e - d) + d);
 }
}

4.定义飘心界面

4.1 HeartView.java

/**
 * 飘心动画的界面
 */
public class HeartView extends ImageView{

 //绘制的时候抗锯齿
 private static final Paint sPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
 private static final Canvas sCanvas = new Canvas();

 private int mHeartResId = R.drawable.heart0;
 private int mHeartBorderResId = R.drawable.heart1;

 private static Bitmap sHeart;
 private static Bitmap sHeartBorder;


 public HeartView(Context context) {
 super(context);
 }

 public HeartView(Context context, AttributeSet attrs) {
 super(context, attrs);
 }

 public HeartView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 }

 public void setDrawable(int resourceId){
 Bitmap heart = BitmapFactory.decodeResource(getResources(), resourceId);
 // Sets a drawable as the content of this ImageView.
 setImageDrawable(new BitmapDrawable(getResources(),heart));
 }

 public void setColor(int color) {
 Bitmap heart = createHeart(color);
 setImageDrawable(new BitmapDrawable(getResources(), heart));
 }

 public void setColorAndDrawables(int color, int heartResId, int heartBorderResId) {
 if (heartResId != mHeartResId) {
  sHeart = null;
 }
 if (heartBorderResId != mHeartBorderResId) {
  sHeartBorder = null;
 }
 mHeartResId = heartResId;
 mHeartBorderResId = heartBorderResId;
 setColor(color);
 }

 public Bitmap createHeart(int color) {
 if (sHeart == null) {
  sHeart = BitmapFactory.decodeResource(getResources(), mHeartResId);
 }
 if (sHeartBorder == null) {
  sHeartBorder = BitmapFactory.decodeResource(getResources(), mHeartBorderResId);
 }
 Bitmap heart = sHeart;
 Bitmap heartBorder = sHeartBorder;
 Bitmap bm = createBitmapSafely(heartBorder.getWidth(), heartBorder.getHeight());
 if (bm == null) {
  return null;
 }
 Canvas canvas = sCanvas;
 canvas.setBitmap(bm);
 Paint p = sPaint;
 canvas.drawBitmap(heartBorder, 0, 0, p);
 p.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
 float dx = (heartBorder.getWidth() - heart.getWidth()) / 2f;
 float dy = (heartBorder.getHeight() - heart.getHeight()) / 2f;
 canvas.drawBitmap(heart, dx, dy, p);
 p.setColorFilter(null);
 canvas.setBitmap(null);
 return bm;
 }

 private static Bitmap createBitmapSafely(int width, int height) {
 try {
  return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
 } catch (OutOfMemoryError error) {
  error.printStackTrace();
 }
 return null;
 }
}

4.2 飘心动画路径布局

HeartLayout.java

/**
 * 飘心动画路径
 */
public class HeartLayout extends RelativeLayout implements View.OnClickListener {

 private AbstractPathAnimator mAnimator;
 private AttributeSet attrs = null;
 private int defStyleAttr = 0;
 private OnHearLayoutListener onHearLayoutListener;
 private static HeartHandler heartHandler;
 private static HeartThread heartThread;

 public void setOnHearLayoutListener(OnHearLayoutListener onHearLayoutListener) {
 this.onHearLayoutListener = onHearLayoutListener;
 }

 public interface OnHearLayoutListener {
 boolean onAddFavor();
 }

 public HeartLayout(Context context) {
 super(context);
 findViewById(context);
 }

 public HeartLayout(Context context, AttributeSet attrs) {
 super(context, attrs);
 this.attrs = attrs;
 findViewById(context);
 }

 public HeartLayout(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 this.attrs = attrs;
 this.defStyleAttr = defStyleAttr;
 findViewById(context);
 }

 private Bitmap bitmap;

 private void findViewById(Context context) {
 LayoutInflater.from(context).inflate(R.layout.ly_periscope, this);
 bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_like);
 dHeight = bitmap.getWidth()/2;
 dWidth = bitmap.getHeight()/2;
 textHight = sp2px(getContext(), 20) + dHeight / 2;

 pointx = dWidth;//随机上浮方向的x坐标

 bitmap.recycle();
 }

 private int mHeight;
 private int mWidth;
 private int textHight;
 private int dHeight;
 private int dWidth;
 private int initX;
 private int pointx;

 public static int sp2px(Context context, float spValue) {
 final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
 return (int) (spValue * fontScale + 0.5f);
 }


 public class HeartHandler extends Handler {
 public final static int MSG_SHOW = 1;
 WeakReference<HeartLayout> wf;

 public HeartHandler(HeartLayout layout) {
  wf = new WeakReference<HeartLayout>(layout);
 }

 @Override
 public void handleMessage(Message msg) {
  super.handleMessage(msg);
  HeartLayout layout = wf.get();
  if (layout == null) return;
  switch (msg.what) {
  case MSG_SHOW:
   addFavor();
   break;
  }
 }
 }

 public class HeartThread implements Runnable {

 private long time = 0;
 private int allSize = 0;

 public void addTask(long time, int size) {
  this.time = time;
  allSize += size;
 }

 public void clean() {
  allSize = 0;
 }

 @Override
 public void run() {
  if (heartHandler == null) return;

  if (allSize > 0) {
  heartHandler.sendEmptyMessage(HeartHandler.MSG_SHOW);
  allSize--;
  }
  postDelayed(this, time);
 }
 }

 private void init(AttributeSet attrs, int defStyleAttr) {
 final TypedArray a = getContext().obtainStyledAttributes(
  attrs, R.styleable.HeartLayout, defStyleAttr, 0);

 if (pointx <= initX && pointx >= 0) {
  pointx -= 10;
 } else if (pointx >= -initX && pointx <= 0) {
  pointx += 10;
 } else pointx = initX;


 mAnimator = new PathAnimator(AbstractPathAnimator.Config.fromTypeArray(a, initX, textHight, pointx, dWidth, dHeight));
 a.recycle();
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 //获取本身的宽高 这里要注意,测量之后才有宽高
 mWidth = getMeasuredWidth();
 mHeight = getMeasuredHeight();
 initX = mWidth / 2 - dWidth / 2;

 }

 public AbstractPathAnimator getAnimator() {
 return mAnimator;
 }

 public void setAnimator(AbstractPathAnimator animator) {
 clearAnimation();
 mAnimator = animator;
 }

 public void clearAnimation() {
 for (int i = 0; i < getChildCount(); i++) {
  getChildAt(i).clearAnimation();
 }
 removeAllViews();
 }

 private static int[] drawableIds = new int[]{R.drawable.heart0, R.drawable.heart1, R.drawable.heart2, R.drawable.heart3, R.drawable.heart4, R.drawable.heart5, R.drawable.heart6, R.drawable.heart7, R.drawable.heart8,};
 private Random random = new Random();

 public void addFavor() {
 HeartView heartView = new HeartView(getContext());
 heartView.setDrawable(drawableIds[random.nextInt(8)]);
 init(attrs, defStyleAttr);
 mAnimator.start(heartView, this);
 }

 private long nowTime, lastTime;
 final static int[] sizeTable = {9, 99, 999, 9999, 99999, 999999, 9999999,
  99999999, 999999999, Integer.MAX_VALUE};

 public static int sizeOfInt(int x) {
 for (int i = 0; ; i++)
  if (x <= sizeTable[i])
  return i + 1;
 }

 public void addFavor(int size) {
 switch (sizeOfInt(size)) {
  case 1:
  size = size % 10;
  break;
  default:
  size = size % 100;
 }
 if (size == 0) return;
 nowTime = System.currentTimeMillis();
 long time = nowTime - lastTime;
 if (lastTime == 0)
  time = 2 * 1000;//第一次分为2秒显示完

 time = time / (size + 15);
 if (heartThread == null) {
  heartThread = new HeartThread();
 }
 if (heartHandler == null) {
  heartHandler = new HeartHandler(this);
  heartHandler.post(heartThread);
 }
 heartThread.addTask(time, size);
 lastTime = nowTime;
 }

 public void addHeart(int color) {
 HeartView heartView = new HeartView(getContext());
 heartView.setColor(color);
 init(attrs, defStyleAttr);
 mAnimator.start(heartView, this);
 }

 public void addHeart(int color, int heartResId, int heartBorderResId) {
 HeartView heartView = new HeartView(getContext());
 heartView.setColorAndDrawables(color, heartResId, heartBorderResId);
 init(attrs, defStyleAttr);
 mAnimator.start(heartView, this);
 }

 @Override
 public void onClick(View v) {
 int i = v.getId();
 if (i == R.id.img) {
  if (onHearLayoutListener != null) {
  boolean isAdd = onHearLayoutListener.onAddFavor();
  if (isAdd) addFavor();
  }
 }
 }

 public void clean() {
 if (heartThread != null) {
  heartThread.clean();
 }
 }

 public void release() {
 if (heartHandler != null) {
  heartHandler.removeCallbacks(heartThread);
  heartThread = null;
  heartHandler = null;
 }
 }
}

5.飘心动画的使用

5.1 activity_heart_animal.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@color/grey"
  android:alpha="0.5">

 <TextView
  android:id="@+id/member_send_good"
  android:layout_width="40dp"
  android:layout_height="40dp"
  android:layout_gravity="center"
  android:layout_alignParentBottom="true"
  android:layout_alignParentRight="true"
  android:layout_marginRight="30dp"
  android:layout_marginBottom="10dp"
  android:background="@drawable/live_like_icon"
  />

 <!-- 飘心的路径 --> <com.myapplication2.app.newsdemo.view.heartview.HeartLayout
  android:id="@+id/heart_layout"
  android:layout_width="100dp"
  android:layout_height="wrap_content"
  android:layout_alignParentRight="true"
  android:layout_alignParentBottom="true"/>

</RelativeLayout>

5.2 activity 中的使用

heartLayout = (HeartLayout)findViewById(R.id.heart_layout);
 findViewById(R.id.member_send_good).setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
  heartLayout.addFavor();
  }
 });

heartLayout.addFavor(); 就是触发飘心动画效果的关键代码

6.参看资料

https://github.com/zhaoyang21cn/Android_Suixinbo

您可能感兴趣的文章:

相关文章