Efecto de animación de corazón flotante de Android (me gusta en vivo)

Cuando estaba escribiendo la transmisión en vivo hace algún tiempo, se requería el efecto de los gustos de la audiencia mientras veía la transmisión en vivo, aquí me refiero a los gustos escritos por Tencent Great God (efecto de animación Piaoxin). La siguiente es la representación:

Escriba la descripción de la imagen aquí

1. Personaliza los atributos de la animación de corazón flotante

Agregar atributos personalizados en 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. Defina el valor predeterminado de Piaoxin

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 enteros.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. Definir controlador de animación de corazón flotante

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. Defina la interfaz Piaoxin

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 Disposición de la ruta de animación de Piaoxin

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. Uso de animación de corazón flotante

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 Uso en actividad

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 (); es el código clave para activar el efecto de animación de corazón flotante

6. Ver información

https://github.com/zhaoyang21cn/Android_Suixinbo

Supongo que te gusta

Origin blog.csdn.net/android_freshman/article/details/52370475
Recomendado
Clasificación