【第22期】观点:IT 行业加班,到底有没有价值?

【Android实战】弹幕效果

原创 2016年08月29日 11:54:42

学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
欢迎高手指点指点^_^

版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

android 中的弹幕效果

android 弹幕评论效果 分类: android2015-07-28 09:21 977人阅读 评论(0) 收藏 举报 android弹幕 纯粹按照自己的想法仿照b站的弹...

Android 手势检测实战 打造支持缩放平移的图片预览效果(下)

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39480503,本文出自:【张鸿洋的博客】 上一篇已经带大家实现了自由的放大缩小图片,简单介绍了下Matr

Android游戏开发示例——弹幕+战棋

下载地址(内含源码及完整项目工程): http://loon-simple.googlecode.com/files/barrage%2Bslg.7z 有段时间没更新博客了,所以小弟特意一次准备了两个游戏示例发出。 1、弹幕示例: 这是一个非常简单的弹幕游戏示例,采用触屏方式控制,不过

Android下 一个自定义VIEW实现简单的弹幕效果

Android下 一个自定义VIEW实现简单的弹幕效果 先上图: xml布局:

Android 手势检测实战 打造支持缩放平移的图片预览效果(上)

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39474553,本文出自:【张鸿洋的博客】1、背景 现在app中,图片预览功能肯定是少不了的,
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)