Android widget - 直播右下角点击刷礼物特效
Widget部件介绍

Android  widget  -  直播右下角点击刷礼物特效,自动以及手动点击锚点视图,添加礼物。新添加的礼物沿着贝塞尔曲线,随机选择加速、减速加速器进行运动。

软件架构

Android 礼物动画特效

使用说明及Demo

1.  主Activity布局XML里添加FavorLayout以及锚点View   

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white">

    <org.tcshare.widgets.FavorLayout
        android:id="@+id/flavorLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="bottom">
    </org.tcshare.widgets.FavorLayout>

    <TextView
        android:id="@+id/ancher"
        android:layout_width="160dp"
        android:layout_height="60dp"
        android:layout_gravity="bottom|right"
        android:layout_marginBottom="42dp"
        android:layout_marginRight="42dp"
        android:background="#99999999"
        android:text="锚点视图,浮动效果从该View中心点开始"/>
</FrameLayout>

2. 主Activity 完整演示代码如下

public class TCLiveLikeActivity extends AppCompatActivity {

    private FavorLayout favor;
    private final Handler handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what == 1){
                handler.sendEmptyMessageDelayed(1, 200);
                favor.addFavor();
            }
        }
    };

    @Override
    protected void onDestroy() {
        handler.removeMessages(1);
        super.onDestroy();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tc_live_like);

        favor = findViewById(R.id.flavorLayout);
        Resources res = getResources();
        List<Drawable> items = new ArrayList<Drawable>(){
            {
                add(ResourcesCompat.getDrawable(res, R.mipmap.gift_1, null));
                add(ResourcesCompat.getDrawable(res, R.mipmap.gift_2, null));
                add(ResourcesCompat.getDrawable(res, R.mipmap.gift_3, null));
                add(ResourcesCompat.getDrawable(res, R.mipmap.gift_4, null));
                add(ResourcesCompat.getDrawable(res, R.mipmap.gift_5, null));
                add(ResourcesCompat.getDrawable(res, R.mipmap.gift_6, null));
                add(ResourcesCompat.getDrawable(res, R.mipmap.gift_7, null));
                add(ResourcesCompat.getDrawable(res, R.mipmap.gift_8, null));
                add(ResourcesCompat.getDrawable(res, R.mipmap.gift_9, null));
                add(ResourcesCompat.getDrawable(res, R.mipmap.gift_10, null));
                add(ResourcesCompat.getDrawable(res, R.mipmap.gift_11, null));
            }
        };
        // 使用自定义的效果图标,默认使用♥形状
        favor.setFavors(items);
        // 设置效果图标,左右飘动的范围,以及终止点的范围
        favor.setFavorWidthHeight(100, 400);
        // 设置AnchorView,效果图标会从该AnchorView的中心点飘出
        favor.setAnchor(findViewById(R.id.ancher));


        // 自动添加效果图标
        handler.sendEmptyMessageDelayed(1, 200);

        // 手动点击添加
        findViewById(R.id.ancher).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                favor.addFavor();
            }
        });
    }
}

FavorLayout.java 源码文件

package org.tcshare.widgets;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import androidx.core.content.res.ResourcesCompat;

import org.tcshare.androidutils.R;
import org.tcshare.utils.DensityUtil;
import org.tcshare.utils.RandomUtils;

import java.util.ArrayList;
import java.util.List;

/**
 *
 */
public class FavorLayout extends RelativeLayout {
    private static final String TAG = FavorLayout.class.getSimpleName();

    private int iHeight = 120;
    private int iWidth = 120;
    private int mHeight;
    private int mWidth;

    private LayoutParams lp;
    private List<Drawable> loves;

    private final List<Interpolator> interpolates = new ArrayList<Interpolator>() {
        {
            add(new LinearInterpolator());
            //add(new AccelerateInterpolator());
            add(new DecelerateInterpolator());
            add(new AccelerateDecelerateInterpolator());
        }
    };
    private PointF startPoint = new PointF();
    private PointF anchorPoint;
    private View anchorView;
    private int favorWidth = -1, favorHeight = -1;
    private boolean stop = false;


    public FavorLayout(Context context) {
        super(context);
        init();
    }

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        startPoint.set((mWidth - iWidth) / 2f, mHeight - iHeight);
    }

    private void init() {
        //底部 并且 水平居中
        lp = new LayoutParams(iWidth, iHeight);
        lp.addRule(CENTER_HORIZONTAL, TRUE); //这里的TRUE 要注意 不是true
        lp.addRule(ALIGN_PARENT_BOTTOM, TRUE);

        Resources res = getResources();
        //初始化显示的图片
        loves = new ArrayList<Drawable>() {
            {
                add(ResourcesCompat.getDrawable(res, R.mipmap.love_a, null));
                add(ResourcesCompat.getDrawable(res, R.mipmap.love_b, null));
                add(ResourcesCompat.getDrawable(res, R.mipmap.love_c, null));
                add(ResourcesCompat.getDrawable(res, R.mipmap.love_d, null));
                add(ResourcesCompat.getDrawable(res, R.mipmap.love_e, null));
            }

        };

    }

    public void setAnchor(final View view) {
        anchorView = view;
        resetAnchorPoint();
    }

    private void resetAnchorPoint() {
        if (anchorView != null) {
            anchorView.post(new Runnable() {
                @Override
                public void run() {
                    int[] outLocation = new int[2];
                    anchorView.getLocationOnScreen(outLocation);
                    float x = outLocation[0] + (anchorView.getWidth() - iWidth) / 2f;
                    float y = outLocation[1] - (anchorView.getHeight() - iHeight) / 2f;
                    anchorPoint = new PointF(x, y);
                }
            });
        }
    }

    /**
     * 点赞
     * 对外暴露的方法
     */
    public void addFavor() {
        if (stop) {
            return;
        }
        ImageView imageView = new ImageView(getContext());
        // 随机选一个
        imageView.setImageDrawable(RandomUtils.getRandomElement(loves));

        if (anchorPoint == null) {
            imageView.setLayoutParams(lp);
        } else {
            imageView.setX(anchorPoint.x - getX());
            imageView.setY(anchorPoint.y - getY());
        }

        addView(imageView);
        Log.d(TAG, "addFavor: " + "add后子view数:" + getChildCount());

        Animator set = getAnimator(imageView);
        set.addListener(new AnimEndListener(imageView));
        set.start();

    }

    /**
     * 设置动画
     */
    private Animator getAnimator(View target) {
        AnimatorSet set = getEnterAnimator(target);

        ValueAnimator bezierValueAnimator = getBezierValueAnimator(target);

        AnimatorSet finalSet = new AnimatorSet();
        finalSet.playSequentially(set);
        finalSet.playSequentially(set, bezierValueAnimator);
        finalSet.setInterpolator(RandomUtils.getRandomElement(interpolates));//实现随机变速
        finalSet.setTarget(target);
        return finalSet;
    }


    /**
     * 设置初始动画
     * 渐变 并且横纵向放大
     */
    private AnimatorSet getEnterAnimator(final View target) {

        ObjectAnimator alpha = ObjectAnimator.ofFloat(target, View.ALPHA, 0.2f, 1f);
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X, 0.2f, 1f);
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y, 0.2f, 1f);
        AnimatorSet enter = new AnimatorSet();
        enter.setDuration(500);
        enter.setInterpolator(new LinearInterpolator());
        enter.playTogether(alpha, scaleX, scaleY);
        enter.setTarget(target);
        return enter;
    }

    public void setFavorWidthHeight(int width, int height) {
        this.favorWidth = DensityUtil.dp2px(getContext(), width);
        this.favorHeight = DensityUtil.dp2px(getContext(), height);
    }

    private PointF getPointLow() {
        PointF pointF = new PointF();
        if (anchorView != null && favorWidth != -1 && favorHeight != -1 && anchorPoint != null) {
            // 中心点
            float x = anchorPoint.x - getX();
            float y = anchorPoint.y - getY();
            pointF.x = x - favorWidth / 2f + RandomUtils.getRandomInt(favorWidth);
            pointF.y = y - favorHeight / 4f - RandomUtils.getRandomInt(favorHeight / 4);

        } else {
            //减去100 是为了控制 x轴活动范围
            pointF.x = RandomUtils.getRandomInt(mWidth - 100);
            //再Y轴上 为了确保第二个控制点 在第一个点之上,我把Y分成了上下两半
            pointF.y = RandomUtils.getRandomInt(mHeight - 100) / 2f;
        }

        return pointF;
    }

    private PointF getPointHeight() {
        PointF pointF = new PointF();
        if (anchorView != null && favorWidth != -1 && favorHeight != -1 && anchorPoint != null) {
            // 中心点
            float x = anchorPoint.x - getX();
            float y = anchorPoint.y - getY();
            pointF.x = x - favorWidth / 2f + RandomUtils.getRandomInt(favorWidth);
            pointF.y = y - favorHeight / 2f - RandomUtils.getRandomInt(favorHeight / 4);

        } else {
            pointF.x = RandomUtils.getRandomInt(mWidth - 100);
            pointF.y = RandomUtils.getRandomInt(mHeight - 100);
        }

        return pointF;
    }

    private PointF getEndPoint() {
        PointF pointF = new PointF();
        if (anchorView != null && favorWidth != -1 && favorHeight != -1 && anchorPoint != null) {
            // 中心点
            float x = anchorPoint.x - getX();
            float y = anchorPoint.y - getY();
            pointF.x = x - favorWidth / 2f + RandomUtils.getRandomInt(favorWidth);
            pointF.y = y - favorHeight;

        } else {
            pointF.set(RandomUtils.getRandomInt(getWidth()), 0);
        }

        return pointF;
    }

    /**
     * 获取贝塞尔曲线动画
     */
    private ValueAnimator getBezierValueAnimator(View target) {

        //初始化一个BezierEvaluator
        BezierEvaluator evaluator = new BezierEvaluator(getPointLow(), getPointHeight());

        // 起点固定,终点随机
        ValueAnimator animator = ValueAnimator.ofObject(evaluator,
                anchorPoint == null ? startPoint : new PointF(anchorPoint.x - getX(), anchorPoint.y - getY()),
                getEndPoint());
        animator.addUpdateListener(new BezierListener(target));
        animator.setTarget(target);
        animator.setDuration(3000);
        return animator;
    }


    /**
     * 设置点赞效果集合
     *
     * @param items 浮动的图片
     */
    public void setFavors(List<Drawable> items) {
        loves.clear();
        loves.addAll(items);
        if (items.size() == 0) {
            throw new UnsupportedOperationException("点赞效果图片不能为空");
        }

        this.iWidth = items.get(0).getIntrinsicWidth();
        this.iHeight = items.get(0).getIntrinsicHeight();
        startPoint = new PointF((mWidth - iWidth) / 2f, mHeight - iHeight);
        resetAnchorPoint();
    }

    public void setStat(boolean stop) {
        this.stop = stop;
    }


    private class AnimEndListener extends AnimatorListenerAdapter {
        private final View target;

        public AnimEndListener(View target) {
            this.target = target;
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            //因为不停的add 导致子view数量只增不减,所以在view动画结束后remove掉
            removeView((target));
            Log.v(TAG, "removeView后子view数:" + getChildCount());
        }
    }


}

widget 链接集合

 

 

 

评论列表: