標籤:
公司的新產品上線需要添加的彈幕功能,於是花了一天時間寫了一個Demo。
效果實現如下:
一開始的思路是:
1、首先實現一個自訂的Layout,在其中獲得需要展示的彈幕數組,每個彈幕數組的項包括彈幕文本以及圖片Url地址。
2、在Layout內部使用Handler或者計時線程迴圈發送彈幕。
3、彈幕實現採用自訂彈幕View,配合動畫實現滾屏呈現。
總結之後發現主要的痛點還是在彈幕的出現位置選擇以及彈幕如何確保及時銷毀上(我會說一開始調試的時候出現滿屏彈幕的華麗情境麼。。),以及如何?組件的複用,並儘可能提高效能。還要注意一些需要實現的功能點:通過Url獲得圖片(可通過圖片緩衝載入架構實現並替代)、防止彈幕堆疊(這個演算法實現還是比較容易的)。
之後發現再寫個自訂的彈幕view(左邊一個圓形頭像右邊文字,外框橢圓背景透明)有點麻煩,於是採用了ListView裡item複用的思想,使用了一個Item布局輕鬆實現辣~
好在最近一直在寫公司新項目的介面,各種技巧運用得比較熟練,彈幕Demo的編寫全程沒有碰到什麼壓力,倒是最後忘記加網路許可權導致調試了半天。。。(哭泣)。
實現步驟:
1、實現主布局:
彈幕地區的位置是可以自己調整的,理論上來說可以安置在螢幕任一位置上。
barrageview_test.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <com.whale.nangua.toquan.view.BarrageView android:id="@+id/barrageview" android:background="@drawable/testbackground" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:layout_width="match_parent" android:layout_height="200dp"/></RelativeLayout>
2、實現彈幕item布局:
使用了自訂的圓圈類實現了圓形頭像的效果,網上一搜一大堆或者評論留言這裡就不填源碼占篇幅了。
barrageview_item.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:background="@drawable/barrage_shape" android:layout_width="wrap_content" android:layout_height="50dp"> <com.whale.nangua.toquan.view.NGNormalCircleImageView android:src="@drawable/pumpkin" android:id="@+id/barrageview_item_img" android:layout_width="50dp" android:layout_height="match_parent"/> <TextView android:maxEms="10" android:textColor="@android:color/white" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:gravity="center" android:text="測試彈幕" android:singleLine="true" android:id="@+id/barrageview_item_tv" android:layout_width="wrap_content" android:layout_height="match_parent"/></LinearLayout>
3、實現測試用的Activity
package com.whale.nangua.toquan;import android.app.Activity;import android.os.Bundle;import android.util.Log;import com.whale.nangua.toquan.bean.Barrage;import com.whale.nangua.toquan.view.BarrageView;import java.util.ArrayList;/** * Created by nangua on 2016/7/18. */public class TestAty extends Activity { BarrageView barrageview; ArrayList<Barrage> date; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.barrageview_test); initView(); } private void initView() { date = new ArrayList<>(); for (int i = 0; i < 100; i++) { date.add(new Barrage( "測試彈幕" + i, "http://pic.818today.com/imgsy/image/2016/0215/6359114592207963687677523.jpg")); } barrageview = (BarrageView) findViewById(R.id.barrageview); Log.d("xiaojingyu", date.size() + ""); barrageview.setSentenceList(date); }}
4、彈幕類BarrageView.java
package com.whale.nangua.toquan.view;import android.animation.Animator;import android.animation.ObjectAnimator;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.util.AttributeSet;import android.util.Log;import android.view.LayoutInflater;import android.widget.FrameLayout;import android.widget.LinearLayout;import android.widget.TextView;import com.whale.nangua.toquan.R;import com.whale.nangua.toquan.bean.Barrage;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.URL;import java.util.ArrayList;/** * 2秒一條 * 螢幕上同時存在5條 * Created by nangua on 2016/7/28. */public class BarrageView extends FrameLayout { private static ArrayList<Barrage> date = new ArrayList<>(); //資料 private int nowIndex = 0; //date的下標 private Bitmap nowBitmap; //當前圖片 int width; //控制項寬 int height; //控制項高 float scale; //像素密度 FrameLayout frameLayout; FrameLayout.LayoutParams tvParams; static boolean IS_START = false; //判斷是否開始 long alltime; //視頻總時間長度 long starttime; //開始時間 // LinearLayout layout; Handler handler = new Handler() { @Override public void handleMessage(Message msg) { Barrage barrage = (Barrage) msg.getData().getSerializable("barrage"); final LinearLayout layout = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.barrageview_item, null); layout.setLayoutParams(tvParams); //隨機獲得Y值 layout.setY(getRamdomY()); layout.setX(width + layout.getWidth()); //設定文字 TextView textView = (TextView) layout.findViewById(R.id.barrageview_item_tv); textView.setText(barrage.getBarrageInfo()); //設定圖片 NGNormalCircleImageView ngNormalCircleImageView = (NGNormalCircleImageView) layout.findViewById(R.id.barrageview_item_img); if (nowBitmap != null) { ngNormalCircleImageView.setImageBitmap(nowBitmap); } frameLayout.addView(layout); final ObjectAnimator anim = ObjectAnimator.ofFloat(layout, "translationX", -width); anim.setDuration(10000); //釋放資源 anim.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { anim.cancel(); layout.clearAnimation(); frameLayout.removeView(layout); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); anim.start(); } }; /** * 使用httprulconnection通過發送網路請求path獲得bitmap * @param path * @return */ public static Bitmap getBitmapFromUrl(String path) { try { //獲得url URL url = new URL(path); //開啟httprulconnection獲得執行個體 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //設定逾時時間 conn.setConnectTimeout(5000); //設定Get conn.setRequestMethod("GET"); //串連成功 if (conn.getResponseCode() == 200) { //獲得輸入資料流 InputStream inputStream = conn.getInputStream(); //得到bitmap Bitmap bitmap = BitmapFactory.decodeStream(inputStream); if (bitmap == null) { } //返回 return bitmap; } //錯誤資訊處理 } catch (Exception e) { //列印錯誤資訊 e.printStackTrace(); } return null; } int lastY;//上一次出現的Y值 /** * 獲得隨機的Y軸的值 * * @return */ private float getRamdomY() { int tempY; int rY; int result = 0; // height * 2 / 4 - 25 //首先隨機播放一條道路 int nowY = (int) (Math.random() * 3); switch (nowY) { case 0: nowY = avoidTheSameY(nowY,lastY); //第一條 tempY = height / 4 - 25; rY = (int) (Math.random() * height / 4); if (rY >= height / 8) { result = tempY + rY; } else { result = tempY - rY + 50 ; } lastY = nowY; break; case 1: nowY = avoidTheSameY(nowY,lastY); //第二條 tempY = height / 2 - 25; rY = (int) (Math.random() * height / 4); if (rY >= height / 8) { result = tempY + rY; } else { result = tempY - rY; } lastY = nowY; break; case 2: nowY = avoidTheSameY(nowY,lastY); //第三條 tempY = height * 3 / 4 - 25; rY = (int) (Math.random() * height / 4); if (rY >= height / 8) { result = tempY + rY -50; } else { result = tempY - rY; } lastY = nowY; break; } return result; } /** * 避免Y重合的方法 * @param lastY * @return */ private int avoidTheSameY(int nowY,int lastY) { if (nowY == lastY) { nowY ++; } if (nowY == 4) { nowY = 0; } return nowY; } public BarrageView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); width = getWidth(); //寬度 height = getHeight(); //高度 init(); } private void init() { setTime(600000); //設定初始時間長度,改完記得刪 starttime = System.currentTimeMillis(); scale = this.getResources().getDisplayMetrics().density; //獲得自身執行個體 frameLayout = (FrameLayout) findViewById(R.id.barrageview); tvParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, 50); if (IS_START) { //開始動畫線程 startBarrageView(); IS_START = false; } } public void startBarrageView() { //開啟線程發送彈幕 new Thread() { @Override public void run() { while ((System.currentTimeMillis() - starttime < alltime) && (nowIndex <= date.size() - 1) ){ try { nowBitmap = getBitmapFromUrl(date.get(nowIndex).getBarrageUrl()); Message message = new Message(); Bundle bundle = new Bundle(); bundle.putSerializable("barrage",date.get(nowIndex)); nowIndex ++; message.setData(bundle); handler.sendMessage(message); Thread.sleep((long) (Math.random() * 3000) + 1000); } catch (InterruptedException e) { e.printStackTrace(); } } return; } }.start(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); } //設定資料 public void setSentenceList(ArrayList<Barrage> date1) { date = date1; IS_START = true; } //獲得視頻總時間長度 public void setTime(long time) { alltime = time; }}
實現的思路大概跟開頭的描述是一樣的,但是這還只是一個比較簡陋的雛形,有大量可模組化的功能點進行擴充,
比如可設定的視頻時間長度,彈幕速率,彈幕字型顏色,彈幕相片順序等等等等都可以自己定製,
代碼注釋詳細思路清晰,不懂得評論@我。
然而明天還要上班,後天才放假,晚安了~
Android 教你親手打造酷炫的彈幕效果