Android_彈幕_效果_學習

/**
@作者 : 西野奈留
@博客:http://blog.csdn.net/narunishino
@聲明:本文僅在【CSDN 博客】發表。
*/


附:

  1. 本文是僅僅是對原文的部分內容添加解釋和小修改,要看原文請移步:http://blog.csdn.net/books1958/article/details/46375591
  2. 圖片來自於原文。

圖片來自於原文

【一共5個類:MainActivity.java; TanmuBean.java; ScreenUtils.java; AnimationHelper.java; DecelerateAccelerateInterporator.java.】

【運行邏輯:

  1. 點擊按鈕。
  2. 新開一個『工作線程』。
  3. 在『工作線程』裏輪詢看看『有多少條彈幕』。
  4. 每隔500毫秒,『有多少條彈幕』,就給handler發送『多少條信息』。
  5. 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-


發佈了38 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章