android 循环弹幕,Android弹幕效果

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

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

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

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

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

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

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

publicclassDanmuView extendsFrameLayout {

privatestaticfinalString TAG = "DanmuView";

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

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

privateLinkedList 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 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 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 danmuList = newArrayList<>();

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

Danmu danmu = newDanmu();

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

}

});

}

}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值