好久没有写过文章,最近发现直播特别的火,很多app都集成了直播的功能,发现有些直播是带有弹幕的,效果还不错,今天心血来潮,特地写了篇制作弹幕的文章.

今天要实现的效果如下:

1.弹幕垂直方向固定

2.弹幕垂直方向随机

上面效果图中白色的背景就是弹幕本身,是一个自定义的FrameLayout,我这里是为了更好的展示弹幕的位置才设置成了白色,当然如果是叠加在VideoView上的话,就需要设置成透明色了.

制作弹幕需要考虑以下几点问题:

1.弹幕的大小可以随意调整

2.弹幕内移动的item(或者称字幕)出现的位置,水平方向是从屏幕右边移动到屏幕左边,垂直方向是不能超出弹幕本身的高度的.

3.字幕移除屏幕后,需要将对应item(字幕)从其父容器(弹幕)中移除.

4.如果字幕出现的垂直方向的高度是随机的,那么还需要避免字幕重叠的情况.

ok,下面是弹幕自定义view的代码:

/**

* Created by dell on 2016/9/28.

*/

public class DanmuView extends FrameLayout {

private static final String TAG = "DanmuView";

private static final long DEFAULT_ANIM_DURATION = 6000; //默认每个动画的播放时长

private static final long DEFAULT_QUERY_DURATION = 3000; //遍历弹幕的默认间隔

private LinkedList mViews = new LinkedList<>();//弹幕队列

private boolean isQuerying;

private int mWidth;//弹幕的宽度

private int mHeight;//弹幕的高度

private Handler mUIHandler = new Handler();

private boolean TopDirectionFixed;//弹幕顶部的方向是否固定

private Handler mQueryHandler;

private int mTopGravity = Gravity.CENTER_VERTICAL;//顶部方向固定时的默认对齐方式

public void setHeight(int height) {

mHeight = height;

}

public void setWidth(int width) {

mWidth = width;

}

public void setTopGravity(int gravity) {

this.mTopGravity = gravity;

}

public void add(List danmuList) {

for (int i = 0; i < danmuList.size(); i++) {

Danmu danmu = danmuList.get(i);

addDanmuToQueue(danmu);

}

}

public void add(Danmu danmu) {

addDanmuToQueue(danmu);

}

public DanmuView(Context context) {

this(context, null);

}

public DanmuView(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public DanmuView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

HandlerThread thread = new HandlerThread("query");

thread.start();

//循环取出弹幕显示

mQueryHandler = new Handler(thread.getLooper()) {

@Override

public void handleMessage(Message msg) {

final View view = mViews.poll();

if (null != view) {

mUIHandler.post(new Runnable() {

@Override

public void run() {

//添加弹幕

showDanmu(view);

}

});

}

sendEmptyMessageDelayed(0, DEFAULT_QUERY_DURATION);

}

};

}

/**

* 将要展示的弹幕添加到队列中

*

* @param danmu

*/

private void addDanmuToQueue(Danmu danmu) {

if (null != danmu) {

final View view = View.inflate(getContext(), R.layout.layout_danmu, null);

TextView usernameTv = (TextView) view.findViewById(R.id.tv_username);

TextView infoTv = (TextView) view.findViewById(R.id.tv_info);

ImageView headerIv = (ImageView) view.findViewById(R.id.iv_header);

usernameTv.setText(danmu.getUserName());//昵称

infoTv.setText(danmu.getInfo());//信息

Glide.with(getContext()).//头像

load(danmu.getHeaderUrl()).

transform(new CropCircleTransformation(getContext())).into(headerIv);

view.measure(0, 0);

//添加弹幕到队列中

mViews.offerLast(view);

}

}

/**

* 播放弹幕

*

* @param topDirectionFixed 弹幕顶部的方向是否固定

*/

public void startPlay(boolean topDirectionFixed) {

this.TopDirectionFixed = topDirectionFixed;

if (mWidth == 0 || mHeight == 0) {

getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

@SuppressLint("NewApi")

@Override

public void onGlobalLayout() {

getViewTreeObserver().removeOnGlobalLayoutListener(this);

if (mWidth == 0) mWidth = getWidth() - getPaddingLeft() - getPaddingRight();

if (mHeight == 0) mHeight = getHeight() - getPaddingTop() - getPaddingBottom();

if (!isQuerying) {

mQueryHandler.sendEmptyMessage(0);

}

}

});

} else {

if (!isQuerying) {

mQueryHandler.sendEmptyMessage(0);

}

}

}

/**

* 显示弹幕,包括动画的执行

*

* @param view

*/

private void showDanmu(final View view) {

isQuerying = true;

Log.d(TAG, "mWidth:" + mWidth + " mHeight:" + mHeight);

final LayoutParams lp = new LayoutParams(view.getMeasuredWidth(), view.getMeasuredHeight());

lp.leftMargin = mWidth;

if (TopDirectionFixed) {

lp.gravity = mTopGravity | Gravity.LEFT;

} else {

lp.gravity = Gravity.LEFT | Gravity.TOP;

lp.topMargin = getRandomTopMargin(view);

}

view.setLayoutParams(lp);

view.setTag(lp.topMargin);

//设置item水平滚动的动画

ValueAnimator animator = ValueAnimator.ofInt(mWidth, -view.getMeasuredWidth());

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator animation) {

lp.leftMargin = (int) animation.getAnimatedValue();

view.setLayoutParams(lp);

}

});

addView(view);//显示弹幕

animator.setDuration(DEFAULT_ANIM_DURATION);

animator.setInterpolator(new LinearInterpolator());

animator.start();//开启动画

animator.addListener(new AnimatorListenerAdapter() {

@Override

public void onAnimationEnd(Animator animation) {

view.clearAnimation();

existMarginValues.remove(view.getTag());//移除已使用过的顶部边距

removeView(view);//移除弹幕

animation.cancel();

}

});

}

//记录当前仍在显示状态的弹幕的垂直方向位置(避免重复)

private Set existMarginValues = new HashSet<>();

private int linesCount;

private int range = 10;

private int getRandomTopMargin(View view) {

//计算可用的行数

linesCount = mHeight / view.getMeasuredHeight();

if (linesCount <= 1) {

linesCount = 1;

}

Log.d(TAG, "linesCount:" + linesCount);

//检查重叠

while (true) {

int randomIndex = (int) (Math.random() * linesCount);

int marginValue = randomIndex * (mHeight / linesCount);

//边界检查

if (marginValue > mHeight - view.getMeasuredHeight()) {

marginValue = mHeight - view.getMeasuredHeight() - range;

}

if (marginValue == 0) {

marginValue = range;

}

if (!existMarginValues.contains(marginValue)) {

existMarginValues.add(marginValue);

Log.d(TAG, "marginValue:" + marginValue);

return marginValue;

}

}

}

}

弹幕实体类:

/**

* Created by dell on 2016/9/28.

*/

public class Danmu {

private String headerUrl;//头像

private String userName;//昵称

private String info;//信息

public String getHeaderUrl() {

return headerUrl;

}

public void setHeaderUrl(String headerUrl) {

this.headerUrl = headerUrl;

}

public String getUserName() {

return userName;

}

public void setUserName(String userName) {

this.userName = userName;

}

public String getInfo() {

return info;

}

public void setInfo(String info) {

this.info = info;

}

}

测试类,MainActivity

public class MainActivity extends AppCompatActivity {

DanmuView mDanmuView;

EditText mMsgEdt;

Button mSendBtn;

Handler mDanmuAddHandler;

boolean continueAdd;

int counter;

@Override

protected void onResume() {

super.onResume();

mDanmuView.startPlay(true);//true表示弹幕的垂直方向是固定的,false则随机

continueAdd = true;

mDanmuAddHandler.sendEmptyMessageDelayed(0, 6000);

}

@Override

protected void onPause() {

super.onPause();

continueAdd = false;

mDanmuAddHandler.removeMessages(0);

}

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

initView();

initData();

initListener();

}

private void initView() {

mDanmuView = (DanmuView) findViewById(R.id.danmuView);

mMsgEdt = (EditText) findViewById(R.id.edt_msg);

mSendBtn = (Button) findViewById(R.id.btn_send);

}

private void initData() {

List danmuList = new ArrayList<>();

for (int i = 0; i < 3; i++) {

Danmu danmu = new Danmu();

danmu.setHeaderUrl("http://tupian.qqjay.com/tou3/2016/0725/cb00091099ffbf09f4861f2bbb5dd993.jpg");

danmu.setUserName("Mr.chen" + i);

danmu.setInfo("我是弹幕啊,不要问我为什么不可以那么长!!!");

danmuList.add(danmu);

}

mDanmuView.add(danmuList);

//下面是模拟每秒添加一个弹幕的过程

HandlerThread ht = new HandlerThread("send danmu");

ht.start();

mDanmuAddHandler = new Handler(ht.getLooper()) {

@Override

public void handleMessage(Message msg) {

runOnUiThread(new Runnable() {

@Override

public void run() {

Danmu danmu = new Danmu();

danmu.setHeaderUrl("http://tupian.qqjay.com/tou3/2016/0803/87a8b262a5edeff0e11f5f0ba24fb22f.jpg");

danmu.setUserName("Mr.new" + (counter++));

danmu.setInfo("新的弹幕啊!!!新的弹幕啊!!!新的弹幕啊!!!新的弹幕啊!!!");

mDanmuView.add(danmu);

}

});

//继续添加

if (continueAdd) {

sendEmptyMessageDelayed(0, 1000);

}

}

};

}

private void initListener() {

//手动添加

mSendBtn.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

String msg = mMsgEdt.getText().toString().trim();

if (TextUtils.isEmpty(msg)) {

Toast.makeText(MainActivity.this, "亲,你想发送什么啊?", Toast.LENGTH_SHORT).show();

return;

}

mMsgEdt.setText("");

Danmu danmu = new Danmu();

danmu.setHeaderUrl("http://img0.imgtn.bdimg.com/it/u=2198087564,4037394230&fm=11&gp=0.jpg");

danmu.setUserName("I'am good man");

danmu.setInfo("我是新人:" + msg);

mDanmuView.add(danmu);

}

});

}

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

android 弹幕动画,Android自制精彩弹幕效果相关推荐

  1. android弹球动画,Android动画之自定义Evaluator实现弹球效果

    前言 今天给大家带来的是自定义Evaluator实现弹球效果,我们先给大家来个效果图. 下面我们介绍具体代码流程 1. 自定义Point类 public class Point { private i ...

  2. android scrollview 动画,Android ScrollView实现下拉弹回动画效果

    这里设计一个自定义View,继承了ScrollView,实现可以下拉里面的内容,松手后画面弹回,这个自定义的View可以当做ScrollView来使用. 一般设计时的应用效果: 一.自定义View的设 ...

  3. android 边框渐隐动画,Android开启动画之渐隐渐现效果

    启动某项程序时我们往往都能看到不同的"开机动画",千变万化的动画也只不过是四种基本动画衍变美化而成的. 四种android动画效果: alpha        渐变透明度动画效果 ...

  4. android tabhost 动画,Android中使用TabHost 与 Fragment 制作页面切换效果

    三个标签页置于顶端 效果图: 在文件BoardTabHost.java中定义页面切换的效果:切换页面时,当前页面滑出,目标页面滑入.这是2个不同的动画设定动画时要区分对待 import android ...

  5. android菊花动画,Android实现仿iOS菊花加载圈动画效果

    常见的实现方式 切图,做旋转动画 自定义View,绘制效果 gif图 1.切图会增加体积,但相对简单,不过在换肤的场景下,会使用不同颜色,需要准备多张图,不够灵活. 2.由于自定义的好处,不同颜色只需 ...

  6. android horizontalscrollview 动画,Android HorizontalScrollView左右滑动效果

    本文实例为大家分享了Android HorizontalScrollView左右滑动的具体代码,供大家参考,具体内容如下 效果图 一.什么是HorizontalScrollView Horizonta ...

  7. android 放大镜动画,Android在图片上进行放大镜效果(放大镜形状)

    Android 图片上放大镜效果实现 1.[文件] ZoomView.java ~ 5KB     下载(55) package com.study.hello; import android.con ...

  8. android 齿轮动画,Android仿正点闹钟时间齿轮滑动效果

    看到正点闹钟上的设置时间的滑动效果非常好看,自己就想做一个那样的,在网上就开始搜资料了,看到网上有的齿轮效果的代码非常多,也非常难懂,我就决定自己研究一下,现在我就把我的研究成果分享给大家.我研究的这 ...

  9. android 百叶窗动画,Android 百叶窗折叠效果

    blinds.png 其实这个标题的类比并不太准确,百叶窗高度是固定的,只是 Z 轴有旋转.折叠效果并没有体现出来.不过毕竟自己一开始想到这个名字,那就硬着头皮也要叫这名字啦. 缘由 在微信的「聊天信 ...

最新文章

  1. visual studio 解决方案项目结构部署和配置
  2. Java中JFrame怎样控制闪烁_在JFrame Java中闪烁
  3. 【C++深度剖析教程2】C++经典问题解析之二 this指针与成员函数
  4. nginx限制请求之一:(ngx_http_limit_conn_module)模块
  5. 前端获取后端16位主键id,后3位四舍五入
  6. smale学习之数学表达式(day3)
  7. asp.net中配置使用Sqlite轻型数据库
  8. [数学建模] TOPSIS法(考虑权重和不考虑权重)--评价类问题
  9. windows7 优化:readyboost总结
  10. WPF - Visual调试工具Snoop
  11. C语言可变参数问题(va_list,va_start,va_arg,va_end用法及定义)
  12. python 将url 相对地址转绝对地址
  13. 【C#】基于System.Speech库实现语音合成与语音识别
  14. 使用Driftnet通过Wifi Pumpkin捕获移动图像
  15. #发现你#小说的交互——交互故事性
  16. “巴巴罗莎计划---我的崛起之路
  17. Matlab-LSB信息隐藏实验
  18. Sumatra PDF阅读器——快捷键与配置
  19. 数字信号处理实验二:DFT的共轭对称性及应用
  20. 模型参数与模型超参数

热门文章

  1. 关于JS里面的Call Stack and Heap
  2. 字面量、直接量、常量
  3. 机器学习预测的概率如何表示?
  4. 首旅如家逆势扩张,全年计划开店800-1000家
  5. 【PMP备考经验分享】从选择培训机构到5A通关,你需要做什么?
  6. 天鹅到家拟在纽交所上市:年营收超7亿元,亏损率逐渐收窄
  7. 收货地址的JavaScript城市三级联动【干货拿走不谢!>_<】
  8. 如何制作没用的电脑操作系统
  9. sas的安装过程中的问题小结
  10. 英飞凌ADS编译器汉化