/**
@作者 : 西野奈留
@博客:http://blog.csdn.net/narunishino
@聲明:本文僅在【CSDN 博客】發表。
*/
附:
- 本文是僅僅是對原文的部分內容添加解釋和小修改,要看原文請移步:http://blog.csdn.net/books1958/article/details/46375591
- 圖片來自於原文。
【一共5個類:MainActivity.java; TanmuBean.java; ScreenUtils.java; AnimationHelper.java; DecelerateAccelerateInterporator.java.】
【運行邏輯:
- 點擊按鈕。
- 新開一個『工作線程』。
- 在『工作線程』裏輪詢看看『有多少條彈幕』。
- 每隔500毫秒,『有多少條彈幕』,就給handler發送『多少條信息』。
- handlerMessage接收到一條信息後,就顯示一條動畫(彈幕)。
】
1.MainActivity.java;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Set;
public class MainActivity extends AppCompatActivity {
private MyHandler handler;
/**
* 彈幕內容
*/
private TanmuBean tanmuBean;
/**
* 放置彈幕內容的容器【containerVG】
*/
private RelativeLayout containerVG;
//【containerVG】的高度
private int validHeightSpace;
private View startTanmuView;
private FrameLayout frameLayout;
//-----------------------分隔符----------------------------
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFrameTest();
init();
/**
* 點擊按鈕後就開始彈幕
*/
startTanmuView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/**
* 容器裏面還有子view的話就return(do nothing)
*/
if (containerVG.getChildCount() > 0) {
return;
}
/**
* 清除【Set集合】裏面的所有數據;
*/
existMarginValues.clear();
/**
* 新開一個線程【工作線程】
*/
new Thread(new CreateTanmuThread()).start();
}
});
}
//-----------------------分隔符----------------------------
/**
* 這個方法是測試用的,沒有任務意味,可以刪除。
*/
private void initFrameTest() {
TextView textViewFrame = new TextView(this);
textViewFrame.setTextSize(30);
textViewFrame.setText("我是一個test啦");
textViewFrame.setTextColor(Color.parseColor("#000000"));
frameLayout = (FrameLayout) findViewById(R.id.frame_container_test);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
textViewFrame.setLayoutParams(lp);
Animation animation = new TranslateAnimation(1080, -1080, 0, 0);
animation.setDuration(3000);
animation.setRepeatCount(20);
textViewFrame.startAnimation(animation);
frameLayout.addView(textViewFrame);
}
//-----------------------分隔符----------------------------
/**
* 初始化
*/
private void init() {
containerVG = (RelativeLayout) findViewById(R.id.tanmu_container);
startTanmuView = findViewById(R.id.startTanmu);
handler = new MyHandler(this);
tanmuBean = new TanmuBean();
/**
* 彈幕的內容
*/
tanmuBean.setItems(new String[]{"I need your help.", "測試一下",
"彈幕這東西真不好做啊", "總是出現各種問題~~",
"我最長--------------------------" +
"-------------我最長",
"也不知道都是爲什麼?麻煩!", "哪位大神可以幫幫我啊?", "I need your help.",
"測試一下", "彈幕這東西真不好做啊", "總是出現各種問題~~", "也不知道都是爲什麼?麻煩!",
"哪位大神可以幫幫我啊?", "I need your help.",
"測試一下", "彈幕這東西真不好做啊", "總是出現各種問題~~",
"也不知道都是爲什麼?麻煩!", "哪位大神可以幫幫我啊?", "I need your help.", "測試一下",
"彈幕這東西真不好做啊", "總是出現各種問題~~",
"也不知道都是爲什麼?麻煩!", "哪位大神可以幫幫我啊?", "I need your help.",
"測試一下", "彈幕這東西真不好做啊", "總是出現各種問題~~", "也不知道都是爲什麼?麻煩!",
"哪位大神可以幫幫我啊?", "I need your help.",
"測試一下", "彈幕這東西真不好做啊", "總是出現各種問題~~",
"也不知道都是爲什麼?麻煩!", "哪位大神可以幫幫我啊?", "I need your help."});
}
//-----------------------分隔符----------------------------
private class CreateTanmuThread implements Runnable {
@Override
public void run() {
/**
* 通過【tanmuBean.getItems()】
* 獲得彈幕的內容
*/
int N = tanmuBean.getItems().length;
for (int i = 0; i < N; i++) {
/**
* public final Message obtainMessage (int what, int arg1, int arg2)
* 【obtainMessage().sendToTarget()】等同於【sendMessage()】,除了性能上的不同
* 作用:有多少條【彈幕】就給【handler】發送多少條【消息】
*/
handler.obtainMessage(1, i, 0).sendToTarget();
/**
* 類似【Thread.sleep(500)】;但是該方法會忽略【InterruptedException】
* 作用:每0.5s自動添加一條彈幕
*/
SystemClock.sleep(500);
}
}
}
//-----------------------分隔符----------------------------
private static class MyHandler extends Handler {
private WeakReference<MainActivity> ref;
MyHandler(MainActivity ac) {
ref = new WeakReference<>(ac);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
MainActivity ac = ref.get();
if (ac != null && ac.tanmuBean != null) {
int index = msg.arg1;
/**
* 要發送的彈幕的內容
*/
String content = ac.tanmuBean.getItems()[index];
/**
* Math.random()返回【0和1】之間的小數,
* 計算結果返回【16和24】之間的大小。
*/
float textSize = (float) (ac.tanmuBean.getMinTextSize() * (1 + Math.random() * ac.tanmuBean.getRange()));
/**
* 返回【灰色】
*/
int textColor = ac.tanmuBean.getColor();
ac.showTanmu(content, textSize, textColor);
}
}
}
}
//-----------------------分隔符----------------------------
private void showTanmu(String content, float textSize, int textColor) {
final TextView textView = new TextView(this);
textView.setTextSize(textSize);
textView.setText(content);
textView.setTextColor(textColor);
/**
* 【containerVG.getRight()】:【containerVG】的最右邊到它的【父控件】的最左邊的長度。
* 【containerVG.getPaddingLeft()】:【containerVG】這個控件有沒有【padding】,沒有的話就爲0.
* 結果:得到這個控件的寬度。
*/
int leftMargin = containerVG.getRight() - containerVG.getLeft() - containerVG.getPaddingLeft();
//計算本條彈幕的topMargin(隨機值,但是與屏幕中已有的不重複)
/**
* 【getRandomTopMargin()】returns 【marginValue】.
* 【marginValue】爲textView距離【containerVG】頂端的高度。
*/
int verticalMargin = getRandomTopMargin();
/**
* 在動畫那裏會用到【getTag】
*/
textView.setTag(verticalMargin);
/**
* LayoutParams(int w, int h)
* 這個【RelativeLayout】就是【containerVG】,因爲,請看【showTanmu方法】中最下面的代碼,
* 【containerVG.addView(textView)】:把【textView】add進了這個RelativeLayout中去了。
* 在【new RelativeLayout.LayoutParams()】設置的參數就是該控件(這裏是textView)而非RelativeLayout的參數。
*/
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams
(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
/**
* 【params.addRule(RelativeLayout.ALIGN_PARENT_TOP)】
* 這裏指【textView】與父佈局【relativeLayout】頂端對齊
*/
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
params.topMargin = verticalMargin;
/**
* 設置【textView】的參數
*/
textView.setLayoutParams(params);
textView.setGravity(Gravity.CENTER_HORIZONTAL);
/**
* 【leftMargin】指的是從控件【containerVG】的最右邊開始。
*/
Animation anim = AnimationHelper.createTranslateAnim(this, leftMargin, -ScreenUtils.getScreenW(this));
/**
* 【動畫基礎】可參考【http://www.imooc.com/video/7362】
*/
anim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
//移除該組件
containerVG.removeView(textView);
//移除佔位
int verticalMargin = (int) textView.getTag();
existMarginValues.remove(verticalMargin);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
textView.startAnimation(anim);
containerVG.addView(textView);
}
//-----------------------分隔符----------------------------
//記錄當前仍在顯示狀態的彈幕的位置(避免重複)
private Set<Integer> existMarginValues = new HashSet<>();
private int linesCount;
private int getRandomTopMargin() {
if (validHeightSpace == 0) {
/**
* 【containerVG.getBottom()】:【containerVG】的底部離它的父控件的頂部的距離。
* 結果:得到【containerVG】的高度。
*/
validHeightSpace = containerVG.getBottom() - containerVG.getTop()
- containerVG.getPaddingTop() - containerVG.getPaddingBottom();
}
if (linesCount == 0) {
/**
* 【tanmuBean.getMinTextSize() * (1 + tanmuBean.getRange())】=【16*1.5】=24
* 計算可用的行數【linesCount】
*/
linesCount = validHeightSpace / ScreenUtils.dp2px(this, tanmuBean.getMinTextSize() * (1 + tanmuBean.getRange()));
if (linesCount == 0) {
throw new RuntimeException("Not enough space to show text.");
}
}
//檢查重疊
while (true) {
/**
* 假設【linesCount】是5行,則【randomIndex】是隨機選到{0,1,2,3,4,5}中的其中一個(整數)。
* (int)使得小數變爲整數。例:1.X都等於1;0.X都等於0。
*/
int randomIndex = (int) (Math.random() * linesCount);
/**
* 【總高度】除以【行數】=【每行的高度】。
* 【每行的高度】乘以【隨機數】=【marginValue】
* 【marginValue】爲textView距離【containerVG】頂端的距離。
*/
int marginValue = randomIndex * (validHeightSpace / linesCount);
/**
* boolean contains(Object o) 如果此 set 包含指定元素,則返回 true。
* 【Set集合】【existMarginValues】裏面包含這個【marginValue長度】嗎,
* 如果不包含就可以把這個【長度】發給【TextView】
*/
if (!existMarginValues.contains(marginValue)) {
existMarginValues.add(marginValue);
return marginValue;
}
}
}
//-----------------------分隔符----------------------------
}
2.TanmuBean.java
import android.graphics.Color;
public class TanmuBean {
private String[] items;
private int color;
private int minTextSize;
private float range;
public TanmuBean() {
//init default value
color = Color.parseColor("#444444");
minTextSize = 16;
range = 0.5f;
}
public String[] getItems() {
return items;
}
public void setItems(String[] items) {
this.items = items;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public int getMinTextSize() {
return minTextSize;
}
/**
* 這個【方法】沒有用到,只是多出來的沒有刪掉而已
*/
public void setMinTextSize(int minTextSize) {
this.minTextSize = minTextSize;
}
public float getRange() {
return range;
}
/**
* 這個【方法】沒有用到,只是多出來的沒有刪掉而已
*/
public void setRange(float range) {
this.range = range;
}
}
3.ScreenUtils.java
import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
public class ScreenUtils {
private static int screenW;
private static int screenH;
private static float screenDensity;
public static int getScreenW(Context context) {
if (screenW == 0) {
initScreen(context);
}
/**
* 【screenW】是屏幕【寬度】
*/
return screenW;
}
public static int getScreenH(Context context) {
if (screenH == 0) {
initScreen(context);
}
return screenH;
}
public static float getScreenDensity(Context context) {
if (screenDensity == 0) {
initScreen(context);
}
return screenDensity;
}
public static void initScreen(Context context) {
DisplayMetrics metric = context.getResources().getDisplayMetrics();
screenW = metric.widthPixels;
screenH = metric.heightPixels;
screenDensity = metric.density;
}
/**
* 根據手機的屏幕密度從 dp 的單位 轉成爲 px(像素)
*/
public static int dp2px(Context context, float dpValue) {
return (int) (dpValue * getScreenDensity(context) + 0.5f);
}
/**
* 根據手機的屏幕密度從 px(像素) 的單位 轉成爲 dp
*/
public static int px2dp(Context context, float pxValue) {
return (int) (pxValue / getScreenDensity(context) + 0.5f);
}
}
4.AnimationHelper.java
import android.content.Context;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
/**
* 動畫工具類
*/
public class AnimationHelper {
/**
* 創建平移動畫
*/
public static Animation createTranslateAnim(Context context, int fromX, int toX) {
/**
* 第一個參數fromXDelta爲動畫起始時 X座標上的移動位置
* 第二個參數toXDelta爲動畫結束時 X座標上的移動位置
* 第三個參數fromYDelta爲動畫起始時Y座標上的移動位置
* 第四個參數toYDelta爲動畫結束時Y座標上的移動位置
* TranslateAnimation(float fromXDelta, float toXDelta,float fromYDelta, float toYDelta)
*/
TranslateAnimation tlAnim = new TranslateAnimation(fromX, toX, 0, 0);
/**
* 【setDuration()】動畫運行持續的時間。
* 【setInterpolator()】控制運行速度。
* 【setFillAfter()】讓View對象在動畫執行完畢後保留在終止位置。
*/
tlAnim.setDuration(4000);
tlAnim.setInterpolator(new DecelerateAccelerateInterpolator());
tlAnim.setFillAfter(true);
tlAnim.setFillEnabled(true);
return tlAnim;
}
}
5.DecelerateAccelerateInterporator.java
import android.view.animation.Interpolator;
/**
* 【Interpolator】是一個速度控制器,控制速度變化。
*/
public class DecelerateAccelerateInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
/**
* 把【input】return回去的話彈幕就是【勻速】從右到左。
*/
return input;
}
}
參考:
『參考了很多文章,不一一寫上,有需要的請聯繫補充。』
-2015/12/04-
-End-