【Android实战】弹幕效果

学Android也有一个多月了,发现弹幕效果很好玩。自己在网上看了一些别人的例子,然后自己动手写了一个,下面写出来分享一下我整个实现的思路。
实现的效果:
这里写图片描述

实现的思路:

1、自定义一个TextView,用来显示弹幕。
2、这个TextView可以从右到左移动。
3、弹幕文字颜色随机。
4、弹幕显示的高度(y轴坐标)随机,显示的区域在屏幕的范围内。
5、(可选)如果显示的弹幕数量不足X时候,弹幕主要是显示在屏幕上方。


详细的实现过程:

1、首先先把屏幕设置为横排,去掉标题,全屏打开。为了看的效果更佳,我把背景修改为黑色

<activity android:name=".MainActivity"
          android:screenOrientation="landscape"
          android:theme="@style/AppTheme">
  <intent-filter>
      <action android:name="android.intent.action.MAIN"/>
      <category android:name="android.intent.category.LAUNCHER"/>
   </intent-filter>
</activity>

对应的styles.xml文件如下:

 <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:windowBackground">@color/black</item>
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowNoTitle">true</item>
    </style>

2、获取弹幕数据
新建一个类专门来获取弹幕数据,我把数据放在assets文件夹下,所以只写了一个获取assets文件的方法,后面还可以加入读取数据库等方法。


public class ReaderBarrage {

    private Context context;

    public ReaderBarrage(Context context){
        this.context = context;
    }

    //读取assets文件夹数据
    public Vector<String> readerAssetsFolder(String foldername){
        try {
            InputStream in = context.getAssets().open(foldername);
            InputStreamReader isr = new InputStreamReader(in);
            BufferedReader br = new BufferedReader(isr);
            String line = "";
            final Vector<String> data = new Vector<String>();
            while ( (line = br.readLine()) != null){
                data.add(line);
            }
            br.close();
            isr.close();
            in.close();
            return data;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

3、自定义一个TextView,用来放字幕。
重写该类的onDraw()方法,在该方法内绘制弹幕

/**
 * 用来显示弹幕的View
 */
public class MyBarrageView extends TextView {

    private static final int TEXTSIZE = 50;     //字体大小
    private Paint paint;        //画笔
    private int posX;           //x坐标
    private int posY;           //y坐标       

    public MyBarrageView(Context context) {
        super(context);
        init();
    }

    private void init() {
        paint = new Paint(); 
        //设置字体颜色。
        paint.setColor(Color.RED);
        //设置弹幕字体大小
        paint.setTextSize(TEXTSIZE);
    }

 @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawText(getShowText(),posX,posY,paint); 
    }

 //获取要显示的文字
    private String getShowText(){
        if(getText() !=null && !getText().toString().isEmpty()){
            return getText().toString();
        }else{
            return getResources().getString(R.string.default_text);
        }
    }
}

因为弹幕是从右到左移动,所以:
弹幕的x轴坐标肯定就是屏幕宽度。
弹幕y轴坐标范围[0~屏幕高度值]内随机数。但是字体本身也有一定高度,为了让它能完整的显示出来,我们需要把它加上,所以弹幕y轴坐标值范围[字体高度~屏幕高度]。
所以,我们接下来需要先获取到屏幕的宽度和高度,由于屏幕宽度和高度是固体的,我把它放在MainActivity里面获取。

public class MainActivity extends Activity {

    public static int windowWidth;     //屏幕宽度
    public static int windowHeight;    //屏幕高度

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init(); 
    }

    private void init() {
        //获取到屏幕宽高
        Rect rect = new Rect();
        getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
        windowHeight = rect.height();
        windowWidth = rect.width();
    }

获取到了屏幕的高和宽,那我们可以进一步修改MyBarrageView类,设置弹幕显示的位置和颜色

public class MyBarrageView extends TextView {

    private Paint paint;
    private int posX;           //x坐标
    private int posY;           //y坐标,需要加上字体的大小,不然不能完整的显示出来
    private Random random;      //随机数
    private static final int TEXTSIZE = 50;     //字体大小
    private int color = Color.RED;      //字体颜色 

    public MyBarrageView(Context context) {
        super(context);
        init();
    }

    private void init() {
        paint = new Paint();
        random = new Random();

        //设置x坐标为屏幕宽度,因为是要从右边滚动。
        posX = MainActivity.windowWidth;
        //设置Y坐标为屏幕内高度的随机值,需要注意要加上字体的大小,保证字体能完整显示。
        //random内需要减掉字体大小,因为windowHeight + TEXTSIZE 超出屏幕范围了。
        posY = TEXTSIZE + random.nextInt(MainActivity.windowHeight - TEXTSIZE);

        //设置随机颜色
        color = Color.rgb(random.nextInt(256),random.nextInt(256),random.nextInt(256));
        paint.setColor(color);
        //设置弹幕字体大小
        paint.setTextSize(TEXTSIZE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawText(getShowText(),posX,posY,paint);
    } 
}

4、设置滚动
准备好弹幕的view之后,我们接下来写一个方法,让它动起来~~
在这里我通过线程来不断重绘该view,实现移动效果。

/**
* 滚动显示字幕的线程
*/
class RollThread extends Thread {

   @Override
   public void run() {
      while (true){ 
        //更新x轴坐标
        setPosX();
        //绘制图像
        postInvalidate();
        //延迟一些时间,不然动画一飞而过。
        try{
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //如果弹幕已经移出屏幕,那就退出循环
        if(StopRollThread()){
           System.out.println(getName() + " 线程停止");
           //把下面这段信息发送到ui线程内运行
           post(new Runnable() {
               @Override
               public void run() {
                  //从父类中移除该组件
                  ((ViewGroup) MyBarrageView.this.getParent()).removeView(MyBarrageView.this);
               }
           });
          break;
         }
    }
}

//判断是否停止线程
private boolean StopRollThread(){
   //如果x轴坐标小于等于字幕文字的宽度,那说明弹幕已经移动出屏幕了。
    if(posX <= -paint.measureText(getShowText())){
         return true;
     }
     return false;
}

//设置x轴坐标,给线程调用,每次减少相应值,达到移动的效果
public void setPosX(){
     posX -= 8;
}

该线程主要是做4件事:
(1)重新设置x轴坐标。(减少一定的值)
(2)重新绘制图像
(3)延迟一点时间,再次重复(1)方法。
(4)检查弹幕是否已移出屏幕,如果是,就退出循环,并移除组件。
需要注意的是,移除组件的操作是要在主线程中调用的,所以我这里通过post(Runnable)方法,把该操作发送到主线程中运行。

滚动效果基本是完成了,那我们现在可以把结合MainActivity来看一下效果了

public class MainActivity extends Activity {

  private Handler handler;
  private ViewGroup.LayoutParams lp;  //设置宽高全屏
  private static final int SLEEP_TIME = 800;    //设置两条信息之间发送的时间间隔。
  private int value = 0;             //字幕文件数组下标。


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init();
        getShowText();
    }

    private void init() {
        //获取到屏幕宽高
        Rect rect = new Rect();
        getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
        windowHeight = rect.height();
        windowWidth = rect.width();

        //设置宽高全屏
        lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        handler = new Handler();
    }

    public void getShowText(){
        ReaderBarrage rb = new ReaderBarrage(this);
        //读取弹幕数据
        final Vector<String> data = rb.readerAssetsFolder("BarrageMessage");
        if(data != null) {
            Runnable createBarrageView = new Runnable() {
                @Override
                public void run() { 
                   if (isStop) {
                        MyBarrageView myBarrageView = new MyBarrageView(MainActivity.this);
                        //传递字幕文字
                        myBarrageView.setText(data.get(value++));
                        //把控件添加到主页面上
                        addContentView(myBarrageView, lp);
                        //如果显示完数组内的数据,那就停止。
                        if (value >= data.size()) {
                            isOnPause = false;
                        }
                    }
                    //发送下一条消息等待时间。
                    handler.postDelayed(this, SLEEP_TIME);
                }
            };
            //将消息发到ui线程中
            handler.post(createBarrageView);
        }
    }

在上面方法中,先通过之前写好的读取数据的类,获取弹幕数据。然后把我们的弹幕View创建出来,通过setText()方法把字幕设置上去。然后通过 Activity中的addContentView()方法,把字幕View添加到主页面上。
通过 handler.postDelayed()方法,设置两条字幕之间的弹出间隔。

然后我们在onDraw()方法中,把我们的线程给启动起来,这样才能实现一个移动的效果

public class MyBarrageView extends TextView {
    private RollThread rollThread;      //滚动线程

。。。。
     protected void onDraw(Canvas canvas) {
            canvas.drawText(getShowText(),posX,posY,paint);
            if(rollThread == null){
                rollThread = new RollThread();
                rollThread.start();
            }
        }
}

然后运行程序,就可以得到刚开始时候的效果了。

5、进一步优化
当弹幕在运行过程中,用户按了home键。然后过了一段时间再返回到程序,就会发现一条弹幕也没有了!这是因为弹幕并没有自动暂停,在用户离开我们页面之后,弹幕的线程还是默默的在后台运行,直至结束。而这并不是我们想要的,我们想要的效果应该是当我们离开当前页面的时候,弹幕应该自动暂停,而当我们再次返回页面的时候,弹幕再次开始。
所以,我们可以设置一个标签,在线程运行时,先检查一下该标签。

 class RollThread extends Thread {

        private Object mPauseLock;      //线程锁
        private boolean mPauseFlag;     //标签:是否暂停

        public RollThread(){
            mPauseLock = new Object();
            mPauseFlag = false;
        }

        @Override
        public void run() {
            while (true){
                checkIsPause();
                 。。。。
            }
        }

        //检查当前进程是否被挂起
        private void checkIsPause() {
            synchronized (mPauseLock){
                if(mPauseFlag){
                    try {
                        System.out.println(getName() + "线程已经被挂起");
                        mPauseLock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        //暂定当前动画
        public void onPause(){
            synchronized (mPauseLock){
                mPauseFlag = true;
            }
        }

        //恢复线程。
        public void onResume(){
            synchronized (mPauseLock){
                mPauseFlag = false;
                mPauseLock.notifyAll();
                mPauseLock.notify();
                System.out.println(getName() + "线程已恢复!");
            }
        }
    }

方法是准备好了,但是还没找到监听当前页面是否在活动的方法呀。经过我不懈努力,终于让我找到了一个onWindowVisibilityChanged(int visibility)方法,当view在当前窗口显示的时候,visibility就等于Viw.VISIBLE,当view不显示在当前窗口时候,visibility就等于View.GONE。so,那就简单多了~


    /**
     * 该方法可以监听当前的屏幕是否可见
     * @param visibility
     */
    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        if(rollThread == null){
            return;
        }
        if(View.GONE == visibility){
            //如果不可见,那就把线程挂起
            rollThread.onPause();
        }else{
            rollThread.onResume();
        }
    }

当然,我们还可以设置,当弹幕数量少于xxx时候,弹幕只在屏幕上方显示就好了。首先在MainActivity中获取到弹幕数量

 public static int count_number = 0;  //保存有多少条字幕
。。。。。    
 count_number = data.size();

然后在弹幕View类中判断一下即可

//如果超过我们设置的弹幕数量,那就在全屏随机显示
if(MAXNUMBER < MainActivity.count_number){
   //设置Y坐标为屏幕内高度的随机值,需要注意要加上字体的大小,保证字体能完整显示
   posY = TEXTSIZE + random.nextInt(MainActivity.windowHeight - TEXTSIZE);
}else{
   //否则值在屏幕上方显示
    posY = TEXTSIZE + random.nextInt(MainActivity.windowHeight / 2 - TEXTSIZE );
}

源码地址:https://github.com/llxyhuang/XYBarrage
欢迎高手指点指点^_^

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/HY_Jue/article/details/52351763
文章标签: android 实战
个人分类: android实战
下一篇【Android实战】2048游戏
想对作者说点什么? 我来说一句

Android弹幕效果实现

2016年10月16日 875KB 下载

android开源弹幕库,强力推荐

2014年06月19日 1.62MB 下载

Android 弹幕效果

2016年09月21日 23.13MB 下载

Android实现视频弹幕效果功能

2015年06月10日 2.93MB 下载

PPT弹幕效果模板

2014年05月24日 1.91MB 下载

Android 弹幕 Demo

2015年08月28日 2.88MB 下载

腾讯云直播+聊天弹幕

2017年06月01日 18.02MB 下载

弹幕页面jsp

2015年12月23日 1KB 下载

没有更多推荐了,返回首页

关闭
关闭