以前工作中的代码,整理成一个自定义layout。先看效果:
使用方法也极为简单,只需要在layout.xml和MainActivity.java里面增加相关代码 。
请看下方代码:
MainActivity.java
1 package com.example.danmulayout;
2
3 import android.graphics.Color;
4 import android.os.Bundle;
5 import android.support.v7.app.AppCompatActivity;
6 import android.view.View;
7 import android.widget.Button;
8
9 import com.example.danmulayout.custom.DanMu;
10 import com.example.danmulayout.custom.DanMuColor;
11 import com.example.danmulayout.custom.MyDanMuLayout;
12
13 /**
14 * 本例,旨在做一个我自己的弹幕 Layout 作为开源库
15 */
16 public class MainActivity extends AppCompatActivity {
17
18 private MyDanMuLayout dl_my_danMu;
19 private Button btn_send, btn_send_plane, btn_hide, btn_show;
20
21 @Override
22 protected void onCreate(Bundle savedInstanceState) {
23 super.onCreate(savedInstanceState);
24 setContentView(R.layout.activity_main);
25 init();
26 }
27
28 private void init() {
29 dl_my_danMu = findViewById(R.id.dl_my_danmu);
30 btn_send = findViewById(R.id.btn_send);
31 btn_hide = findViewById(R.id.btn_hide);
32 btn_show = findViewById(R.id.btn_show);
33 btn_send_plane = findViewById(R.id.btn_send_plane);
34
35 btn_send.setOnClickListener(new View.OnClickListener() {
36 @Override
37 public void onClick(View v) {
38 dl_my_danMu.sendDanMu(DanMu.getRandomDanMuContentInPresets(), DanMuColor.getRandomColorInPresets(), Color.TRANSPARENT);
39 }
40 });
41 btn_send_plane.setOnClickListener(new View.OnClickListener() {
42 @Override
43 public void onClick(View v) {
44 dl_my_danMu.sendPlane();
45 }
46 });
47 btn_hide.setOnClickListener(new View.OnClickListener() {
48 @Override
49 public void onClick(View v) {
50 dl_my_danMu.hideDanMu();
51 }
52 });
53 btn_show.setOnClickListener(new View.OnClickListener() {
54 @Override
55 public void onClick(View v) {
56 dl_my_danMu.showDanMu();
57 }
58 });
59 }
60
61 @Override
62 protected void onDestroy() {
63 super.onDestroy();
64 dl_my_danMu.release();
65 }
66 }
activity_main.xml
1 <?xml version="1.0" encoding="utf-8"?>
2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:tools="http://schemas.android.com/tools"
4 android:id="@+id/rl_main"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:orientation="vertical"
8 android:padding="5dp"
9 tools:context=".MainActivity">
10
11 <LinearLayout
12 android:layout_width="match_parent"
13 android:layout_height="wrap_content"
14 android:orientation="horizontal">
15
16 <Button
17 android:id="@+id/btn_send"
18 android:layout_width="wrap_content"
19 android:layout_height="wrap_content"
20 android:layout_margin="1dp"
21 android:text="发自动弹幕" />
22
23 <Button
24 android:id="@+id/btn_send_plane"
25 android:layout_width="wrap_content"
26 android:layout_height="wrap_content"
27 android:layout_margin="1dp"
28 android:text="送飞机" />
29
30 <Button
31 android:id="@+id/btn_hide"
32 android:layout_width="wrap_content"
33 android:layout_height="wrap_content"
34 android:layout_margin="1dp"
35 android:text="隐藏" />
36
37 <Button
38 android:id="@+id/btn_show"
39 android:layout_width="wrap_content"
40 android:layout_height="wrap_content"
41 android:layout_margin="1dp"
42 android:text="显示" />
43 </LinearLayout>
44
45
46 <com.example.danmulayout.custom.MyDanMuLayout
47 android:id="@+id/dl_my_danmu"
48 android:layout_width="match_parent"
49 android:layout_height="match_parent"
50 android:background="@android:color/darker_gray"></com.example.danmulayout.custom.MyDanMuLayout>
51
52 </LinearLayout>
两个文件的代码行数总共加起来不超过200行。
下面列出关键代码:
MyDanMuLayout.java
1 package com.example.danmulayout.custom; 2 3 import android.animation.Animator; 4 import android.animation.AnimatorListenerAdapter; 5 import android.animation.ObjectAnimator; 6 import android.content.Context; 7 import android.graphics.drawable.Drawable; 8 import android.os.Handler; 9 import android.util.AttributeSet; 10 import android.util.Log; 11 import android.view.View; 12 import android.view.ViewTreeObserver; 13 import android.view.animation.LinearInterpolator; 14 import android.widget.ImageView; 15 import android.widget.RelativeLayout; 16 import android.widget.TextView; 17 18 import com.example.danmulayout.R; 19 20 import java.util.LinkedList; 21 import java.util.Queue; 22 import java.util.Timer; 23 import java.util.TimerTask; 24 25 public class MyDanMuLayout extends RelativeLayout { 26 27 private Context mContext; 28 29 /*********************重写构造函数********************/ 30 public MyDanMuLayout(Context context) { 31 this(context, null); 32 } 33 34 public MyDanMuLayout(Context context, AttributeSet attrs) { 35 this(context, attrs, 0); 36 } 37 38 public MyDanMuLayout(Context context, AttributeSet attrs, int defStyleAttr) { 39 super(context, attrs, defStyleAttr); 40 mContext = context; 41 setWidthObserverOfLayout(); 42 } 43 44 private int measuredWidthOfDanMuView; 45 private int layoutWidth, layoutHeight; 46 private Handler mHandler = new Handler(); 47 48 /** 49 * 全局监听,为了得出自定义layout的实际宽度和高度(这个宽度会用来计算弹幕需要移动多长距离,高度会用来计算 每条弹幕离顶部的距离) 50 */ 51 private void setWidthObserverOfLayout() { 52 //以本layout为根进行监听,当它的布局完成之后,执行逻辑,并且移除监听器 53 ViewTreeObserver observer = this.getViewTreeObserver(); 54 observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {// 当全局布局发生变化,或者子view的可见状态发生变化时,它就会被回调 55 56 @SuppressWarnings("deprecation") 57 @Override 58 public void onGlobalLayout() {//当检测到布局变化时,这个监听器就会回调 59 Log.d("WidthObserverOfLayout", "执行widthObserverOfLayout"); 60 getViewTreeObserver().removeGlobalOnLayoutListener(this);//一旦被回调,立即移除监听,不然就会无限执行 61 layoutWidth = getMeasuredWidth(); //获得当前自定义layout的实际宽度 62 layoutHeight = getMeasuredHeight();//实际高度 63 startDanMuLooper();//开始队列循环 64 } 65 }); 66 } 67 68 69 //在这里设计一个队列机制,目的就是 将外界传进来的弹幕信息,都封装成Danmu对象,然后队列无限循环,显示弹幕 70 private Queue<DanMu> danMuQueue = new LinkedList<>(); 71 private Timer timer; 72 73 /** 74 * 开始弹幕队列循环 75 */ 76 private void startDanMuLooper() { 77 if (null != timer) { 78 timer.cancel(); 79 timer.purge(); 80 } 81 timer = new Timer(); 82 timer.schedule(new TimerTask() { 83 @Override 84 public void run() { 85 //这里有一个无限循环的timer,它会无限地将danmuQueue中的弹幕, 86 final DanMu danmu = danMuQueue.poll(); 87 if (null != danmu) { 88 mHandler.post(new Runnable() { 89 @Override 90 public void run() { 91 initDanMu(mContext, danmu.content, danmu.distanceToTop, danmu.duration, danmu.textColor, danmu.bgColor, danmu.drawable); 92 } 93 }); 94 } 95 } 96 }, 50, 100);//每100毫秒检测一次队列里面有没有弹幕 97 } 98 99 /** 100 * 开始对View进行动画处理 101 * 102 * @param danMuView 103 * @param duration 104 * @param distanceToTopPercent 105 */ 106 private void animate(final View danMuView, final int duration, final float distanceToTopPercent) { 107 ViewTreeObserver observer = danMuView.getViewTreeObserver();//增加监听, 为了获得弹幕view的实际宽度 108 observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 109 @Override 110 public void onGlobalLayout() { 111 danMuView.getViewTreeObserver().removeOnGlobalLayoutListener(this);//一旦被回调,立即移除监听,不然就会无限执行 112 measuredWidthOfDanMuView = danMuView.getMeasuredWidth();//得出测量之后的宽度值 113 114 // 借助RelativeLayout.LayoutParam 让它总是在右侧外围 115 RelativeLayout.LayoutParams layoutParams = (LayoutParams) danMuView.getLayoutParams(); 116 layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); 117 layoutParams.topMargin = (int) (distanceToTopPercent * layoutHeight);//设定离父组件最上方的距离 118 layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); 119 layoutParams.rightMargin = -measuredWidthOfDanMuView; 120 danMuView.requestLayout();//强制刷新布局 121 122 //然后执行动画 123 startMovement(danMuView, duration);//拿到宽度之后才能进行动画执行,因为弹幕textview的移动距离依赖 layoutWidth 124 } 125 }); 126 } 127 128 /** 129 * 弹幕初始化 130 */ 131 private void initDanMu(Context ctx, String danMuContent, final float distanceToTopPercent, final int duration, int textColor, int bgColor, Drawable drawable) { 132 View danMuView; 133 if (drawable == null) {//先看看有没有图,有图的话,就创建ImageView,否则就创建TextView 134 135 TextView tv_danMuTemp = new TextView(ctx);//创建一个弹幕textView,这里的TextView是原生的,其实我可以做成自定义的TextView 136 tv_danMuTemp.setText(danMuContent);//设置文字 137 tv_danMuTemp.setTextColor(textColor);//设置文字颜色 138 tv_danMuTemp.setBackgroundColor(bgColor);//设置边框颜色 139 140 danMuView = tv_danMuTemp; 141 142 } else { 143 ImageView iv_danMuTemp = new ImageView(ctx); 144 iv_danMuTemp.setImageDrawable(drawable); 145 iv_danMuTemp.setMaxWidth(200); 146 iv_danMuTemp.setMaxHeight(200); 147 148 danMuView = iv_danMuTemp; 149 } 150 151 //下面的代码是为了将新产生的view 刚好 隐藏到本layout最右边 152 RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);//设置初始布局 153 layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);//相对布局的参数:靠右 154 layoutParams.rightMargin = -layoutWidth;//并且继续往右,直到隐藏 155 addView(danMuView, layoutParams);//添加弹幕textView 156 157 animate(danMuView, duration, distanceToTopPercent); 158 } 159 160 161 /** 162 * 开始移动 163 * 164 * @param tv_danMuTemp 165 */ 166 private void startMovement(final View tv_danMuTemp, int duration) { 167 float targetDistance = layoutWidth + measuredWidthOfDanMuView;// 计算总共需要移动的距离 168 ObjectAnimator animator = ObjectAnimator.ofFloat(tv_danMuTemp, "translationX", -targetDistance);//定义移动动画ObjectAnimator 169 animator.setDuration(duration * 1000);//时长 170 animator.setInterpolator(new LinearInterpolator());//匀速移动 171 animator.addListener(new AnimatorListenerAdapter() {// 动画监听器,重写适配器 172 @Override 173 public void onAnimationEnd(Animator animation) { 174 removeView(tv_danMuTemp);//一旦动画执行完毕,立即移除该view 175 Log.d("onAnimationEnd", "getChildCount:" + getChildCount());//打印移除之后还剩下多少 176 } 177 }); 178 animator.start();//一切就绪,开始动画 179 } 180 181 /******************************************************* 以下是所有公开接口 ************************************************/ 182 183 /** 184 * 向外开放一个接口,发送弹幕 185 * 186 * @param content 文字内容 187 * @param textColor 文字颜色 188 * @param bgColor textView背景颜色 189 */ 190 public void sendDanMu(String content, int textColor, int bgColor) { 191 DanMu danmu = new DanMu(); 192 danmu.content = content; 193 danmu.distanceToTop = (float) Math.random(); 194 danmu.duration = 10; 195 danmu.textColor = textColor; 196 danmu.bgColor = bgColor; 197 danMuQueue.add(danmu);//将弹幕对象添加到队列中 198 } 199 200 /** 201 * 送飞机 202 */ 203 public void sendPlane() { 204 DanMu danmu = new DanMu(); 205 danmu.distanceToTop = (float) Math.random(); 206 danmu.duration = 10; 207 danmu.drawable = mContext.getResources().getDrawable(R.drawable.plane); 208 danMuQueue.add(danmu);//将弹幕对象添加到队列中 209 } 210 211 /** 212 * 隐藏所有弹幕view 213 */ 214 public void hideDanMu() { 215 for (int i = 0; i < getChildCount(); i++) { 216 getChildAt(i).setVisibility(View.INVISIBLE); 217 } 218 } 219 220 /** 221 * 显示所有弹幕view 222 */ 223 public void showDanMu() { 224 for (int i = 0; i < getChildCount(); i++) { 225 getChildAt(i).setVisibility(View.VISIBLE); 226 } 227 } 228 229 /** 230 * 引用本layout的activity,在onDestroy中一定要调用资源释放接口 231 */ 232 public void release() { 233 if (null != timer) { 234 timer.cancel(); 235 timer.purge(); 236 } 237 } 238 239 }
DanMu.java
1 package com.example.danmulayout.custom; 2 3 4 import android.graphics.drawable.Drawable; 5 6 /** 7 * 8 */ 9 public class DanMu { 10 public String content;//弹幕的文字内容 11 public float distanceToTop;//距离顶端的比例(0-1) 12 public int duration;//显示时长,单位是秒 13 public int textColor;//文字颜色 14 public int bgColor;//边框颜色 15 public Drawable drawable; 16 17 18 public static String getRandomDanMuContentInPresets(){ 19 String result; 20 21 int n = (int) (10 * Math.random()); 22 switch (n) { 23 case 1: 24 case 2: 25 result = "6666666"; 26 break; 27 case 3: 28 case 4: 29 result = "不要放弃,加油,你是最胖的!"; 30 break; 31 case 5: 32 case 6: 33 result = "一条大河啊啊啊啊向西流"; 34 break; 35 case 7: 36 case 8: 37 result = "冷静冷静"; 38 break; 39 case 9: 40 result = "卧槽什么鬼,吓我一跳"; 41 break; 42 default: 43 result = "2333333"; 44 } 45 return result; 46 } 47 }
DanMuColor.java
1 package com.example.danmulayout.custom; 2 3 import android.graphics.Color; 4 5 public class DanMuColor { 6 public static final int RED = Color.RED; 7 public static final int BLUE = Color.BLUE; 8 public static final int YELLOW = Color.YELLOW; 9 public static final int GREEN = Color.GREEN; 10 public static final int ORANGE = Color.parseColor("#FFAA00"); 11 12 /** 13 * 返回上面预设中的任意一个颜色 14 * 15 * @return 16 */ 17 public static int getRandomColorInPresets() { 18 int result; 19 20 int n = (int) (10 * Math.random()); 21 switch (n) { 22 case 1: 23 case 2: 24 result = RED; 25 break; 26 case 3: 27 case 4: 28 result = BLUE; 29 break; 30 case 5: 31 case 6: 32 result = YELLOW; 33 break; 34 case 7: 35 case 8: 36 result = GREEN; 37 break; 38 case 9: 39 result = ORANGE; 40 break; 41 default: 42 result = BLUE; 43 } 44 return result; 45 } 46 47 }