产品需求:
1、 有暴击,暴击的时长为5秒
2、 有数据需要无限往里面添加,主播需要实时看到送礼物的情况
3、 当礼物被后面的数据顶上去时,如果有暴击,重新进入,然后叠加
3、当动画进入后才显示后面的数据(暂未实现)
实现效果:
大致的效果图如上,一般直播项目中使用的礼物框架都是类似的实现方式,不过有些像 *爱网
的动画是有一个队列的形式,而且没有暴击,当用户赠送的礼物过多时,会出现礼物不断的出现在上面;还有像*鱼直播App
实现的暴击和上面的类似,不过当礼物推上去之后是不会重新暴击的;我们的产品暴击时间比较长,目前的实现方式参考了市面上多个APP
的形式。
下面,我们对该需求进行拆分:
一、对象创建
简单的分析需求后,我们大致可以将该动画分为三个对象:
1、 礼物管理类,
2、 礼物布局类
3、 动画类
礼物管理类
该对象中,应该保存所有的礼物布局对象,控制其添加、删除;
礼物布局类
布局当然是显示当前礼物的数据:赠送人的头像、名字、接收人的名字、礼物的图片、暴击的数量等,礼物布局还有一个隐藏的功能是自身的暴击时间和隐藏的时间控制;当然,这两个时间是一样的,当有暴击的时候,取消隐藏的时间,然后暴击结束后又开始隐藏时间的倒计时。
动画类
控制布局的显示、隐藏、暴击的动画
二、 礼物管理 GiftControl.java
2.1 添加动态参数
首先,我们需要创建一个GiftControl
,添加大致的框架方法,不需要设置为单例类,因为单例引用可能导致界面的内存泄漏,所以我们直接设置构造方法传Context
进入即可:
public class GiftControl {
public GiftControl(Context context) {
mContext = context;
}
}
然后,作为礼物布局的存放管理者,还需要有一个布局容器来添加,所以,还需要从页面中传入一个ViewGroup
到里面,布局容器中还需要有一个礼物的数量,比如我上面最大的显示数量为2,这些都可以动态控制,所以增加一个方法设置这两个参数:
public class GiftControl {
public GiftControl(Context context) {
mContext = context;
}
/**
* @param giftLayoutParent 存放礼物控件的父容器
* @param giftMaxNum 礼物控件的数量
* @return
*/
public GiftControl setGiftLayout(LinearLayout giftLayoutParent, @NonNull int giftMaxNum) {
if (giftMaxNum <= 0) {
throw new IllegalArgumentException("GiftFrameLayout数量必须大于0");
}
if (giftLayoutParent.getChildCount() > 0) {
//如果父容器没有子孩子,就进行添加
return this;
}
mGiftLayoutParent = giftLayoutParent;
mGiftLayoutMaxNums = giftMaxNum;
LayoutTransition transition = new LayoutTransition();
transition.setAnimator(LayoutTransition.CHANGE_APPEARING,
transition.getAnimator(LayoutTransition.CHANGE_APPEARING));
transition.setAnimator(LayoutTransition.APPEARING,
transition.getAnimator(LayoutTransition.APPEARING));
transition.setAnimator(LayoutTransition.DISAPPEARING,
transition.getAnimator(LayoutTransition.CHANGE_APPEARING));
transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,
transition.getAnimator(LayoutTransition.DISAPPEARING));
mGiftLayoutParent.setLayoutTransition(transition);
return this;
}
}
上面,我们添加了两个参数设置,还给布局添加了 LayoutTransition
动画,关于LayoutTransition
动画的使用,官网对其进行了详细的解释使用:
https://developer.android.com/reference/android/animation/LayoutTransition
2.2 添加礼物数据、显示礼物布局
添加礼物
public class GiftControl {
public GiftControl(Context context) {
mContext = context;
}
/**
* @param giftLayoutParent 存放礼物控件的父容器
* @param giftMaxNum 礼物控件的数量
* @return
*/
public GiftControl setGiftLayout(LinearLayout giftLayoutParent, @NonNull int giftMaxNum) {
if (giftMaxNum <= 0) {
throw new IllegalArgumentException("GiftFrameLayout数量必须大于0");
}
if (giftLayoutParent.getChildCount() > 0) {
//如果父容器没有子孩子,就进行添加
return this;
}
mGiftLayoutParent = giftLayoutParent;
mGiftLayoutMaxNums = giftMaxNum;
LayoutTransition transition = new LayoutTransition();
transition.setAnimator(LayoutTransition.CHANGE_APPEARING,
transition.getAnimator(LayoutTransition.CHANGE_APPEARING));
transition.setAnimator(LayoutTransition.APPEARING,
transition.getAnimator(LayoutTransition.APPEARING));
transition.setAnimator(LayoutTransition.DISAPPEARING,
transition.getAnimator(LayoutTransition.CHANGE_APPEARING));
transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,
transition.getAnimator(LayoutTransition.DISAPPEARING));
mGiftLayoutParent.setLayoutTransition(transition);
return this;
}
/**
* 添加礼物数据
*/
public synchronized void loadGift(LiveGiftBean gift) {
showGift(gift);
}
private synchronized void showGift(LiveGiftBean giftBean) {
if (giftBean == null) {
return;
}
LiveGiftLayout giftLayout;
int childCount = mGiftLayoutParent.getChildCount();
Log.d(TAG, "showGift: 礼物布局的个数" + childCount);
//没有超过最大的礼物布局数量,可以继续添加礼物布局
giftLayout = new LiveGiftLayout(mContext);
giftLayout.setIndex(0);
//两个参数分别是layout_width,layout_height
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mGiftLayoutParent.getLayoutParams();
//这个就是添加其他属性的,这个是在父元素的底部。
lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
mGiftLayoutParent.addView(giftLayout);
boolean hasGift = giftLayout.setGift(giftBean);
if (hasGift) {
giftLayout.startAnimation(customAnim);
// 添加到view队列中
mLiveGiftLayouts.add(giftLayout);
}
}
}
添加数据,直接调用显示数据的方法,在里面,我们新建布局类,然后外层是一个RelativeLayout
将其规则设置为底部,因为上面是自底向上动画;
好了,动画管理类,简单的框架就是这样,后面就只需要往方法上叠加逻辑就好了。
三、 动画布局类 LiveGiftLayout.java
布局类是一个自定义的组合View
,布局是比较简单的,根据创建自定义View
的方式来即可:
public class LiveGiftLayout extends FrameLayout {
private LayoutInflater mInflater;
private Context mContext;
RelativeLayout mGiftItemContent;
ImageView mIvGift, mIvSenderHeader;
TextView mTvSenderName, mTvSenderInfo;
TextView mTvGiftNum;
private View rootView;
public LiveGiftLayout(Context context) {
this(context, null);
}
public LiveGiftLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LiveGiftLayout(Context context, AttributeSet attributeSet, @AttrRes int defStyleAttr) {
super(context, attributeSet, defStyleAttr);
mInflater = LayoutInflater.from(context);
mContext = context;
initView();
}
private void initView() {
rootView = mInflater.inflate(R.layout.item_live_voice_gift, null);
mGiftItemContent = rootView.findViewById(R.id.item_live_voice_gift_content);
mIvGift = rootView.findViewById(R.id.item_live_voice_gift_iv_gift);
mTvGiftNum = rootView.findViewById(R.id.item_live_voice_gift_tv_num);
mIvSenderHeader = rootView.findViewById(R.id.item_live_voice_gift_iv_header);
mTvSenderName = rootView.findViewById(R.id.item_live_voice_gift_tv_nickname);
mTvSenderInfo = rootView.findViewById(R.id.item_live_voice_gift_tv_info);
this.addView(rootView);
}
public boolean setGift(LiveGiftBean gift) {
if (gift == null) {
return false;
}
mGift = gift;
if (mGift.isCurrentStart()) {
mGiftCount = gift.getGiftCount() + mGift.getHitCombo();
} else {
mGiftCount = gift.getGiftCount();
}
if (!TextUtils.isEmpty(gift.getSendUserName())) {
mTvSenderName.setText(gift.getSendUserName());
}
if (!TextUtils.isEmpty(gift.getGiftId())) {
mTvSenderInfo.setText(gift.getGiftName());
}
// 设置头像
mIvSenderHeader.setImageDrawable(ContextCompat.getDrawable(mContext, gift.getSendUserPic()));
// 设置名字
String firstText = "送 ";
String beforeColor = "#2d2d2d";
String afterColor = "#ff3a72";
//创建SpannableStringBuilder,并添加前面文案
SpannableStringBuilder builder = new SpannableStringBuilder(firstText);
//设置前面的字体颜色
builder.setSpan(new ForegroundColorSpan(Color.parseColor(beforeColor)), 0, firstText.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
//追加后面文案
builder.append(gift.getRecevierUserName());
//设置后面的字体颜色
builder.setSpan(new ForegroundColorSpan(Color.parseColor(afterColor)), firstText.length(), builder.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
mTvSenderInfo.setText(builder);
// 设置礼物图片
mIvGift.setImageDrawable(ContextCompat.getDrawable(mContext, gift.getGiftPic()));
mTvGiftNum.setText("x " + (mGiftCount));
mCombo = mGiftCount;
return true;
}
}
上面,我们创建了一个自定义组合View
,因为在GiftControl
中显示layout
的时候,调用了setGift()
方法,所以我们在里面创建这个方法,目的是设置里面控件数据。
到现在,我们可以添加数据到控制类中,然后显示数据到ViewGroup
中,图片的话,自己脑补一下吧😊