編輯:關於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, 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; } } 測試類,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中,Broadcast是一種廣泛運用的在應用程序之間傳輸信息的機制。我們拿廣播電台來做個比方。我們平常使用收音機收音是這樣的:許許多多不同的廣播
先看一組加載效果圖,有點粉粉的加載圈: 自定義這樣的圓形加載圈還是比較簡單的,主要是用到Canvans的繪制文本,繪制圓和繪制圓弧的api:/** * 繪制圓 * @pa
寫在前面:本篇可能是手把手自定義view系列最後一篇了,實際上我也是一周前才開始真正接觸自定義view,通過這一周的練習,基本上已經熟練自定義view,能夠應對一般的vi
其實對於接觸過Android開發的人來說,視圖的滑動並不陌生,因為這一功能特性可以說是隨處可見。常用的就例如ScrollView、HorizontalScrollView