Android的简易弹幕


1.布局文件:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.skg.danmudemo.MainActivity">
    <!--自定义弹幕显示框控件-->
    <com.skg.danmudemo.DanMuView
        android:id="@+id/danmu"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/text"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:layout_weight="1"/>

        <Button
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="发送"/>

    </LinearLayout>
</LinearLayout>

2.主类: MainActivity

/**
 * @author ClamLaw
 * @time 2016/11/26  
 * @desc MainActivity
 */
public class MainActivity extends AppCompatActivity {

    private EditText mText;
    private List<String> mList = new ArrayList<>();
    private DanMuView mDanMuView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();//初始化
        initData();

    }

    private void initData() {
        String[] array = getResources().getStringArray(R.array.danmuList);//先获取部分数据
        for (int i = 0; i < array.length; i++) {
            mList.add(array[i]);
        }
        mDanMuView.setData(mList);//向弹幕框添加数据
        mDanMuView.startDanmu();//开始弹幕
    }

    //初始化
    private void initView() {
        mText = (EditText) findViewById(R.id.text);
        mDanMuView = (DanMuView) findViewById(R.id.danmu);
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {//发送按钮
            @Override
            public void onClick(View v) {
                //发送评论
                String str = mText.getText().toString();
                mDanMuView.insertDanmu(str);
            }
        });
    }
}

3.自定义弹幕显示框控件: DanMuView

/**
 * @author ClamLaw
 * @time 2016/11/26  19:01
 * @desc 自定义弹幕的view
 */
public class DanMuView extends RelativeLayout {
    private Context mContext;
    private View view;
    private RelativeLayout mContainerVG;
    private List<String> mList;
    //父组件的高度
    private int validHeightSpace;
    private Set existMarginValues = new HashSet<>();
    private int lastMarginValue;
    ExecutorService executorService = Executors.newFixedThreadPool(1);//线程池

    Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    int index = msg.arg1;
                    String data = mList.get(index);
                    showDanmu(data);
                    break;
            }
        }
    };

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

    public DanMuView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        view = LayoutInflater.from(context).inflate(R.layout.danmu_view, null, false);
        mContainerVG = (RelativeLayout) view.findViewById(R.id.danmu);
        mContainerVG.setBackgroundDrawable(null);
        addView(view);
    }

    /**
     * 添加数据到弹幕中
     *
     * @param list
     */
    public void setData(List<String> list) {
        this.mList = list;
    }

    /**
     * 用户发送评论弹幕
     *
     * @param data
     */
    public void insertDanmu(String data) {
        showDanmu(data);//显示弹幕
        mList.add(data);//将弹幕信息添加到集合的第一位
        startDanmu();//开始弹幕
    }

    /**
     * 开始弹幕
     */
    public void startDanmu() {
       existMarginValues.clear();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                for (int i =0 ;i<Integer.MAX_VALUE; i++){
                    int size = mList.size();
                    if (size==0){
                        break;
                    }
                    mHandler.obtainMessage(1,i%size,0).sendToTarget();
                    SystemClock.sleep(1000);
                }
            }
        });
    }

    /**
     * 显示弹幕
     */
    public void showDanmu(String data) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.item_danmu, null, false);
        final RelativeLayout danmu_container = (RelativeLayout) view.findViewById(R.id.danmu_container);
        TextView danmu_text = (TextView) view.findViewById(R.id.danmu_text);
        ImageView danmu_img = (ImageView)view.findViewById(R.id.danmu_img);
        String str = clipLongTextByChineseCount(data, 12);//评论内容长度限制
        danmu_img.setImageResource(R.drawable.deta_shoucz);
        danmu_text.setText(str);//设置弹幕
        danmu_container.setBackgroundResource(R.drawable.textview_bg);//设置背景

        int leftMargin = mContainerVG.getRight() - mContainerVG.getLeft() - mContainerVG.getPaddingLeft();
        //计算本条弹幕的topMargin(随机值,但是与屏幕中已有的不重复)
        int verticalMargin = getRandomTopMargin();

        LayoutParams params = new LayoutParams(
            LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        params.addRule(RelativeLayout.ALIGN_PARENT_TOP);//设置节点属性,上边缘
        params.topMargin = verticalMargin;

        danmu_container.setLayoutParams(params);

        //动画
        Animation anim = new AnimationHelper().createTranslateAnim(mContext, leftMargin, -getScreenWidth((Activity) mContext));
        //动画监听
        anim.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                //动画结束时移除控件
                mContainerVG.removeView(danmu_container);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });

        danmu_container.startAnimation(anim);//开始动画
        mContainerVG.addView(danmu_container);
    }

    private int getRandomTopMargin() {
        if (validHeightSpace == 0) {
            validHeightSpace = mContainerVG.getBottom() - mContainerVG.getTop()
                - mContainerVG.getPaddingBottom() - mContainerVG.getTop();
        }
        if (existMarginValues.size() == 3) {
            existMarginValues.clear();
            while (true) {
                int randomIndex = (int) (Math.random() * 5);
                int marginValue = randomIndex * (validHeightSpace / 5);
                if (lastMarginValue != marginValue) {
                    existMarginValues.add(marginValue);
                    return marginValue;
                }
            }
        } else {
            //检查重叠
            while (true) {
                int randomIndex = (int) (Math.random() * 5);
                int marginValue = randomIndex * (validHeightSpace / 5);

                if (!existMarginValues.contains(marginValue)) {
                    existMarginValues.add(marginValue);
                    if (existMarginValues.size() == 3) {
                        lastMarginValue = marginValue;
                    }
                    return marginValue;
                }
            }
        }
    }

    /**
     * 获取屏幕的宽度
     * @param context
     * @return
     */
    private int getScreenWidth(Context context) {
        WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        return manager.getDefaultDisplay().getWidth();
    }

    /**
     * 评论的长度限制
     * @param str
     * @param count
     * @return
     */
    public static String clipLongTextByChineseCount(String str, int count) {
        if (str != null) {
            final String encoding = "GBK";
            try {
                byte[] b = str.getBytes(encoding);
                if (b.length >= (count + 1) * 2) {
                    int end = count * 2;
                    String result = new String(b, 0, end, encoding);
                    if (str.indexOf(result) == -1) {
                        return new String(b, 0, end - 1, encoding) + "...";
                    }
                    return result + "...";
                }
            } catch (UnsupportedEncodingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
        return str;
    }

    //动画
    public class AnimationHelper{

        public AnimationHelper() {
        }
        //fromX 开始的位置 ,toX结束的位置
        public Animation createTranslateAnim(Context context, int fromX, int toX){
            TranslateAnimation translateAnimation = new TranslateAnimation(fromX, toX, 0, 0);
            int width = getScreenWidth((Activity)context);//获取屏幕的宽度
            //自动计算时间
            long duration = (long) (Math.abs(toX - fromX) * 1.0f / width * 6000);
            translateAnimation.setDuration(duration);//动画时间
           // translateAnimation.setInterpolator(new DecelerateAccelerateInterpolator());//动画速率
            translateAnimation.setFillAfter(true);//终止时停留最后一帧
            return translateAnimation;
        }
    }



    public class DecelerateAccelerateInterpolator implements Interpolator {
        @Override
        public float getInterpolation(float input) {
            return (float) (Math.tan((input * 2 - 1) / 4 * Math.PI)) / 2.0f + 0.5f;
        }
    }
}

4.弹幕容器布局:danmu_view.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <RelativeLayout
        android:id="@+id/danmu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

5.弹幕子控件布局:item_danmu.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:id="@+id/danmu_container"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
    <ImageView
        android:id="@+id/danmu_img"
        android:layout_width="25dp"
        android:layout_height="25dp"
        android:layout_marginLeft="1px"
        android:layout_marginTop="1px"/>
    <TextView
        android:id="@+id/danmu_text"
        android:textColor="@android:color/white"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:singleLine="true"
        android:layout_marginLeft="25dp"
        android:text="123123"
        android:textSize="20dp"/>
</RelativeLayout>

本Demo的源码下载链接:DanMuDemo.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,350评论 0 17
  • 学计算机的人就是喜欢折腾,在学校因为校园网的慢的出翔,时不时系统坏了或者同学之间传点儿稍大的资源因为网速学校断网...
    神奇_小天阅读 74,717评论 11 39
  • 海洋绕王国 王国在天上 天上有星星 星星藏海浪 海浪翻云朵 云朵漏月光 月光流花园 花园立国王 国王爱王后 王后自...
    白月明阅读 118评论 0 0
  • 导读 曾经有个朋友对我说,一个娃的妈,最在乎的是小孩;二个娃的妈,最在乎的才是妈妈自己。对于当时只有一个娃的我并不...
    Suven阅读 381评论 6 2