首页 > 系统 > Android > 正文

Android自制精彩弹幕效果

2019-10-22 18:30:42
字体:
来源:转载
供稿:网友

好久没有写过文章,最近发现直播特别的火,很多app都集成了直播的功能,发现有些直播是带有弹幕的,效果还不错,今天心血来潮,特地写了篇制作弹幕的文章.

今天要实现的效果如下:

1.弹幕垂直方向固定

Android,弹幕

2.弹幕垂直方向随机

Android,弹幕

上面效果图中白色的背景就是弹幕本身,是一个自定义的FrameLayout,我这里是为了更好的展示弹幕的位置才设置成了白色,当然如果是叠加在VideoView上的话,就需要设置成透明色了.
制作弹幕需要考虑以下几点问题:
1.弹幕的大小可以随意调整
2.弹幕内移动的item(或者称字幕)出现的位置,水平方向是从屏幕右边移动到屏幕左边,垂直方向是不能超出弹幕本身的高度的.
3.字幕移除屏幕后,需要将对应item(字幕)从其父容器(弹幕)中移除.
4.如果字幕出现的垂直方向的高度是随机的,那么还需要避免字幕重叠的情况.

ok,下面是弹幕自定义view的代码:

 

/** * Created by dell on 2016/9/28. */public class DanmuView extends FrameLayout { private static final String TAG = "DanmuView"; private static final long DEFAULT_ANIM_DURATION = 6000; //默认每个动画的播放时长 private static final long DEFAULT_QUERY_DURATION = 3000; //遍历弹幕的默认间隔 private LinkedList<View> mViews = new LinkedList<>();//弹幕队列 private boolean isQuerying; private int mWidth;//弹幕的宽度 private int mHeight;//弹幕的高度 private Handler mUIHandler = new Handler(); private boolean TopDirectionFixed;//弹幕顶部的方向是否固定 private Handler mQueryHandler; private int mTopGravity = Gravity.CENTER_VERTICAL;//顶部方向固定时的默认对齐方式 public void setHeight(int height) {  mHeight = height; } public void setWidth(int width) {  mWidth = width; } public void setTopGravity(int gravity) {  this.mTopGravity = gravity; } public void add(List<Danmu> danmuList) {  for (int i = 0; i < danmuList.size(); i++) {   Danmu danmu = danmuList.get(i);   addDanmuToQueue(danmu);  } } public void add(Danmu danmu) {  addDanmuToQueue(danmu); } public DanmuView(Context context) {  this(context, null); } public DanmuView(Context context, AttributeSet attrs) {  this(context, attrs, 0); } public DanmuView(Context context, AttributeSet attrs, int defStyleAttr) {  super(context, attrs, defStyleAttr);  HandlerThread thread = new HandlerThread("query");  thread.start();  //循环取出弹幕显示  mQueryHandler = new Handler(thread.getLooper()) {   @Override   public void handleMessage(Message msg) {    final View view = mViews.poll();    if (null != view) {     mUIHandler.post(new Runnable() {      @Override      public void run() {       //添加弹幕       showDanmu(view);      }     });    }    sendEmptyMessageDelayed(0, DEFAULT_QUERY_DURATION);   }  }; } /**  * 将要展示的弹幕添加到队列中  *  * @param danmu  */ private void addDanmuToQueue(Danmu danmu) {  if (null != danmu) {   final View view = View.inflate(getContext(), R.layout.layout_danmu, null);   TextView usernameTv = (TextView) view.findViewById(R.id.tv_username);   TextView infoTv = (TextView) view.findViewById(R.id.tv_info);   ImageView headerIv = (ImageView) view.findViewById(R.id.iv_header);   usernameTv.setText(danmu.getUserName());//昵称   infoTv.setText(danmu.getInfo());//信息   Glide.with(getContext()).//头像     load(danmu.getHeaderUrl()).     transform(new CropCircleTransformation(getContext())).into(headerIv);   view.measure(0, 0);   //添加弹幕到队列中   mViews.offerLast(view);  } } /**  * 播放弹幕  *  * @param topDirectionFixed 弹幕顶部的方向是否固定  */ public void startPlay(boolean topDirectionFixed) {  this.TopDirectionFixed = topDirectionFixed;  if (mWidth == 0 || mHeight == 0) {   getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {    @SuppressLint("NewApi")    @Override    public void onGlobalLayout() {     getViewTreeObserver().removeOnGlobalLayoutListener(this);     if (mWidth == 0) mWidth = getWidth() - getPaddingLeft() - getPaddingRight();     if (mHeight == 0) mHeight = getHeight() - getPaddingTop() - getPaddingBottom();     if (!isQuerying) {      mQueryHandler.sendEmptyMessage(0);     }    }   });  } else {   if (!isQuerying) {    mQueryHandler.sendEmptyMessage(0);   }  } } /**  * 显示弹幕,包括动画的执行  *  * @param view  */ private void showDanmu(final View view) {  isQuerying = true;  Log.d(TAG, "mWidth:" + mWidth + " mHeight:" + mHeight);  final LayoutParams lp = new LayoutParams(view.getMeasuredWidth(), view.getMeasuredHeight());  lp.leftMargin = mWidth;  if (TopDirectionFixed) {   lp.gravity = mTopGravity | Gravity.LEFT;  } else {   lp.gravity = Gravity.LEFT | Gravity.TOP;   lp.topMargin = getRandomTopMargin(view);  }  view.setLayoutParams(lp);  view.setTag(lp.topMargin);  //设置item水平滚动的动画  ValueAnimator animator = ValueAnimator.ofInt(mWidth, -view.getMeasuredWidth());  animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {   @Override   public void onAnimationUpdate(ValueAnimator animation) {    lp.leftMargin = (int) animation.getAnimatedValue();    view.setLayoutParams(lp);   }  });  addView(view);//显示弹幕  animator.setDuration(DEFAULT_ANIM_DURATION);  animator.setInterpolator(new LinearInterpolator());  animator.start();//开启动画  animator.addListener(new AnimatorListenerAdapter() {   @Override   public void onAnimationEnd(Animator animation) {    view.clearAnimation();    existMarginValues.remove(view.getTag());//移除已使用过的顶部边距    removeView(view);//移除弹幕    animation.cancel();   }  }); } //记录当前仍在显示状态的弹幕的垂直方向位置(避免重复) private Set<Integer> existMarginValues = new HashSet<>(); private int linesCount; private int range = 10; private int getRandomTopMargin(View view) {  //计算可用的行数  linesCount = mHeight / view.getMeasuredHeight();  if (linesCount <= 1) {   linesCount = 1;  }  Log.d(TAG, "linesCount:" + linesCount);  //检查重叠  while (true) {   int randomIndex = (int) (Math.random() * linesCount);   int marginValue = randomIndex * (mHeight / linesCount);   //边界检查   if (marginValue > mHeight - view.getMeasuredHeight()) {    marginValue = mHeight - view.getMeasuredHeight() - range;   }   if (marginValue == 0) {    marginValue = range;   }   if (!existMarginValues.contains(marginValue)) {    existMarginValues.add(marginValue);    Log.d(TAG, "marginValue:" + marginValue);    return marginValue;   }  } }}

弹幕实体类:

 

/** * Created by dell on 2016/9/28. */public class Danmu { private String headerUrl;//头像 private String userName;//昵称 private String info;//信息 public String getHeaderUrl() {  return headerUrl; } public void setHeaderUrl(String headerUrl) {  this.headerUrl = headerUrl; } public String getUserName() {  return userName; } public void setUserName(String userName) {  this.userName = userName; } public String getInfo() {  return info; } public void setInfo(String info) {  this.info = info; }}测试类,MainActivitypublic class MainActivity extends AppCompatActivity { DanmuView mDanmuView; EditText mMsgEdt; Button mSendBtn; Handler mDanmuAddHandler; boolean continueAdd; int counter; @Override protected void onResume() {  super.onResume();  mDanmuView.startPlay(true);//true表示弹幕的垂直方向是固定的,false则随机  continueAdd = true;  mDanmuAddHandler.sendEmptyMessageDelayed(0, 6000); } @Override protected void onPause() {  super.onPause();  continueAdd = false;  mDanmuAddHandler.removeMessages(0); } @Override protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);  initView();  initData();  initListener(); } private void initView() {  mDanmuView = (DanmuView) findViewById(R.id.danmuView);  mMsgEdt = (EditText) findViewById(R.id.edt_msg);  mSendBtn = (Button) findViewById(R.id.btn_send); } private void initData() {  List<Danmu> danmuList = new ArrayList<>();  for (int i = 0; i < 3; i++) {   Danmu danmu = new Danmu();   danmu.setHeaderUrl("http://tupian.qqjay.com/tou3/2016/0725/cb00091099ffbf09f4861f2bbb5dd993.jpg");   danmu.setUserName("Mr.chen" + i);   danmu.setInfo("我是弹幕啊,不要问我为什么不可以那么长!!!");   danmuList.add(danmu);  }  mDanmuView.add(danmuList);  //下面是模拟每秒添加一个弹幕的过程  HandlerThread ht = new HandlerThread("send danmu");  ht.start();  mDanmuAddHandler = new Handler(ht.getLooper()) {   @Override   public void handleMessage(Message msg) {    runOnUiThread(new Runnable() {     @Override     public void run() {      Danmu danmu = new Danmu();      danmu.setHeaderUrl("http://tupian.qqjay.com/tou3/2016/0803/87a8b262a5edeff0e11f5f0ba24fb22f.jpg");      danmu.setUserName("Mr.new" + (counter++));      danmu.setInfo("新的弹幕啊!!!新的弹幕啊!!!新的弹幕啊!!!新的弹幕啊!!!");      mDanmuView.add(danmu);     }    });    //继续添加    if (continueAdd) {     sendEmptyMessageDelayed(0, 1000);    }   }  }; } private void initListener() {  //手动添加  mSendBtn.setOnClickListener(new View.OnClickListener() {   @Override   public void onClick(View v) {    String msg = mMsgEdt.getText().toString().trim();    if (TextUtils.isEmpty(msg)) {     Toast.makeText(MainActivity.this, "亲,你想发送什么啊?", Toast.LENGTH_SHORT).show();     return;    }    mMsgEdt.setText("");    Danmu danmu = new Danmu();    danmu.setHeaderUrl("http://img0.imgtn.bdimg.com/it/u=2198087564,4037394230&fm=11&gp=0.jpg");    danmu.setUserName("I'am good man");    danmu.setInfo("我是新人:" + msg);    mDanmuView.add(danmu);   }  }); }}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持VEVB武林网。


注:相关教程知识阅读请移步到Android开发频道。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表