Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 教你親手打造酷炫的彈幕效果

Android 教你親手打造酷炫的彈幕效果

編輯:關於Android編程

公司的新產品上線需要添加的彈幕功能,於是花了一天時間寫了一個Demo。

效果實現如下:

\

一開始的思路是:

1、首先實現一個自定義的Layout,在其中獲得需要展示的彈幕數組,每個彈幕數組的項包括彈幕文本以及圖片Url地址。

2、在Layout內部使用Handler或者計時線程循環發送彈幕。

3、彈幕實現采用自定義彈幕View,配合動畫實現滾屏呈現。

總結之後發現主要的難點還是在彈幕的出現位置選擇以及彈幕如何確保及時銷毀上(我會說一開始調試的時候出現滿屏彈幕的華麗場景麼。。),以及如何實現組件的復用,並盡可能提高性能。還要注意一些需要實現的功能點:通過Url獲得圖片(可通過圖片緩存加載框架實現並替代)、防止彈幕堆疊(這個算法實現還是比較容易的)。

之後發現再寫個自定義的彈幕view(左邊一個圓形頭像右邊文字,外框橢圓背景透明)有點麻煩,於是采用了ListView裡item復用的思想,使用了一個Item布局輕松實現辣~

好在最近一直在寫公司新項目的界面,各種技巧運用得比較熟練,彈幕Demo的編寫全程沒有碰到什麼壓力,倒是最後忘記加網絡權限導致調試了半天。。。(哭泣)。

實現步驟:

1、實現主布局:

彈幕區域的位置是可以自己調整的,理論上來說可以安置在屏幕任一位置上。

barrageview_test.xml

 




    

2、實現彈幕item布局:

 

使用了自定義的圓圈類實現了圓形頭像的效果,網上一搜一大堆或者評論留言這裡就不填源碼占篇幅了。

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);
    }
}

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 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;
    }

}
實現的思路大概跟開頭的描述是一樣的,但是這還只是一個比較簡陋的雛形,有大量可模塊化的功能點進行擴充,

 

比如可設置的視頻時長,彈幕速率,彈幕字體顏色,彈幕排列方式等等等等都可以自己定制,

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved