纯粹依照自己的想法仿照b站的弹幕写的一个demo,不知道正确的姿势怎么样的。
首先。一条弹幕就是一个textview
public abstract class Danmu extends TextView{ private Context context; private int position;//弹幕的位置,在屏幕哪一行 public Danmu(Context context) { super(context); this.context=context; setSingleLine(); } public int getPosition() { return position; } public void setPosition(int position) { this.position = position; } public abstract void send(); }
将弹幕放在一个相对布局容器中
<RelativeLayout android:id="@+id/danmuContainer" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="3" />
当字数非常多时,会放不下全部文字,所以手动设置了容器的宽度
容器设置足够大就好
ViewGroup.LayoutParams lp=container.getLayoutParams(); lp.width=DensityUtils.sp2px(this,15)*100; container.setLayoutParams(lp);弹幕分为好几种这里做了普通的从右到左的。逆向的。还有在顶部和底部的
普通弹幕由两个TranslateAnimation完毕,第一个是当弹幕移动后空出足够多空间时通知其它弹幕能够跟在它后面,第二个动画完毕接下来的移出屏幕
public class NormalDanmu extends Danmu { private Animation animation0,animation1; private int fx0,tx0,fx1,tx1; private int duration0,duration1; private OnAnimationEndListener onAnimationEndListener; public interface OnAnimationEndListener { public void clearPosition();//第一个动画结束,将当前行设置为能够发送弹幕 public void animationEnd();//弹幕全然移出屏幕 } public NormalDanmu(Context context,int fx,int tx) { super(context); this.fx0=fx; this.tx0=Math.abs(fx)-Math.abs(tx)-100;//第一个动画结束位置,当尾部空出100像素时就能够通知其它弹幕跟上了 this.fx1=tx0; this.tx1=tx; duration0=2000*(Math.abs(tx0-fx0))/DensityUtils.getScreenW(context); duration1=2000*(Math.abs(tx1-fx1))/DensityUtils.getScreenW(context); initAnimation(); } private void initAnimation() { animation0=new TranslateAnimation(fx0,tx0,0,0); animation1=new TranslateAnimation(fx1,tx1,0,0); animation0.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { clearAnimation(); startAnimation(animation1); if (onAnimationEndListener!=null) { onAnimationEndListener.clearPosition(); } } @Override public void onAnimationRepeat(Animation animation) { } }); animation0.setFillAfter(true); animation0.setDuration(duration0); animation0.setInterpolator(new AccelerateInterpolator()); animation1.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { if(onAnimationEndListener!=null) { onAnimationEndListener.animationEnd(); } } @Override public void onAnimationRepeat(Animation animation) { } }); animation1.setFillAfter(true); animation1.setDuration(duration1); animation1.setInterpolator(new DecelerateInterpolator()); } public void setOnAnimationEndListener(OnAnimationEndListener onAnimationEndListener) { this.onAnimationEndListener=onAnimationEndListener; } @Override public void send() { startAnimation(animation0); } }
然后发送弹幕 final NormalDanmu danmu=new NormalDanmu(this,sWidth,(int) -paint.measureText(str));
swidth表示屏幕宽度。paint.measureText(str)是textview宽度,表示从最右端移动到左边全然移出屏幕
lp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
lp.topMargin=i*danmuHeight;
danmuHeight是一个textview的高度,这里设置放在容器的第i行
private void setDanmu() { String ss="按是按时按是android.os.BinderProx按是"; int ll=ss.length()*DensityUtils.sp2px(this,15); int ran= new Random().nextInt(ss.length()); String str=ss.substring(ran); final NormalDanmu danmu=new NormalDanmu(this,sWidth,(int) -paint.measureText(str)); danmu.setTextSize(15); danmu.setText(str); danmu.setOnAnimationEndListener(new NormalDanmu.OnAnimationEndListener() { @Override public void clearPosition() { sendPosition.put(danmu.getPosition(), false); } @Override public void animationEnd() { container.removeView(danmu); } }); for(int i=0;i<count;i++) { if(sendPosition.get(i)==false) { danmu.setPosition(i); RelativeLayout.LayoutParams lp=new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, danmuHeight); lp.addRule(RelativeLayout.ALIGN_PARENT_TOP); lp.topMargin=i*danmuHeight; danmu.setGravity(Gravity.CENTER); container.addView(danmu, lp); danmu.send(); sendPosition.put(i,true); break; } } }
逆向弹幕就是和普通弹幕移动方向不同其它全然一样
顶部和底部的弹幕主要就是显示几秒后再消失即可了比較简单
public class TopDanmu extends Danmu { private OnDisappearListener onDisappearListener; private int duration; private Handler handler=new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if(msg.what==1) { if(onDisappearListener!=null) { onDisappearListener.disappear(); } } } }; public TopDanmu(Context context,int duration) { super(context); this.duration=duration; } public interface OnDisappearListener { public void disappear(); } @Override public void send() { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(duration); handler.sendEmptyMessage(1); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } public void setOnDisappearListener(OnDisappearListener onDisappearListener ) { this.onDisappearListener=onDisappearListener; } }发送顶部弹幕
顶部弹幕要水平居中,这里的容器设置的宽度超过的屏幕大小。所以要手动计算弹幕的水平位置
int margin= (int) ((sWidth-paint.measureText(danmu.getText().toString()))/2);
private void setTopDanmu() { String ss="按是按时按是android.os.BinderProx按是"; int ran= new Random().nextInt(ss.length()); String str=ss.substring(ran); int ll=str.length()*DensityUtils.sp2px(this, 15); final TopDanmu danmu=new TopDanmu(this,2000); danmu.setTextSize(15); danmu.setText(str); danmu.setTextColor(Color.GREEN); danmu.setOnDisappearListener(new TopDanmu.OnDisappearListener() { @Override public void disappear() { container.removeView(danmu); topSendPosition.put(danmu.getPosition(), false); } }); for(int i=0;i<count;i++) { if(topSendPosition.get(i)==false) { danmu.setPosition(i); RelativeLayout.LayoutParams lp=new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, danmuHeight); lp.addRule(RelativeLayout.ALIGN_PARENT_TOP); int margin= (int) ((sWidth-paint.measureText(danmu.getText().toString()))/2); lp.topMargin=i*danmuHeight; lp.leftMargin=margin; danmu.setGravity(Gravity.CENTER); container.addView(danmu, lp); danmu.send(); topSendPosition.put(i,true); break; } } }