前段时间在写直播的时候,需要观众在看直播的时候点赞的效果,在此参照了腾讯大神写的点赞(飘心动画效果)。下面是效果图:
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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。