Android開發之APP彈幕功能
阿新 • • 發佈:2019-02-19
首先我們要整理一下思緒我們大概需要實現哪個細節板塊呢。
我們最直觀的看來,彈幕就是從右往左出現到消失。我們要實現這個動畫,彈幕的大小,顏色,出現方式,加速,彈幕的不重疊(這個我想了好久還沒有實現,有實現方法可以聯絡下我)。
我們先來了解一下等會程式裡面會用到的相關知識點,等會看程式碼會更輕鬆一點。
<span style="font-size:14px;">/*getHeight跟getMeasureHeight的區別
* 實際上在當螢幕可以包裹內容的時候,他們的值相等,只有當view超出屏幕後,才能看出他們的區別:
* getMeasuredHeight()是實際View的大小,與螢幕無關,而getHeight的大小此時則是螢幕的大小。
* 當超出屏幕後, getMeasuredHeight() 等於 getHeight()加上螢幕之外沒有顯示的大小
*
* */</span>
<span style="font-size:14px;">/Activity生命週期中,onStart, onResume, onCreate都不是真正visible的時間點,真正的visible時間點是onWindowFocusChanged()函式被執行時。
//當你螢幕的焦點發生變化時候,想要操作什麼也完全可以在這個方法裡面執行
// Interpolator 被用來修飾動畫效果,定義動畫的變化率,可以使存在的動畫效果accelerated(加速),decelerated(減速),repeated(重複),bounced(彈跳)等。
/*
* AccelerateDecelerateInterpolator 在動畫開始與結束的地方速率改變比較慢,在中間的時候加速
AccelerateInterpolator 在動畫開始的地方速率改變比較慢,然後開始加速
AnticipateInterpolator 開始的時候向後然後向前甩
AnticipateOvershootInterpolator 開始的時候向後然後向前甩一定值後返回最後的值
BounceInterpolator 動畫結束的時候彈起
CycleInterpolator 動畫迴圈播放特定的次數,速率改變沿著正弦曲線
DecelerateInterpolator 在動畫開始的地方快然後慢
LinearInterpolator 以常量速率改變
OvershootInterpolator 向前甩一定值後再回到原來位置
* fillBefore是指動畫結束時畫面停留在此動畫的第一幀;
fillAfter是指動畫結束是畫面停留在此動畫的最後一幀。
Java程式碼設定如下:
/*****動畫結束時,停留在最後一幀*********
setFillAfter(true);
setFillBefore(false);
/*****動畫結束時,停留在第一幀*********
setFillAfter(false);
setFillBefore(true);
*</span>
下面我們就來看一下彈幕實現的效果。
彈幕會出現重疊,這個問題還未解決
讓我們開始看程式碼結構吧。
我們字型顏色的xml都寫在了colors.xml中了,BarrageItem裡面存放著我們的一些變數,而核心程式碼都在View中
BraagetItem.java
<span style="font-size:14px;">package com.example.bibibibibibibibi;
import android.widget.TextView;
import android.widget.TextView;
/**
* Created by lixueyong on 16/2/19.
*/
public class BarrageItem {
public TextView textView;//文字框
public int textColor;//文字顏色
public String text;//文字物件
public int textSize;//文字的大小
public int moveSpeed;//移動速度
public int verticalPos;//垂直方向顯示的位置
public int textMeasuredWidth;//字型顯示佔據的寬度
}</span>
在BarrageItem裡面處理了彈幕的速度,大小,顏色,動畫,等事件 在這個檔案中 我註釋的內容是我對彈幕重疊的操作程式碼,但是除了問題,有興趣的可以看一下
package com.example.bibibibibibibibi;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import com.example.bibibibibibibibi.R.integer;
/**
* Created by nzx on 16/5/30.
*/
public class BarrageView extends RelativeLayout {
private Context mContext;
private BarrageHandler mHandler = new BarrageHandler();
private Random random = new Random(System.currentTimeMillis());//System.currentTimeMillis()產生一個當前的毫秒
private static final long BARRAGE_GAP_MIN_DURATION = 1000;//兩個彈幕的最小間隔時間
private static final long BARRAGE_GAP_MAX_DURATION = 2000;//兩個彈幕的最大間隔時間
private int maxSpeed = 10000;//速度,ms
private int minSpeed = 5000;//速度,ms
private int maxSize = 30;//文字大小,dp
private int minSize = 15;//文字大小,dp
private int totalHeight = 0;//整個的高度
private int lineHeight = 0;//每一行彈幕的高度
private int totalLine = 0;//彈幕的行數
private String[] itemText = {"大頭死變態", "老圩人最屌了", "唉這把中單是火男,難玩了", "大頭是傻子", "世界上最長的路是套路", "英雄聯盟最強的是補丁",
"我不會輕易的go die", "嘿嘿", "加班加班"};
private int textCount;//文字的組數
//private RelativeLayout Rparams;
// private List itemList = new ArrayList();
//實現RelativeLayout的重寫的構造方法。
/*
* //content 上下文
//AttributeSet 屬性集
//defStyleAttr 預設樣式屬性集
//defStyleRes 預設樣式資源屬性集
*
* */
public BarrageView(Context context) {
this(context, null);
}
public BarrageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BarrageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init();
}
private void init() {
textCount = itemText.length;
int duration = (int) ((BARRAGE_GAP_MAX_DURATION - BARRAGE_GAP_MIN_DURATION) * Math.random());
mHandler.sendEmptyMessageDelayed(0, duration);
}
@Override
//Activity生命週期中,onStart, onResume, onCreate都不是真正visible的時間點,真正的visible時間點是onWindowFocusChanged()函式被執行時。
//當你螢幕的焦點發生變化時候,想要操作什麼也完全可以在這個方法裡面執行。
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
totalHeight = getMeasuredHeight();
/*getHeight跟getMeasureHeight的區別
* 實際上在當螢幕可以包裹內容的時候,他們的值相等,只有當view超出屏幕後,才能看出他們的區別:
* getMeasuredHeight()是實際View的大小,與螢幕無關,而getHeight的大小此時則是螢幕的大小。
* 當超出屏幕後, getMeasuredHeight() 等於 getHeight()加上螢幕之外沒有顯示的大小
*
* */
//獲取每一行彈幕的最大高度
lineHeight = getLineHeight();
//我們整個彈幕的高度view/每一行的最大彈幕高度=
totalLine = totalHeight / lineHeight;
}
private void generateItem() {
BarrageItem item = new BarrageItem();
//把我們的每行彈幕的行數順序跟彈幕進行一個隨機
String tx = itemText[(int) (Math.random() * textCount)];
//隨機彈幕大小
int sz = (int) (minSize + (maxSize - minSize) * Math.random());
item.textView = new TextView(mContext);
item.textView.setText(tx);
item.textView.setTextSize(sz);
item.textView.setTextColor(Color.rgb(random.nextInt(256), random.nextInt(256), random.nextInt(256)));
//這裡我們需要傳入三個引數 文字物件,文字行數跟大小
item.textMeasuredWidth=(int) getTextWidth(item, tx, sz);
//這是設定彈幕移動速度,實現有快有慢的感覺
item.moveSpeed = (int) (minSpeed + (maxSpeed - minSpeed) * Math.random());
//這裡為了實現一個彈幕迴圈播放的專案,在我們實際中看情況而定
if (totalLine == 0) {
totalHeight = getMeasuredHeight();
lineHeight = getLineHeight();
totalLine = totalHeight / lineHeight;
}
//彈幕在y軸上出現的位置
item.verticalPos = random.nextInt(totalLine) * lineHeight;
// itemList.add(item);
showBarrageItem(item);
}
private void showBarrageItem(final BarrageItem item) {
//paddingLeft是設定佈局裡面的內容左邊的距離,這樣我們這就可以讓這個彈幕的textview完全消失
int leftMargin = this.getRight() - this.getLeft() - this.getPaddingLeft();
//這裡我們通過動態的方式去設定一些我們佈局的屬性。
// int verticalMargin = getRandomTopMargin();
// item.textView.setTag(verticalMargin);
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
params.topMargin = item.verticalPos;
this.addView(item.textView, params);
Animation anim = generateTranslateAnim(item, leftMargin);
anim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
//當我們動畫結束的時候,清除該條彈幕
public void onAnimationEnd(Animation animation) {
item.textView.clearAnimation();
BarrageView.this.removeView(item.textView);
}
@Override
//動畫被取消的時候出發
public void onAnimationRepeat(Animation animation) {
}
});
item.textView.startAnimation(anim);
}
//
private TranslateAnimation generateTranslateAnim(BarrageItem item, int leftMargin) {
//這裡我們有四個引數(動畫開始的x點,結束點,開始y軸點,結束的y點)
TranslateAnimation anim = new TranslateAnimation(leftMargin, -item.textMeasuredWidth, 0, 0);
//我們設定動畫的持續時間,彈幕移動多久,我們就持續多久動畫
anim.setDuration(item.moveSpeed);
// Interpolator 被用來修飾動畫效果,定義動畫的變化率,可以使存在的動畫效果accelerated(加速),decelerated(減速),repeated(重複),bounced(彈跳)等。
/*
* AccelerateDecelerateInterpolator 在動畫開始與結束的地方速率改變比較慢,在中間的時候加速
AccelerateInterpolator 在動畫開始的地方速率改變比較慢,然後開始加速
AnticipateInterpolator 開始的時候向後然後向前甩
AnticipateOvershootInterpolator 開始的時候向後然後向前甩一定值後返回最後的值
BounceInterpolator 動畫結束的時候彈起
CycleInterpolator 動畫迴圈播放特定的次數,速率改變沿著正弦曲線
DecelerateInterpolator 在動畫開始的地方快然後慢
LinearInterpolator 以常量速率改變
OvershootInterpolator 向前甩一定值後再回到原來位置
* */
anim.setInterpolator(new AccelerateDecelerateInterpolator());
/*
* fillBefore是指動畫結束時畫面停留在此動畫的第一幀;
fillAfter是指動畫結束是畫面停留在此動畫的最後一幀。
Java程式碼設定如下:
/*****動畫結束時,停留在最後一幀*********
setFillAfter(true);
setFillBefore(false);
/*****動畫結束時,停留在第一幀*********
setFillAfter(false);
setFillBefore(true);
*
* */
anim.setFillAfter(true);
return anim;
}
/**
* 計算TextView中字串的長度
*
* @param text 要計算的字串
* @param Size 字型大小
* @return TextView中字串的長度
*/
//因為我們的彈幕包裹在一個矩形中
public float getTextWidth(BarrageItem item, String text, float Size) {
Rect bounds = new Rect();
TextPaint paint;
paint = item.textView.getPaint();
//這裡引數是獲取文字物件,開始的長度,結束的長度,我們繪製好的矩形框
paint.getTextBounds(text, 0, text.length(), bounds);
return bounds.width();
}
/**
* 獲得每一行彈幕的最大高度
*
* @return
*/
private int getLineHeight() {
BarrageItem item = new BarrageItem();
String tx = itemText[0];
item.textView = new TextView(mContext);
item.textView.setText(tx);
item.textView.setTextSize(maxSize);
Rect bounds = new Rect();
TextPaint paint;
paint = item.textView.getPaint();
paint.getTextBounds(tx, 0, tx.length(), bounds);
return bounds.height();
}
class BarrageHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
generateItem();
//每個彈幕產生的間隔時間隨機
int duration = (int) ((BARRAGE_GAP_MAX_DURATION - BARRAGE_GAP_MIN_DURATION) * Math.random());
//多個訊息可以使用同一個handler, 通過what不同區分不同的訊息來源, 從而獲取訊息內容
this.sendEmptyMessageDelayed(0, duration);
}
}
//記錄一下當前在顯示彈幕的高度,避免彈幕出現重疊
private Set existMarginValues = new HashSet<>();
private int linesCount;
// private int getRandomTopMargin()
// {
// //計算彈幕的空間高度
// if(totalLine==0)
// {
// totalLine=Rparams.getBottom()-Rparams.getTop()-Rparams.getPaddingTop()
// -Rparams.getPaddingBottom();
// if (totalHeight==0) {
// totalHeight = getMeasuredHeight();
// lineHeight = getLineHeight();
// totalLine = totalHeight / lineHeight;
// }
// //檢查重疊
// while (true) {
// int randomIndex = (int) (Math.random() * linesCount);
// int marginValue = (int) (randomIndex * (totalLine / linesCount));
//
// if (!existMarginValues.contains(marginValue)) {
// existMarginValues.add(marginValue);
// return marginValue;
// }
// }
//
//
// }
}
BarrageActivity.java
在這個類裡面我們可以去進行一些事件,但是我這裡沒有去處理,大家按自己的需求來。
package com.example.bibibibibibibibi;
import android.app.Activity;
import android.os.Bundle;
/**
* Created by lixueyong on 16/2/19.
*/
public class BarrageActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_barrage);
}
}