美文网首页
Android弹幕效果

Android弹幕效果

作者: 杰子他爸 | 来源:发表于2019-08-02 16:25 被阅读0次

上面效果图中白色的背景就是弹幕本身,是一个自定义的FrameLayout,我这里是为了更好的展示弹幕的位置才设置成了白色,当然如果是叠加在VideoView上的话,就需要设置成透明色了.

制作弹幕需要考虑以下几点问题:

1.弹幕的大小可以随意调整

2.弹幕内移动的item(或者称字幕)出现的位置,水平方向是从屏幕右边移动到屏幕左边,垂直方向是不能超出弹幕本身的高度的.

3.字幕移除屏幕后,需要将对应item(字幕)从其父容器(弹幕)中移除.

4.如果字幕出现的垂直方向的高度是随机的,那么还需要避免字幕重叠的情况.

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

publicclassDanmuView extendsFrameLayout {

 privatestaticfinalString TAG = "DanmuView";

 privatestaticfinallongDEFAULT_ANIM_DURATION = 6000; //默认每个动画的播放时长

 privatestaticfinallongDEFAULT_QUERY_DURATION = 3000; //遍历弹幕的默认间隔

 privateLinkedList<View> mViews = newLinkedList<>();//弹幕队列

 privatebooleanisQuerying;

 privateintmWidth;//弹幕的宽度

 privateintmHeight;//弹幕的高度

 privateHandler mUIHandler = newHandler();

 privatebooleanTopDirectionFixed;//弹幕顶部的方向是否固定

 privateHandler mQueryHandler;

 privateintmTopGravity = Gravity.CENTER_VERTICAL;//顶部方向固定时的默认对齐方式

 publicvoidsetHeight(intheight) {

  mHeight = height;

 }

 publicvoidsetWidth(intwidth) {

  mWidth = width;

 }

 publicvoidsetTopGravity(intgravity) {

  this.mTopGravity = gravity;

 }

 publicvoidadd(List<Danmu> danmuList) {

  for(inti = 0; i < danmuList.size(); i++) {

   Danmu danmu = danmuList.get(i);

   addDanmuToQueue(danmu);

  }

 }

 publicvoidadd(Danmu danmu) {

  addDanmuToQueue(danmu);

 }

 publicDanmuView(Context context) {

  this(context, null);

 }

 publicDanmuView(Context context, AttributeSet attrs) {

  this(context, attrs, 0);

 }

 publicDanmuView(Context context, AttributeSet attrs, intdefStyleAttr) {

  super(context, attrs, defStyleAttr);

  HandlerThread thread = newHandlerThread("query");

  thread.start();

  //循环取出弹幕显示

  mQueryHandler = newHandler(thread.getLooper()) {

   @Override

   publicvoidhandleMessage(Message msg) {

    finalView view = mViews.poll();

    if(null!= view) {

     mUIHandler.post(newRunnable() {

      @Override

      publicvoidrun() {

       //添加弹幕

       showDanmu(view);

      }

     });

    }

    sendEmptyMessageDelayed(0, DEFAULT_QUERY_DURATION);

   }

  };

 }

 /**

  * 将要展示的弹幕添加到队列中

  *

  * @param danmu

  */

 privatevoidaddDanmuToQueue(Danmu danmu) {

  if(null!= danmu) {

   finalView 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(newCropCircleTransformation(getContext())).into(headerIv);

   view.measure(0, 0);

   //添加弹幕到队列中

   mViews.offerLast(view);

  }

 }

 /**

  * 播放弹幕

  *

  * @param topDirectionFixed 弹幕顶部的方向是否固定

  */

 publicvoidstartPlay(booleantopDirectionFixed) {

  this.TopDirectionFixed = topDirectionFixed;

  if(mWidth == 0|| mHeight == 0) {

   getViewTreeObserver().addOnGlobalLayoutListener(newViewTreeObserver.OnGlobalLayoutListener() {

    @SuppressLint("NewApi")

    @Override

    publicvoidonGlobalLayout() {

     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

  */

 privatevoidshowDanmu(finalView view) {

  isQuerying = true;

  Log.d(TAG, "mWidth:"+ mWidth + " mHeight:"+ mHeight);

  finalLayoutParams lp = newLayoutParams(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(newValueAnimator.AnimatorUpdateListener() {

   @Override

   publicvoidonAnimationUpdate(ValueAnimator animation) {

    lp.leftMargin = (int) animation.getAnimatedValue();

    view.setLayoutParams(lp);

   }

  });

  addView(view);//显示弹幕

  animator.setDuration(DEFAULT_ANIM_DURATION);

  animator.setInterpolator(newLinearInterpolator());

  animator.start();//开启动画

  animator.addListener(newAnimatorListenerAdapter() {

   @Override

   publicvoidonAnimationEnd(Animator animation) {

    view.clearAnimation();

    existMarginValues.remove(view.getTag());//移除已使用过的顶部边距

    removeView(view);//移除弹幕

    animation.cancel();

   }

  });

 }

 //记录当前仍在显示状态的弹幕的垂直方向位置(避免重复)

 privateSet<Integer> existMarginValues = newHashSet<>();

 privateintlinesCount;

 privateintrange = 10;

 privateintgetRandomTopMargin(View view) {

  //计算可用的行数

  linesCount = mHeight / view.getMeasuredHeight();

  if(linesCount <= 1) {

   linesCount = 1;

  }

  Log.d(TAG, "linesCount:"+ linesCount);

  //检查重叠

  while(true) {

   intrandomIndex = (int) (Math.random() * linesCount);

   intmarginValue = 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);

    returnmarginValue;

   }

  }

 }

}

弹幕实体类:

/**

 * Created by dell on 2016/9/28.

 */

publicclassDanmu {

 privateString headerUrl;//头像

 privateString userName;//昵称

 privateString info;//信息

 publicString getHeaderUrl() {

  returnheaderUrl;

 }

 publicvoidsetHeaderUrl(String headerUrl) {

  this.headerUrl = headerUrl;

 }

 publicString getUserName() {

  returnuserName;

 }

 publicvoidsetUserName(String userName) {

  this.userName = userName;

 }

 publicString getInfo() {

  returninfo;

 }

 publicvoidsetInfo(String info) {

  this.info = info;

 }

}

测试类,MainActivity

publicclassMainActivity extendsAppCompatActivity {

 DanmuView mDanmuView;

 EditText mMsgEdt;

 Button mSendBtn;

 Handler mDanmuAddHandler;

 booleancontinueAdd;

 intcounter;

 @Override

 protectedvoidonResume() {

  super.onResume();

  mDanmuView.startPlay(true);//true表示弹幕的垂直方向是固定的,false则随机

  continueAdd = true;

  mDanmuAddHandler.sendEmptyMessageDelayed(0, 6000);

 }

 @Override

 protectedvoidonPause() {

  super.onPause();

  continueAdd = false;

  mDanmuAddHandler.removeMessages(0);

 }

 @Override

 protectedvoidonCreate(Bundle savedInstanceState) {

  super.onCreate(savedInstanceState);

  setContentView(R.layout.activity_main);

  initView();

  initData();

  initListener();

 }

 privatevoidinitView() {

  mDanmuView = (DanmuView) findViewById(R.id.danmuView);

  mMsgEdt = (EditText) findViewById(R.id.edt_msg);

  mSendBtn = (Button) findViewById(R.id.btn_send);

 }

 privatevoidinitData() {

  List<Danmu> danmuList = newArrayList<>();

  for(inti = 0; i < 3; i++) {

   Danmu danmu = newDanmu();

   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 = newHandlerThread("send danmu");

  ht.start();

  mDanmuAddHandler = newHandler(ht.getLooper()) {

   @Override

   publicvoidhandleMessage(Message msg) {

    runOnUiThread(newRunnable() {

     @Override

     publicvoidrun() {

      Danmu danmu = newDanmu();

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

    }

   }

  };

 }

 privatevoidinitListener() {

  //手动添加

  mSendBtn.setOnClickListener(newView.OnClickListener() {

   @Override

   publicvoidonClick(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 = newDanmu();

    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弹幕效果

    上面效果图中白色的背景就是弹幕本身,是一个自定义的FrameLayout,我这里是为了更好的展示弹幕的位置才设置成...

  • iOS 弹幕效果

    iOS 弹幕效果 iOS 弹幕效果

  • 弹幕效果

    现在我们会经常看一些直播,会经常用一些直播软件,这些软件有一个很好的功能,就是我们可以在上面发送弹幕交流娱乐,今...

  • android弹幕效果实践demo

    目前主流的android直播平台好多,龙珠,B站,虎牙,斗鱼,熊猫TV.......,大多数玩过直播的人都知道可以...

  • 弹幕刷屏之术——Android无时间线弹幕实现

    弹幕刷屏之术——Android无时间线弹幕实现 标签(空格分隔): Android 今天我们来实现一种普通的弹幕,...

  • 各种弹幕实现研究

    1、弹幕效果 2、旋转效果 2.旋转木马 3、改变透明度效果 4、超链接弹幕效果 5、绘图+渐隐 效果 代码: 6...

  • Android弹幕功能实现,模仿斗鱼直播的弹幕效果

    如今直播行业确实是非常火爆啊,大大小小的公司都要涉足一下直播的领域,用斗鱼的话来讲,现在就是千播之战。而弹幕则无疑...

  • Flutter 实现虎牙/斗鱼 弹幕效果

    老孟导读:用Flutter实现弹幕功能,轻松实现虎牙、斗鱼的弹幕效果。 先来一张效果图: 实现原理 弹幕的实现原理...

  • Android弹幕实现现状与原理浅析

    弹幕实现对比 目前的弹幕实现方案,主要有以下几种实现方式。 Android View实现 通过Android已有的...

  • EasyBarrage-Android轻量级弹幕效果

    概述 EasyBarrage是Android平台的一种轻量级弹幕效果目前支持以下设置: 自定义字体颜色,支持随机颜...

网友评论

      本文标题:Android弹幕效果

      本文链接:https://www.haomeiwen.com/subject/ryoldctx.html