仿直播送礼物控件

源码
点击添加子视图,并播放动画集合。其中包含子视图的贝塞尔曲线运动动画、放大动画和渐变动画。贝塞尔曲线运动动画通过值动画ValueAnimator和贝塞尔曲线工具类取点实现。
public class PraiseView extends FrameLayout implements View.OnClickListener, LifecycleObserver {private static PointF start;private float v;private int measuredWidth;private ArrayList<ImageView> imageViews;private ImageView view;private int count = 0;private ArrayList<Integer> resIds;private int minMultiplicator = 0;private int maxMultiplicator = 5;private int maxNumExpression = 5;private int initExpressionSize = dp2px(10);private int duration = 1500;private LayoutParams layoutParams;private AnimatorSetConfig config;public PraiseView(Context context) {this(context, null);}public PraiseView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public PraiseView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);if (context instanceof LifecycleOwner) {((LifecycleOwner) context).getLifecycle().addObserver(this);}Drawable drawable = null;int buttonW = dp2px(20);int buttonH = dp2px(20);boolean isShowButton = true;TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PraiseView);if (typedArray != null) {isShowButton = typedArray.getBoolean(R.styleable.PraiseView_isShowButton, true);buttonW = typedArray.getDimensionPixelSize(R.styleable.PraiseView_button_width, buttonW);buttonH = typedArray.getDimensionPixelSize(R.styleable.PraiseView_button_height, buttonH);initExpressionSize = typedArray.getDimensionPixelSize(R.styleable.PraiseView_expression_size, initExpressionSize);drawable = typedArray.getDrawable(R.styleable.PraiseView_button_icon_res);duration = typedArray.getInteger(R.styleable.PraiseView_anim_duration, duration);maxMultiplicator = typedArray.getInteger(R.styleable.PraiseView_multiplying_power, maxMultiplicator);//倍率(放大倍率)maxNumExpression = typedArray.getInteger(R.styleable.PraiseView_expression_max_num, maxNumExpression);//倍率(同一张资源图同时出现的最大数)//获取arrays.xml文件中图片资源集合int resourceId = typedArray.getResourceId(R.styleable.PraiseView_expression_list, -1);if (resourceId != -1) {resIds = new ArrayList<>();TypedArray array = getResources().obtainTypedArray(resourceId);for (int i = 0; i < array.length(); i++) {int resourceId1 = array.getResourceId(i, -1);if (resourceId1 != -1) {resIds.add(resourceId1);}array.recycle();}}typedArray.recycle();}view = new ImageView(context);layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);layoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;setButtonSize(buttonW, buttonH);view.setOnClickListener(this);if (drawable != null) {view.setImageDrawable(drawable);} else {view.setBackgroundColor(Color.RED);}imageViews = new ArrayList<>();addView(view);isShowButton(isShowButton);}/*** 设置按钮图片资源*/public void setButtonBgRes(@DrawableRes int res) {if (view != null) {view.setImageResource(res);}}/*** 是否显示按钮*/public void isShowButton(boolean isShow) {if (view != null) {view.setEnabled(isShow);view.setVisibility(isShow ? VISIBLE : GONE);}}/*** 设置按钮尺寸*/public void setButtonSize(int wdp, int hdp) {if (layoutParams != null && view != null) {layoutParams.height = dp2px(wdp);layoutParams.width = dp2px(hdp);view.setLayoutParams(layoutParams);}}/*** 设置动画图片资源*/public void setImageResIds(ArrayList<Integer> resIds) {this.resIds = resIds;}/*** 设置动画图片放大倍率*/public void setMaxMultiplicator(@IntRange(from = 1) int maxMultiplicator) {this.maxMultiplicator = maxMultiplicator;}/*** 设置动画图片最小显示倍率*/public void setMinMultiplicator(@IntRange(from = 0) int minMultiplicator) {this.minMultiplicator = minMultiplicator;}/*** 设置同一时间同一资源图片在页面的最大显示数量*/public void setMaxNumExpression(@IntRange(from = 1) int maxNumExpression) {this.maxNumExpression = maxNumExpression;}/*** 设置资源图片尺寸(最大显示尺寸= 资源图片尺寸*(放大倍率+最小显示倍率)*/public void setInitExpressionSize(int dp) {initExpressionSize = dp2px(dp);}/*** 自定义动画集合*/public void setAnimators(AnimatorSetConfig config) {this.config = config;}public void setDuration(int duration) {this.duration = duration;}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);measuredWidth = getMeasuredWidth();int measuredHeight = getMeasuredHeight();v = measuredHeight / 5f;start = new PointF(measuredWidth / 2f, measuredHeight - (view.getMeasuredHeight() / 2f));}/*** 播放动画*/public void playAnim() {if (resIds == null || resIds.size() <= 0) {return;}AnimatorSet animatorSet = null;if (imageViews.size() > count && imageViews.get(count) != null) {animatorSet = (AnimatorSet) imageViews.get(count).getTag(R.id.image_anim);} else {ImageView imageView = new ImageView(getContext());LayoutParams layoutParams = new LayoutParams(initExpressionSize, initExpressionSize);layoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;imageView.setLayoutParams(layoutParams);imageView.setImageResource(resIds.get(count % resIds.size()));imageViews.add(imageView);addView(imageView);animatorSet = new AnimatorSet();BaseBezierAnimUpdateListener myUpdateListener = null;if (config == null) {//贝塞尔曲线运动动画(含放大动画)ValueAnimator animator = ValueAnimator.ofFloat(0, 1);myUpdateListener = new BezierAnimUpdateListener(imageView);animator.addUpdateListener(myUpdateListener);animator.setInterpolator(new LinearInterpolator());//渐变动画ValueAnimator alpha = ValueAnimator.ofFloat(1, 0);alpha.addUpdateListener(new AlphaAnimUpdateListener(imageView));alpha.setInterpolator(new AccelerateInterpolator());animatorSet.playTogether(animator, alpha);} else {myUpdateListener = config.config(animatorSet, imageView);}animatorSet.setDuration(duration);imageView.setTag(R.id.image_anim, animatorSet);imageView.setTag(R.id.image_anim_update, myUpdateListener);}if (animatorSet != null && !animatorSet.isRunning()) {if (imageViews.get(count).getTag(R.id.image_anim_update) != null) {((BaseBezierAnimUpdateListener) imageViews.get(count).getTag(R.id.image_anim_update)).resettingPoint();}animatorSet.start();}count++;if (count >= resIds.size() * maxNumExpression) {count = 0;}}/*** 获取X轴随机点** @param y 轴位置*/public PointF getRandomP(float y) {return new PointF((float) Math.random() * measuredWidth, y);}@OnLifecycleEvent(Lifecycle.Event.ON_START)public void onStart() {isPauseAnim(false);}@OnLifecycleEvent(Lifecycle.Event.ON_STOP)public void onStop() {isPauseAnim(true);}@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)public void onDestroy() {for (ImageView image : imageViews) {if (image != null) {if (image.getTag(R.id.image_anim) != null) {((AnimatorSet) image.getTag(R.id.image_anim)).cancel();}}image = null;}imageViews.clear();imageViews = null;if (resIds != null) {resIds.clear();}if (getContext() instanceof LifecycleOwner) {((LifecycleOwner) getContext()).getLifecycle().removeObserver(this);}removeAllViews();}public int dp2px(float dpValue) {final float scale = getResources().getDisplayMetrics().density;return (int) (dpValue * scale + 0.5f);}private void isPauseAnim(boolean isPause) {for (ImageView image : imageViews) {if (image != null && image.getTag(R.id.image_anim) != null) {AnimatorSet animator = (AnimatorSet) image.getTag(R.id.image_anim);if (isPause) {if (animator.isRunning()) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {animator.pause();} else {animator.end();}}} else {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && animator.isPaused()) {animator.resume();}}}}}@Overridepublic void onClick(View v) {playAnim();}public interface AnimatorSetConfig {BaseBezierAnimUpdateListener config(AnimatorSet set, ImageView imageView);}private class AlphaAnimUpdateListener implements ValueAnimator.AnimatorUpdateListener {private ImageView imageView;public AlphaAnimUpdateListener(ImageView imageView) {this.imageView = imageView;}@Overridepublic void onAnimationUpdate(ValueAnimator animation) {imageView.setAlpha((Float) animation.getAnimatedValue());}}public abstract static class BaseBezierAnimUpdateListener implements ValueAnimator.AnimatorUpdateListener {private ImageView imageView;public BaseBezierAnimUpdateListener(ImageView imageView) {this.imageView = imageView;}@Overridepublic void onAnimationUpdate(ValueAnimator animation) {onAnimationUpdate(animation, start, imageView);}public abstract void onAnimationUpdate(ValueAnimator animation, PointF startP, ImageView imageView);public abstract void resettingPoint();}private class BezierAnimUpdateListener extends BaseBezierAnimUpdateListener {private PointF p1;private PointF p2;private PointF end;public BezierAnimUpdateListener(ImageView imageView) {super(imageView);resettingPoint();}@Overridepublic void resettingPoint() {p1 = getRandomP((float) Math.random() * v + (3 * v));p2 = getRandomP((float) Math.round(2) * v + (2 * v));end = getRandomP((float) Math.random() * v);}@Overridepublic void onAnimationUpdate(ValueAnimator animation, PointF startP, ImageView imageView) {float t = (float) animation.getAnimatedValue();PointF bezierPoint = BezierUtils.getBezierPoint(t, startP, p1, p2, end);imageView.setY(bezierPoint.y);imageView.setX(bezierPoint.x);imageView.setScaleY(t * maxMultiplicator + minMultiplicator);imageView.setScaleX(t * maxMultiplicator + minMultiplicator);}}
}
attr.xml
<declare-styleable name="PraiseView"><attr name="button_width" format="dimension" /><attr name="button_height" format="dimension" /><attr name="button_icon_res" format="reference" /><attr name="isShowButton" format="boolean" /><attr name="expression_list" format="reference" /><attr name="expression_max_num" format="integer" /><attr name="expression_size" format="dimension" /><attr name="anim_duration" format="integer" /><attr name="multiplying_power" format="integer" /></declare-styleable>
ids.xml
<resources><item name="image_anim" type="id"/><item name="image_anim_update" type="id"/>
</resources>
使用示例
<com.***.widget.PraiseViewandroid:id="@+id/praise_view"android:layout_width="100dp"android:layout_height="500dp"app:button_icon_res="@drawable/image_1" />
贝塞尔曲线