这里有新鲜出炉的精品教程,程序狗速度看过来!
Android 是一种基于 Linux 的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由 Google 公司和开放手机联盟领导及开发。尚未有统一中文名称,中国大陆地区较多人使用 "安卓" 或 "安致"。
这篇文章主要为大家详细介绍了 Android 自制精彩弹幕效果,弹幕垂直方向可固定随机。具有一定的参考价值,感兴趣的小伙伴们可以参考一下
好久没有写过文章, 最近发现直播特别的火, 很多 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 < View > 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 < Danmu > 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 < Integer > 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 < Danmu > 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);
- }
- });
- }
- }
来源: http://www.phperz.com/article/17/0826/344210.html