需求
实现一个跟支付宝蚂蚁森林的弹幕一样的效果,每次增加一条弹幕就显示到末尾,每次只能显示一条,一条显示一多半的时候就立马显示下一条。
效果图如下
第一步:
将所有的childView摆放位置(摆放到屏幕外)
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int childCount = getChildCount();
if (childCount <= 0) {
return;
}
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
if (childView == null) {
return;
}
LayoutParams layoutParams = (LayoutParams) childView.getLayoutParams();
childView.layout(mScreenWidth, layoutParams.topMargin, mScreenWidth + childView.getMeasuredWidth(), layoutParams.topMargin + childView.getMeasuredHeight());
}
}
重写了onLayout方法,将所有的childView的位置设置为mScreenWidth+childView.getMeasuredWidth(),这样就保证了childView默认在屏幕外。
第二步:
添加要显示的弹幕view,获取view的宽高,开启平移属性动画,并且在属性动画执行完成的时候移除它,在动画的更新监听中发送消息给下一条要显示的view。
public void createBarrageItemView(final BarrageBean barrageBean) {
final View itemView = LayoutInflater.from(getContext()).inflate(R.layout.layout_barrage, null);
TextView tvBarrageContent = itemView.findViewById(R.id.id_tv_barrage_content);
tvBarrageContent.setText(barrageBean.getContent());
// 获取itemView的宽高
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
itemView.measure(widthMeasureSpec, heightMeasureSpec);
int width = itemView.getMeasuredWidth();
int height = itemView.getMeasuredHeight();
LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layoutParams.topMargin = 1 * height;
addView(itemView, layoutParams);
final float totalWidth = mScreenWidth + width; // 平移的宽度
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(itemView, "translationX", 0, -(totalWidth));
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setDuration(DURATION);
objectAnimator.start();
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override public void onAnimationEnd(Animator animation) {
removeView(itemView);
}
});
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
if (value < -totalWidth / 2 && !barrageBean.isStartTranslationX()) {
mBarrageQueue.pop();
barrageBean.setStartTranslationX(true);
mHandler.sendEmptyMessage(MSG_WAHT_1);
}
}
});
}
写完这些代码之后,我以为能成,结果点击按钮添加一条弹幕,没有显示,找了一会之后发现得在你的xml文件中加上 android:clipchildren=”false”这个属性,不显示childview的位置和大小。切记!
public class BarrageView extends RelativeLayout {
private final int MSG_WAHT_1 = 1;
private final int DURATION = 5000; // 时长
private int mScreenWidth; // 屏幕宽度
// private List<String> mBarrageList = new ArrayList<>(); // 总共有多少条弹幕
private ArrayDeque<BarrageBean> mBarrageQueue = new ArrayDeque<>(); // 总共有多少条弹幕
private Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) {
if (mBarrageQueue.isEmpty()) {
return;
}
BarrageBean barrageBean = mBarrageQueue.getFirst();
createBarrageItemView(barrageBean);
}
};
public BarrageView(@NonNull Context context) {
this(context, null);
}
public BarrageView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BarrageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mScreenWidth = ActivityUtil.getScreenWidth(context);
}
@Override public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
}
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int childCount = getChildCount();
if (childCount <= 0) {
return;
}
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
if (childView == null) {
return;
}
LayoutParams layoutParams = (LayoutParams) childView.getLayoutParams();
childView.layout(mScreenWidth, layoutParams.topMargin, mScreenWidth + childView.getMeasuredWidth(), layoutParams.topMargin + childView.getMeasuredHeight());
}
}
/***
* 设置数据
*
* @param list
* 集合数据
*/
public void setDatas(List<BarrageBean> list) {
if (list == null || list.isEmpty()) {
return;
}
mBarrageQueue.addAll(list);
if (mBarrageQueue.size() == list.size()) {
mHandler.sendEmptyMessageDelayed(MSG_WAHT_1, 0);
}
}
/***
* 如果当前弹幕集合为空,那么添加进来的弹幕就直接显示, 如果当前弹幕集合不为空,则直接添加到集合中即可
*
* @param barrageBean
*/
public void addBarrage(BarrageBean barrageBean) {
mBarrageQueue.add(barrageBean);
if (mBarrageQueue.size() == 1) {
mHandler.sendEmptyMessageDelayed(MSG_WAHT_1, 0);
}
}
/***
* 创建一个弹幕itemView
*
* @param barrageBean
* 内容
*/
public void createBarrageItemView(final BarrageBean barrageBean) {
final View itemView = LayoutInflater.from(getContext()).inflate(R.layout.layout_barrage, null);
TextView tvBarrageContent = itemView.findViewById(R.id.id_tv_barrage_content);
tvBarrageContent.setText(barrageBean.getContent());
// 获取itemView的宽高
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
itemView.measure(widthMeasureSpec, heightMeasureSpec);
int width = itemView.getMeasuredWidth();
int height = itemView.getMeasuredHeight();
LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layoutParams.topMargin = 1 * height;
addView(itemView, layoutParams);
final float totalWidth = mScreenWidth + width; // 平移的宽度
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(itemView, "translationX", 0, -(totalWidth));
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setDuration(DURATION);
objectAnimator.start();
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override public void onAnimationEnd(Animator animation) {
removeView(itemView);
}
});
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
if (value < -totalWidth / 2 && !barrageBean.isStartTranslationX()) {
mBarrageQueue.pop();
barrageBean.setStartTranslationX(true);
mHandler.sendEmptyMessage(MSG_WAHT_1);
}
}
});
}
@Override protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mHandler != null) {
mHandler.removeMessages(MSG_WAHT_1);
mHandler = null;
}
}
}
public class BarrageActivity extends Activity implements View.OnClickListener {
private BarrageView id_barrage;
private TextView id_tv_add;
private TextView id_tv_start;
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_barrage);
id_barrage = findViewById(R.id.id_barrage);
id_tv_add = findViewById(R.id.id_tv_add);
id_tv_start = findViewById(R.id.id_tv_start);
id_tv_add.setOnClickListener(this);
id_tv_start.setOnClickListener(this);
}
@Override public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.id_tv_add:
BarrageBean barrageBean2 = new BarrageBean();
barrageBean2.setContent("弹幕"+System.currentTimeMillis());
id_barrage.addBarrage(barrageBean2);
break;
case R.id.id_tv_start:
List<BarrageBean> list = new ArrayList<>();
BarrageBean barrageBean = null;
for (int i = 0; i < 10; i++) {
barrageBean = new BarrageBean();
barrageBean.setContent("弹幕"+i);
list.add(barrageBean);
}
id_barrage.setDatas(list);
break;
}
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<cms.wxj.community.view.BarrageView
android:id="@+id/id_barrage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/id_tv_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginRight="10dp"
android:background="@color/colorAccent"
android:gravity="center"
android:padding="10dp"
android:text="添加一个弹幕"
android:textColor="#ffffff" />
<TextView
android:id="@+id/id_tv_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:background="@color/colorAccent"
android:gravity="center"
android:padding="10dp"
android:text="开始播放弹幕"
android:textColor="#ffffff" />
</LinearLayout>
</LinearLayout>
参考博客地址
博客地址