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

作者:android_小路 时间:2023-05-28 09:45:31 

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

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

来源:https://blog.csdn.net/android_freshman/article/details/52370475

标签:android,直播,点赞
0
投稿

猜你喜欢

  • Java 8 Function函数式接口及函数式接口实例

    2022-04-13 14:55:05
  • 自定义BufferedReader的实例

    2021-06-10 08:13:39
  • Java日常练习题,每天进步一点点(58)

    2021-06-26 01:13:02
  • JVM分配和回收堆外内存的方式与注意点

    2021-07-25 08:53:34
  • JAVA算法起步之插入排序实例

    2021-05-30 15:52:30
  • 解读赫夫曼树编码的问题

    2022-03-13 06:37:26
  • 浅析java中next与nextLine用法对比

    2022-01-11 01:02:53
  • Spring Boot非Web项目运行的方法

    2021-07-23 13:05:32
  • Unity3D UI Text得分数字增加的实例代码

    2021-11-13 21:03:10
  • Java多线程之同步工具类CountDownLatch

    2023-10-23 22:17:51
  • Android判断11位手机号码的方法(正则表达式)

    2022-03-06 03:17:43
  • Android 下的 QuickJS Binding 库特性使用详解

    2022-08-08 00:24:24
  • c# Newtonsoft 六个值得使用的特性(下)

    2022-02-18 11:38:32
  • Android实现歌词滚动效果

    2023-11-08 04:29:54
  • spring MVC实践需要注意的地方

    2023-02-07 00:33:07
  • Java实现中文算数验证码的实现示例(算数运算+-*/)

    2023-10-23 03:08:09
  • C#实现合并及拆分PDF文件的方法

    2021-06-15 12:55:23
  • android实现扫码枪功能

    2022-08-28 21:33:35
  • SpringBoot Security权限控制自定义failureHandler实例

    2022-12-03 08:46:58
  • Java多线程工具篇BlockingQueue的详解

    2022-07-03 20:47:56
  • asp之家 软件编程 m.aspxhome.com