android-自定义弹幕layout的实现

以前工作中的代码,整理成一个自定义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 }

 

猜你喜欢

转载自www.cnblogs.com/hankzhouAndroid/p/8998449.html