继承FrameLayout做自定义控件实现弹幕图层控件

      思路其实很简单,就是假定每条弹幕是一个TextView子类,然后添加到FrameLayout中,然后TextView子类有一个每次移动多少的量,用户可以设置这个数值moveStep控制弹幕的移动快慢。FrameLayout子类中有一个线程无线循环地把TextView子类的X坐标每次减去moveStep的值实现弹幕左移。由于安卓的View默认是OpenGL加速的,所以这套图层添加200个TextView弹幕也只是消耗百分之5的CPU,并不太耗费资源,非常丝滑。可以底层控件播放视频,顶层使用该控件即可做一个简单的弹幕Demo。

       理论上,你可以继承ImageView,或者直接继承View实现自定义控件,使得该弹幕可以支持表情弹幕、GIF弹幕等等。这只是我的一种尝试。

 

 

一、首先是TextView子类BarrageText,moveStep用于控制弹幕步进像素量

package cjz.project.barrageView;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * Created by cjz on 2019/5/7.
 */

public class BarrageText extends TextView{

    /**此条弹幕的移动速度**/
    private int moveStep = 2;



    public BarrageText(Context context) {
        super(context);
    }

    public BarrageText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public BarrageText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**此条弹幕的移动速度**/
    public int getMoveStep() {
        return moveStep;
    }

    /**设置此条弹幕的移动速度**/
    public void setMoveStep(int moveStep) {
        this.moveStep = moveStep;
    }
}

二、然后是弹幕容器BarrageView,用于控制弹幕移动,自动清理过多的弹幕以免资源消耗尽等等

package cjz.project.barrageView;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
/**
 * Created by cjz on 2019/5/7.
 */

public class BarrageView extends FrameLayout {

    /**最大弹幕容纳量**/
    private int container = 300;

    /**宏:触发滑动**/
    private final int SWEEP = 0;

    public BarrageView(Context context) {
        super(context);
    }

    public BarrageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public BarrageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**间接操作UI,匿名内部类覆盖重写一下就够用**/
    private class UIHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case SWEEP:
                    //如果同时存在于屏幕中的弹幕太多了,就清理掉最开始的百分之60
                    if(getChildCount() > container){
                        synchronized (this) {
                            removeViews(0, (int) (container * 0.6));
//                            removeAllViews();
                        }
                    }
                    //弹幕移动
                    for(int i = 0; i < getChildCount(); i++){
                       View view = getChildAt(i);
                       if(view instanceof BarrageText){
                           BarrageText barrageText = (BarrageText) view;
                           barrageText.setX(barrageText.getX() - barrageText.getMoveStep());
                           if(barrageText.getX() + barrageText.getWidth() < 0){
                               //Log.i("清理", barrageText.toString());
                               removeViewAt(i);
                               i--;
                           }
                       }
                    }
//                    Log.i("View数量", getChildCount() + "");
                    break;
            }
        }
    }
    private UIHandler handler = new UIHandler();


    /**是否保持轮播**/
    private boolean run = true;
    /**轮播线程**/
    private Loop loop = null;

    /**轮播线程**/
    private class Loop extends Thread {
        @Override
        public void run() {
            super.run();
            while (run) {
                try {
                    Thread.sleep(16);
                    //Log.i("looping", "looping");
                    handler.sendEmptyMessage(SWEEP);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            loop = null;
        }
    }



    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //检测本控件的依附情况
        this.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {

            //重新被依附就启动轮播线程
            @Override
            public void onViewAttachedToWindow(View v) {
                run = true;
                startLoop();
            }

            //脱离依附则关闭循环线程
            @Override
            public void onViewDetachedFromWindow(View v) {
                run = false;
                if(loop != null && loop.isAlive()){
                    try {
                        loop.interrupt();
                    } catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        });
    }


    /**轮播触发**/
    private void startLoop() {
        //初始化循环
        if (loop == null) {
            loop = new Loop();
        }
        if (!loop.isAlive()) {
            loop.start();
        }
    }




    /**添加弹幕**/
    public void addBarrageText(BarrageText barrageText){
        barrageText.setX(getWidth());
        addView(barrageText);
        startLoop();
    }


    /**可以容纳多少个弹幕**/
    public void setContainer(int container) {
        this.container = container;
    }
}

 

三、Demo Activity,添加随机字符作为弹幕,并以随机速度、随机色彩、随机字体大小插入到容器中:

package cjz.project.barrageView;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;

import java.util.Random;

import cjz.project.maptry.R;

/**
 * Created by cjz on 2019/5/7.
 */

public class MainActivity extends Activity {

    private BarrageView barrageView;
    private Handler handler = new Handler();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);
        barrageView = findViewById(R.id.barrageView);
        final Runnable barrageRunnable = new Runnable() {
            final Random random = new Random();
            @Override
            public void run() {
                BarrageText barrageText = new BarrageText(MainActivity.this);
                barrageText.setText(getRandomString(random.nextInt(15)));
                barrageText.setY(random.nextInt(1080));
                barrageText.setTextSize((int)(30 + Math.random()*(100 - 1 + 30)));
                barrageText.setMoveStep((int)(30 + Math.random()*(100 - 1 + 30)));
                barrageText.setTextColor((0xFF000000 | (random.nextInt(255) & 0xFF) << 16 | (random.nextInt(255) & 0xFF) << 8 | (random.nextInt(255) & 0xFF)));
                barrageView.addBarrageText(barrageText);
            }
        };
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    handler.post(barrageRunnable);
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }


    public String getRandomString(int length){
        String str="陈杰柱弹幕测试,好好玩哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈";
        Random random=new Random();
        StringBuffer sb=new StringBuffer();
        for(int i=0;i

四、布局文件:




    

    


五、测试效果:

继承FrameLayout做自定义控件实现弹幕图层控件_第1张图片

你可能感兴趣的:(安卓开发)