溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android自制精彩彈幕效果

發(fā)布時間:2020-08-29 04:23:54 來源:腳本之家 閱讀:161 作者:mChenys 欄目:移動開發(fā)

好久沒有寫過文章,最近發(fā)現(xiàn)直播特別的火,很多app都集成了直播的功能,發(fā)現(xiàn)有些直播是帶有彈幕的,效果還不錯,今天心血來潮,特地寫了篇制作彈幕的文章.

今天要實現(xiàn)的效果如下:

1.彈幕垂直方向固定

Android自制精彩彈幕效果

2.彈幕垂直方向隨機

Android自制精彩彈幕效果

上面效果圖中白色的背景就是彈幕本身,是一個自定義的FrameLayout,我這里是為了更好的展示彈幕的位置才設(shè)置成了白色,當(dāng)然如果是疊加在VideoView上的話,就需要設(shè)置成透明色了.
制作彈幕需要考慮以下幾點問題:
1.彈幕的大小可以隨意調(diào)整
2.彈幕內(nèi)移動的item(或者稱字幕)出現(xiàn)的位置,水平方向是從屏幕右邊移動到屏幕左邊,垂直方向是不能超出彈幕本身的高度的.
3.字幕移除屏幕后,需要將對應(yīng)item(字幕)從其父容器(彈幕)中移除.
4.如果字幕出現(xiàn)的垂直方向的高度是隨機的,那么還需要避免字幕重疊的情況.

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();
 //循環(huán)取出彈幕顯示
 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);
 }
 }
 }

 /**
 * 顯示彈幕,包括動畫的執(zhí)行
 *
 * @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);
 //設(shè)置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();
 }
 });
 }

 //記錄當(dāng)前仍在顯示狀態(tài)的彈幕的垂直方向位置(避免重復(fù))
 private Set<Integer> existMarginValues = new HashSet<>();
 private int linesCount;
 private int range = 10;

 private int getRandomTopMargin(View view) {
 //計算可用的行數(shù)
 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);
 }
 });
 //繼續(xù)添加
 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, "親,你想發(fā)送什么啊?", 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);
 }
 });
 }
}

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI