天天看點

android 彈幕評論效果

純粹按照自己的想法仿照b站的彈幕寫的一個demo,不知道正确的姿勢怎麼樣的。

demo下載下傳位址

首先,一條彈幕就是一個textview

public abstract class Danmu extends TextView{
    private Context context;
    private int position;//彈幕的位置,在螢幕哪一行

    public Danmu(Context context) {
        super(context);
        this.context=context;
        setSingleLine();
    }

    public int getPosition() {
        return position;
    }

    public void setPosition(int position) {
        this.position = position;
    }

    public abstract void send();


}
           

将彈幕放在一個相對布局容器中

<RelativeLayout
        android:id="@+id/danmuContainer"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="3" />
           

當字數很多時,會放不下所有文字,是以手動設定了容器的寬度

容器設定足夠大就好

ViewGroup.LayoutParams lp=container.getLayoutParams();
        lp.width=DensityUtils.sp2px(this,15)*100;
        container.setLayoutParams(lp);
           

彈幕分為好幾種這裡做了普通的從右到左的,逆向的,還有在頂部和底部的

普通彈幕由兩個TranslateAnimation完成,第一個是當彈幕移動後空出足夠多空間時通知其他彈幕可以跟在它後面,第二個動畫完成接下來的移出螢幕

public class NormalDanmu extends Danmu {
    private Animation animation0,animation1;
    private int fx0,tx0,fx1,tx1;
    private int duration0,duration1;
    private OnAnimationEndListener onAnimationEndListener;

    public interface OnAnimationEndListener
    {
        public void clearPosition();//第一個動畫結束,将目前行設定為可以發送彈幕
        public void animationEnd();//彈幕完全移出螢幕
    }

    public NormalDanmu(Context context,int fx,int tx)
    {
        super(context);
        this.fx0=fx;
        this.tx0=Math.abs(fx)-Math.abs(tx)-100;//第一個動畫結束位置,當尾部空出100像素時就可以通知其他彈幕跟上了
        this.fx1=tx0;
        this.tx1=tx;

        duration0=2000*(Math.abs(tx0-fx0))/DensityUtils.getScreenW(context);
        duration1=2000*(Math.abs(tx1-fx1))/DensityUtils.getScreenW(context);

        initAnimation();
    }

    private void initAnimation()
    {
        animation0=new TranslateAnimation(fx0,tx0,0,0);
        animation1=new TranslateAnimation(fx1,tx1,0,0);
        animation0.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {

                clearAnimation();
                startAnimation(animation1);
                if (onAnimationEndListener!=null)
                {
                    onAnimationEndListener.clearPosition();
                }
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });

        animation0.setFillAfter(true);
        animation0.setDuration(duration0);
        animation0.setInterpolator(new AccelerateInterpolator());

        animation1.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {

                if(onAnimationEndListener!=null)
                {
                    onAnimationEndListener.animationEnd();
                }

            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });

        animation1.setFillAfter(true);
        animation1.setDuration(duration1);
        animation1.setInterpolator(new DecelerateInterpolator());
    }

    public void setOnAnimationEndListener(OnAnimationEndListener onAnimationEndListener)
    {
        this.onAnimationEndListener=onAnimationEndListener;
    }

    @Override
    public void send() {
        startAnimation(animation0);
    }
}
           

然後發送彈幕 final NormalDanmu danmu=new NormalDanmu(this,sWidth,(int) -paint.measureText(str));

swidth表示螢幕寬度,paint.measureText(str)是textview寬度,表示從最右端移動到左邊完全移出螢幕

lp.addRule(RelativeLayout.ALIGN_PARENT_TOP);

lp.topMargin=i*danmuHeight;

danmuHeight是一個textview的高度,這裡設定放在容器的第i行

private void setDanmu()
    {
        String ss="按是按時按是android.os.BinderProx按是";
        int ll=ss.length()*DensityUtils.sp2px(this,15);
        int ran= new Random().nextInt(ss.length());
        String str=ss.substring(ran);
        final NormalDanmu danmu=new NormalDanmu(this,sWidth,(int) -paint.measureText(str));
        danmu.setTextSize(15);
        danmu.setText(str);
        danmu.setOnAnimationEndListener(new NormalDanmu.OnAnimationEndListener() {
            @Override
            public void clearPosition() {

                sendPosition.put(danmu.getPosition(), false);
            }

            @Override
            public void animationEnd() {

                container.removeView(danmu);
            }

        });

        for(int i=0;i<count;i++)
        {
            if(sendPosition.get(i)==false)
            {
                danmu.setPosition(i);
                RelativeLayout.LayoutParams lp=new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, danmuHeight);
                lp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
                lp.topMargin=i*danmuHeight;
                danmu.setGravity(Gravity.CENTER);
                container.addView(danmu, lp);

                danmu.send();

                sendPosition.put(i,true);
                break;
            }

        }
    }
           

逆向彈幕就是和普通彈幕移動方向不同其他完全一樣

頂部和底部的彈幕主要就是顯示幾秒後再消失就行了比較簡單

public class TopDanmu extends Danmu {
    private OnDisappearListener onDisappearListener;
    private int duration;
    private Handler handler=new Handler()
    {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what==1)
            {
                if(onDisappearListener!=null)
                {
                    onDisappearListener.disappear();
                }
            }
        }
    };

    public TopDanmu(Context context,int duration) {
        super(context);
        this.duration=duration;
    }



    public interface OnDisappearListener
    {
        public void disappear();
    }
    @Override
    public void send() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(duration);
                    handler.sendEmptyMessage(1);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public void setOnDisappearListener(OnDisappearListener onDisappearListener )
    {
        this.onDisappearListener=onDisappearListener;
    }
}
           

發送頂部彈幕

頂部彈幕要水準居中,這裡的容器設定的寬度超過的螢幕大小,是以要手動計算彈幕的水準位置

int margin= (int) ((sWidth-paint.measureText(danmu.getText().toString()))/2);

private void setTopDanmu()
    {
        String ss="按是按時按是android.os.BinderProx按是";

        int ran= new Random().nextInt(ss.length());
        String str=ss.substring(ran);
        int ll=str.length()*DensityUtils.sp2px(this, 15);
        final TopDanmu danmu=new TopDanmu(this,2000);
        danmu.setTextSize(15);
        danmu.setText(str);
        danmu.setTextColor(Color.GREEN);
        danmu.setOnDisappearListener(new TopDanmu.OnDisappearListener() {
            @Override
            public void disappear() {
                container.removeView(danmu);
                topSendPosition.put(danmu.getPosition(), false);
            }
        });

        for(int i=0;i<count;i++)
        {
            if(topSendPosition.get(i)==false)
            {
                danmu.setPosition(i);
                RelativeLayout.LayoutParams lp=new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, danmuHeight);
                lp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
                int margin= (int) ((sWidth-paint.measureText(danmu.getText().toString()))/2);
                lp.topMargin=i*danmuHeight;
                lp.leftMargin=margin;
                danmu.setGravity(Gravity.CENTER);
                container.addView(danmu, lp);

                danmu.send();

                topSendPosition.put(i,true);
                break;
            }

        }
    }