Android实现弹幕效果
来源:互联网 发布:学为软件下载 编辑:程序博客网 时间:2024/05/01 22:17
相信大家看视频的时候都会有弹幕效果,这似乎已经成为视频软件的标配,接下来让我们来看看如何实现这个弹幕效果。
一.弹幕效果分析
我可以看到,弹幕效果是在屏幕上方飘过,即从屏幕外一端出现,一直在屏幕中飘动至屏幕另一端,我们将其实现逻辑进行分解如下:
1.一个viewGroup中包含多个textView
2.textView包含平移动画
经过分解发现其逻辑一点也不复杂,分析完
二.实现
上面我们分析了自定义弹幕效果View的逻辑:
这里我们让我们自定义的的BarrageView继承RelateLayout,并实现onLayout方法。
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); int childCount = this.getChildCount(); for(int i = 0; i < childCount; i++){ final View view = getChildAt(i); if (view != null){ RelativeLayout.LayoutParams lp = (LayoutParams) view.getLayoutParams(); if (lp.leftMargin <= 0){ if(mDirection == FROM_RIGNG_TO_LEFT){ view.layout(mScreenWidth,lp.topMargin,mScreenWidth+view.getMeasuredWidth(), lp.topMargin+view.getMeasuredHeight()); }else if(mDirection == FROM_LEFT_TO_RIGHT){ view.layout(-view.getMeasuredWidth(),lp.topMargin,0, lp.topMargin+view.getMeasuredHeight()); } } } } }
这里很多人可能会问为什么是实现onLayout方法,我们的BarrageView继承了RelateLayout,即继承了一个viewGroup,而飘屏的弹幕均属于这个viewGroup的子View,我们的弹幕效果要实现必须在我们需要的时候往这个BarrageView中添加一个子view,添加子view我们就必须要为其指定在父控件中的位置。而每当我们像BarrageView中加入一个子view,就会执行其onLayout方法,所以我们最终就在onLayout中确定子view的初始位置
从代码中我们可以看到这边我支持了两个方向,从左到右(FROM_LEFT_TO_RIGHT)和从右到左(FROM_RIGNG_TO_LEFT),这里我们分析从右到左,毕竟目前较多的软件都是从右到左飘屏
view.layout(mScreenWidth,lp.topMargin,mScreenWidth+view.getMeasuredWidth(), lp.topMargin+view.getMeasuredHeight());这段代码写的很清晰,既然是从右到左飘屏,那我们添加的子view最开始的位置当然就是在屏幕外右端,如下图
接下来我们要添加子view,并为其设置相应的动画效果,使其达到飘屏的效果
private void createBarrageItemView(Context context,String text,int textSize,int textColor){ final TextView textView = new TextView(context); textView.setBackgroundResource(R.drawable.barrage_item_bg); if (textColor != 0){ textView.setTextColor(getResources().getColor(textColor)); } if (textSize != 0){ textView.setTextSize(textSize); } textView.setText(text); int w = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED); int h = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED); textView.measure(w, h); int height =textView.getMeasuredHeight(); int width = textView.getMeasuredWidth(); int row = new Random().nextInt(100) % mRowNum; while (needResetRow(row)){ row = new Random().nextInt(100) % mRowNum; } mRowPosList.add(row); RelativeLayout.LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT); lp.topMargin = row * (height + ToolUtils.dip2px(context,10));// lastRow = row; textView.setLayoutParams(lp); textView.setPadding(ToolUtils.dip2px(context,15), ToolUtils.dip2px(context,2), ToolUtils.dip2px(context,15), ToolUtils.dip2px(context,2)); textView.getBackground().setAlpha(mAlpha); this.addView(textView); ViewPropertyAnimator animator = null; if(mDirection == FROM_RIGNG_TO_LEFT){ animator = textView.animate() .translationXBy(-(mScreenWidth + width + 80)); }else if(mDirection == FROM_LEFT_TO_RIGHT){ animator = textView.animate() .translationXBy(mScreenWidth + width + 80); } animator.setDuration(mDuration); animator.setInterpolator(new LinearInterpolator()); animator.start(); animator.setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) {// mChildView.remove(textView); BarrageView.this.removeView(textView); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); mChildView.add(textView); }
添加子view需要注意一下几点:
利用随机数,生成子view出现在第几行,
int row = new Random().nextInt(100) % mRowNum; while (needResetRow(row)){ row = new Random().nextInt(100) % mRowNum; } mRowPosList.add(row);然后在判断子view是否需要重新设置所在行(需要设置是为了不让后面的子view与前面的子view重叠),其实现如下:
public boolean needResetRow(int row){ int size = mRowPosList.size(); int sameRowPos =-1; for(int i = size; i > 0; i--){ if (row == mRowPosList.get(i-1)){ sameRowPos = i-1; break; } } if (sameRowPos != -1){ TextView tv = mChildView.get(sameRowPos); if (mScreenWidth -tv.getX() < tv.getWidth()){ return true; } } return false; }该逻辑思想为,从所有子view中遍历,找到与当前需要添加的子view同一行的最后一个添加的子view,(我们用sameRowView表示),判断sameRowView是否显示完全,如果未显示完全就继续生成随机行数。(图画的丑 ,能看懂就行....)
这里指的一提的是,以上逻辑是存在问题的,如果每个子view都很长的话,当全部子view占满屏幕且都没显示完全时,我们将会陷入一个死循环,这样我们的应用就会卡死(为了制作gif图好看,采用这种方式),通过上网查阅,给出的方案是,只记录最后一个子view所在的位置,判断当前新家的子view是否与上一个子view同行,若是,则重选行数。
int row = new Random().nextInt(100) % mRowNum; while (mLastRow == row){ row = new Random().nextInt(100) % mRowNum; } mLastRow = row;很遗憾的是,上述的方法虽然不会卡死(不会卡死就很重要了,毕竟能卡死应用已经很严重,所以实际使用推荐使用这种方式,或者有其他更好的方式欢迎交流) 但是没有解决子view间相互覆盖的问题。如下图,我们1先加入BarrageView中,然后我们2加入并且我们2不会与1在同一行,如果同行就重选选行,同理3不会与2同行,但是3可以与1同行,因为1不是最后一个加入BarrageView的子view,但是确实存在了覆盖问题
至此我们的自定义弹幕BarrageView就分析完了,下面贴上调用代码与自定义BarrageView代码
MainActivity.java
public class MainActivity extends AppCompatActivity { private BarrageView barrageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); barrageView = (BarrageView) findViewById(R.id.barrageView); barrageView.setBackgroundResource(0); new Thread(new Runnable() { @Override public void run() { for(int i = 0; i < 10000; i++){ final int finalI = i; runOnUiThread(new Runnable() { @Override public void run() { if (finalI%2 == 0){ barrageView.addBarrageItemView(MainActivity.this,"第" + finalI +"条弹幕"); }else{ barrageView.addBarrageItemView(MainActivity.this,"第" + finalI +"条弹幕"+"第" + finalI +"条弹幕"); } } }); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); }}BarrageView.java
public class BarrageView extends RelativeLayout{ private int mRowNum = 6; private long mDuration = 5000;//弹幕在屏幕显示的时间 默认5s private int mAlpha = 180;//背景的透明度0-255 private int mDirection = FROM_RIGNG_TO_LEFT;//当前弹幕活动方向 默认从右到左 public static final int FROM_LEFT_TO_RIGHT = 1;//从左到右 public static final int FROM_RIGNG_TO_LEFT = 2;//从右到左 private int mScreenWidth; private List<TextView> mChildView; private LinkedList mRowPosList; public BarrageView(Context context) { super(context); init(context); } public BarrageView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public BarrageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context){ mScreenWidth = ToolUtils.getScreenWidth(context); mChildView = new ArrayList<TextView>(); mRowPosList = new LinkedList(); } /** * 设置弹幕飘动方向 * @param direction 弹幕飘动方向 默认从右到左 FROM_RIGNG_TO_LEFT */ public void setDirection(int direction){ mDirection = direction; } /** * 设置弹幕飘屏时间 * @param duration 弹幕飘屏时间 默认5s */ public void setDuration(long duration){ mDuration = duration; } /** * 设置飘屏行数 * @param rowNum 飘屏行数 默认6条 */ public void setRowNum(int rowNum){ mRowNum = rowNum; } /** * 设置item的背景透明度 范围:0~255 * @param alpha 取值0~255 0为全透明 */ public void setBackgroundAlpha(int alpha){ mAlpha = alpha; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); int childCount = this.getChildCount(); for(int i = 0; i < childCount; i++){ final View view = getChildAt(i); if (view != null){ RelativeLayout.LayoutParams lp = (LayoutParams) view.getLayoutParams(); if (lp.leftMargin <= 0){ if(mDirection == FROM_RIGNG_TO_LEFT){ view.layout(mScreenWidth,lp.topMargin,mScreenWidth+view.getMeasuredWidth(), lp.topMargin+view.getMeasuredHeight()); }else if(mDirection == FROM_LEFT_TO_RIGHT){ view.layout(-view.getMeasuredWidth(),lp.topMargin,0, lp.topMargin+view.getMeasuredHeight()); } } } } } public void addBarrageItemView(Context context,String text,int textSize,int textColor){ createBarrageItemView(context,text,textSize,textColor); } public void addBarrageItemView(Context context,String text){ createBarrageItemView(context,text,0,0); } private void createBarrageItemView(Context context,String text,int textSize,int textColor){ final TextView textView = new TextView(context); textView.setBackgroundResource(R.drawable.barrage_item_bg); if (textColor != 0){ textView.setTextColor(getResources().getColor(textColor)); } if (textSize != 0){ textView.setTextSize(textSize); } textView.setText(text); int w = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED); int h = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED); textView.measure(w, h); int height =textView.getMeasuredHeight(); int width = textView.getMeasuredWidth(); int row = new Random().nextInt(100) % mRowNum; while (needResetRow(row)){ row = new Random().nextInt(100) % mRowNum; } mRowPosList.add(row); RelativeLayout.LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT); lp.topMargin = row * (height + ToolUtils.dip2px(context,10));// lastRow = row; textView.setLayoutParams(lp); textView.setPadding(ToolUtils.dip2px(context,15), ToolUtils.dip2px(context,2), ToolUtils.dip2px(context,15), ToolUtils.dip2px(context,2)); textView.getBackground().setAlpha(mAlpha); this.addView(textView); ViewPropertyAnimator animator = null; if(mDirection == FROM_RIGNG_TO_LEFT){ animator = textView.animate() .translationXBy(-(mScreenWidth + width + 80)); }else if(mDirection == FROM_LEFT_TO_RIGHT){ animator = textView.animate() .translationXBy(mScreenWidth + width + 80); } animator.setDuration(mDuration); animator.setInterpolator(new LinearInterpolator()); animator.start(); animator.setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) {// mChildView.remove(textView); BarrageView.this.removeView(textView); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); mChildView.add(textView); } public boolean needResetRow(int row){ int size = mRowPosList.size(); int sameRowPos =-1; for(int i = size; i > 0; i--){ if (row == mRowPosList.get(i-1)){ sameRowPos = i-1; break; } } if (sameRowPos != -1){ TextView tv = mChildView.get(sameRowPos); if (mScreenWidth -tv.getX() < tv.getWidth()){ return true; } } return false; }}
阅读全文
0 0
- Android弹幕效果实现
- Android弹幕效果实现
- Android---弹幕效果实现
- Android实现弹幕效果
- Android:简易弹幕效果实现
- Android 简易弹幕效果实现
- Android实现弹幕效果——BarrageDemo
- Android直播中弹幕效果实现
- 弹幕效果实现
- Jquery实现弹幕效果
- jquery实现弹幕效果
- jQuery实现弹幕效果
- JS--实现弹幕效果
- jQuery实现弹幕效果
- GoEasy实现弹幕效果
- 简单实现弹幕效果
- android 弹幕评论效果
- android 中的弹幕效果
- java面试算法题总结
- php远程复制文件或文件夹下的所有文件到另一个文件夹
- ajax前台接受到的中文乱码
- 浅谈HTTP中Get与Post的区别
- POJ 1217 FOUR QUARTERS 笔记
- Android实现弹幕效果
- swjtu2385(Maximize The Beautiful Value)
- Office2007 PPT 宏修改文字颜色
- codevs 1315 摆花 (DP)
- 高斯混合模型的重叠度计算 (Overlap Rate, OLR)
- && 与 || 的理解
- Android 日志收集
- Android Studio中配置科大讯飞SDK
- Hive 导入CSV文件