Android自制精彩弹幕效果

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

今天要实现的效果如下:

1.弹幕垂直方向固定

2.弹幕垂直方向随机

上面效果图中白色的背景就是弹幕本身,是一个自定义的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,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;
 }
}

测试类,MainActivity

public 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);
   }
  });
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

以上是来客网为你收集整理的Android自制精彩弹幕效果全部内容,希望文章能够帮你解决Android自制精彩弹幕效果所遇到的程序开发问题。

如果觉得来客网网站内容还不错,欢迎将来客网网站推荐给程序员好友。