編輯:關於Android編程
公司的新產品上線需要添加的彈幕功能,於是花了一天時間寫了一個Demo。
效果實現如下:

一開始的思路是:
1、首先實現一個自定義的Layout,在其中獲得需要展示的彈幕數組,每個彈幕數組的項包括彈幕文本以及圖片Url地址。
2、在Layout內部使用Handler或者計時線程循環發送彈幕。
3、彈幕實現采用自定義彈幕View,配合動畫實現滾屏呈現。
總結之後發現主要的難點還是在彈幕的出現位置選擇以及彈幕如何確保及時銷毀上(我會說一開始調試的時候出現滿屏彈幕的華麗場景麼。。),以及如何實現組件的復用,並盡可能提高性能。還要注意一些需要實現的功能點:通過Url獲得圖片(可通過圖片緩存加載框架實現並替代)、防止彈幕堆疊(這個算法實現還是比較容易的)。
之後發現再寫個自定義的彈幕view(左邊一個圓形頭像右邊文字,外框橢圓背景透明)有點麻煩,於是采用了ListView裡item復用的思想,使用了一個Item布局輕松實現辣~
好在最近一直在寫公司新項目的界面,各種技巧運用得比較熟練,彈幕Demo的編寫全程沒有碰到什麼壓力,倒是最後忘記加網絡權限導致調試了半天。。。(哭泣)。
實現步驟:
1、實現主布局:
彈幕區域的位置是可以自己調整的,理論上來說可以安置在屏幕任一位置上。
barrageview_test.xml
使用了自定義的圓圈類實現了圓形頭像的效果,網上一搜一大堆或者評論留言這裡就不填源碼占篇幅了。
barrageview_item.xml
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 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);
}
}
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 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 date1) {
date = date1;
IS_START = true;
}
//獲得視頻總時長
public void setTime(long time) {
alltime = time;
}
}
實現的思路大概跟開頭的描述是一樣的,但是這還只是一個比較簡陋的雛形,有大量可模塊化的功能點進行擴充,
比如可設置的視頻時長,彈幕速率,彈幕字體顏色,彈幕排列方式等等等等都可以自己定制,
Android studio 插件之 GsonFormat (自動生成javabean)
概述相信大家在做開發的過程中都寫過很多的javabean ,很多情況下 都是一個列表數據就是一個單獨的javabean,如果大家自己敲的話費時費力 還很容易敲錯。今天給大
實例解析Android ImageView的scaleType屬性
這篇隨筆將會簡單的記錄下ImageView這個控件的一些使用方法,以及其最重要的一個屬性: scaleTypeImageView這個控件是用來顯示圖片用的,例如我們可以通
Android性能優化——界面流暢度優化
序言首先流暢度不僅僅是受到代碼的影響。也會跟機器的硬件配置有關系。所以第一點需要明確的是,流暢度最低保證在哪個硬件配置之上。這樣有了一個基點之後,才能比較好明確優化目標。
解析Android開發優化之:對界面UI的優化詳解(二)
如果我們在每個xml文件中都把相同的布局都重寫一遍,一個是代碼冗余,可讀性很差;另一個是修改起來比較麻煩,對後期的修改和維護非常不利