preface

For more code, check out my github: github.com/shuaijia/Js… , give a star if you like! You can also follow my public account and search android dry Goods Camp

Now more and more video websites or clients support the function of bullet screen, and it seems that the function of bullet screen has become a hobby of many people. Sending bullet screen and watching bullet screen have become a way for people to make fun of, laugh and express their opinions.

And the originator of domestic bullet barrage should be station A and station B.

Barrage, or barrage, is a popular Chinese term that refers to the barrage of large or small amounts of artillery. And a barrage, as the name suggests, is a curtain of bullets, and when a lot of jokes float across the screen it looks like a barrage from a flying shooter.

Recently I have been writing video player, how can I get less bullet screen! So I want to write out my ideas for developing bullet screen functions to share with you.

Still go up first effect picture:

General train of thought

Our goal is to display a variety of ItemViews on top of the player and scroll them. Itemviews are customizable, which looks like a ListView, except that they have multiple rows and columns and need to calculate the position of each itemView and scroll all the time.

Therefore, I use Adapter mode, imitation of ListView Adapter to achieve the function of barrage. Thinking of this, many people think that this is not a typical horizontal waterfall flow, which can be easily implemented using RecyclerView or Flexbox.

But I want to consider from the design mode, implementation principle, design, so that you can also have a deeper understanding of the adapter mode and ListView principle, if you want to use RecyclerView to achieve, you can try yourself.

Key:

  • Use the adapter pattern to adapt, process, and display various ItemViews
  • Use hadler to send messages periodically to scroll the itemView
  • Calculate the optimal position of itemView
  • Scroll area Settings

Let’s implement it together:

1. Entity classes

Of course, the entity class can not be omitted:

Public class DanmuModel {private int type; public class DanmuModel {private int type; public int getType() { return type; } public void setType(int type) { this.type = type; }}Copy the code

Where type is the type of the Model entity class, because there will be multiple types in the itemView of the barrage, corresponding to the entity class of different types.

When used, you can define your own entity class, which inherits from DanmuModel, or not, as long as you can distinguish between different types: Since the later Adapter does not define a method for retrieving the item type as the ListView adapter does, we can simply select a different itemView in the getView method based on type.


2, BaseAdapter

First, the Adapter is defined as an abstract class and the generic M is set, which is the corresponding entity class.

Define three abstract methods in Adapter:

  1. GetViewTypeArray: Gets an array of itemView types
  2. GetSingleLineHeight: Gets the height of a single itemView
  3. GetView: Gets an itemView, similar to the getView method in the Adapter of ListView
Public abstract class DanmuAdapter<M> {public abstract class DanmuAdapter<M> {public abstract int[] getViewTypeArray(); /** @return */ public abstract int getSingleLineHeight(); /** * Get itemView ** @param entry * @param convertView * @return */ public abstract View getView(M entry, View convertView); }Copy the code

Is the adapter abstract class defined? No!

When displaying bullets, a large number of View objects will be created. If not handled, it is easy to cause memory overflow, so we need to optimize the cache:

A. First create A map collection

Private HashMap<Integer, Stack< view >> cacheViews;Copy the code

The view type is key, the corresponding view is stored in the stack, and the stack is value.

B. In construction

public DanmuAdapter() { cacheViews = new HashMap<>(); typeArray = getViewTypeArray(); for (int i = 0; i < typeArray.length; i++) { Stack<View> stack = new Stack<>(); cacheViews.put(typeArray[i], stack); }}Copy the code

Get an array of itemView types and loop through a stack of corresponding types.

C. Add itemView to cache

/** * synchronized public void addViewToCache(int type, synchronized) ** @param view */ synchronized public void addViewToCache(int type, synchronized) View view) { if (cacheViews.containsKey(type)) { cacheViews.get(type).push(view); } else { throw new Error("your cache has not this type"); }}Copy the code

D. Remove itemView from cache

** @param type * @return */ synchronized public View removeViewFromCache(int type) {if (cacheViews.containsKey(type) && cacheViews.get(type).size() > 0) return cacheViews.get(type).pop(); else return null; }Copy the code

E. Reduce the cache size

Public void shrinkCacheSize() {int[] typeArray = getViewTypeArray(); / / Public void shrinkCacheSize() {int[] typeArray = getViewTypeArray(); for (int i = 0; i < typeArray.length; i++) { if (cacheViews.containsKey(typeArray[i])) { Stack<View> typeStack = cacheViews.get(typeArray[i]); int length = typeStack.size(); While (typeStack.size() > (int) (length / 2.0 + 0.5)) {typeStack.pop(); } cacheViews.put(typeArray[i], typeStack); }}}Copy the code

F. Get the cache size

** @return */ public int getCacheSize() {int size = 0; int[] types = getViewTypeArray(); for (int i = 0; i < types.length; i++) { size = size + cacheViews.get(types[i]).size(); } return size; }Copy the code

Ok, at this point the BaseAdapter is wrapped


3, DanmuView

Inheriting from ViewGroup, overriding its three constructors is inevitable. No longer drag

A, variables, and get/set methods

Public static final int LOWER_SPEED = 1; public static final int NORMAL_SPEED = 4; public static final int HIGH_SPEED = 8; Public final static int GRAVITY_TOP = 1; //001 public final static int GRAVITY_CENTER = 2; //010 public final static int GRAVITY_BOTTOM = 4; //100 public final static int GRAVITY_FULL = 7; //111 private int gravity = GRAVITY_FULL; private int speed = 4; private int spanCount = 6; private int WIDTH, HEIGHT; private int singltLineHeight; private DanmuAdapter adapter; public List<View> spanList; private OnItemClickListener onItemClickListener;Copy the code

The first idea is to extract the method from the adapter, return the height of the itemView, draw the area height according to the barrage View, divide by the height of the itemView, Figure out a reasonable number of bullet screen lines (you can see why we define getSingleLineHeight() when writing the adapter).

B. Encapsulate the entity class again

It is simply a matter of encapsulating the passed entity class DanmuModel with the calculated optimal number of rows.

class InnerEntity {
    public int bestLine;
    public DanmuModel model;
}Copy the code

C. Set the Adapter

public void setAdapter(DanmuAdapter adapter) { this.adapter = adapter; singltLineHeight = adapter.getSingleLineHeight(); // Open the thread to make the barrage scroll, more on that later}Copy the code

D. Calculate the best position

Here’s the key. Code first

/** @return */ private int getBestLine() {// convert to binary int gewei = gravity % 2; int temp = gravity / 2; int shiwei = temp % 2; temp = temp / 2; int baiwei = temp % 2; Int firstLine = (int) (spanCount / 3.0 + 0.5); spanCount = spanCount (spanCount / 3.0 + 0.5); List<Integer> legalLines = new ArrayList<>(); if (gewei == 1) { for (int i = 0; i < firstLine; i++) { legalLines.add(i); } } if (shiwei == 1) { for (int i = firstLine; i < firstLine * 2; i++) { legalLines.add(i); } } if (baiwei == 1) { for (int i = firstLine * 2; i < spanCount; i++) { legalLines.add(i); } } int bestLine = 0; // Return for (int I = 0; i < spanCount; i++) { if (spanList.get(i) == null) { bestLine = i; if (legalLines.contains(bestLine)) return bestLine; } } float minSpace = Integer.MAX_VALUE; For (int I = spanCount - 1; i >= 0; i--) { if (legalLines.contains(i)) { if (spanList.get(i).getX() + spanList.get(i).getWidth() <= minSpace) { minSpace = spanList.get(i).getX() + spanList.get(i).getWidth(); bestLine = i; } } } return bestLine; }Copy the code

Do not know whether to have noticed that when defining display position normally on, it only took 1,2,4,7, because they are converted to a binary number is 001010100111, here with a clever idea, three digits represent the screen three locations, 0 means no barrage, 1 display barrage (ever) be suddenly enlightened

You can refer to the code, calculate the best position of the idea is like this:

  1. Convert the set position to a binary number to determine the display position
  2. Divide all the lines into three, the first two lines have the same number, round the number of the first line, and put all the lines to show the barrage into one set
  3. We loop through the top to see if there’s a free row, and we return that row, and that row is the best place for this itemView
  4. If there are no empty lines, the best position for this itemView is to return the maximum space from the bottom up

E. Set View according to type

Public void addTypeView(DanmuModel Model, View Child, Boresh) {super.addView(child); child.measure(0, 0); Int Width = child.getMeasuredWidth(); // Return measuredWidth (); int height = child.getMeasuredHeight(); Int bestLine = getBestLine(); // Set child.layout(WIDTH, singltLineHeight * bestLine, WIDTH + WIDTH, singltLineHeight * bestLine + height); InnerEntity innerEntity = null; innerEntity = (InnerEntity) child.getTag(R.id.tag_inner_entity); if (! isReused || innerEntity == null) { innerEntity = new InnerEntity(); } innerEntity.model = model; innerEntity.bestLine = bestLine; child.setTag(R.id.tag_inner_entity, innerEntity); spanList.set(bestLine, child); }Copy the code

I’m not going to say any more here, but I’m going to map the itemView’s Model to the best location and set the location;

And then I’m going to set the spanList (itemView collection) view.

Be sure to note: super.addView(child); child.measure(0, 0); These two words cannot be omitted!

F. Add barrage

Public void addDanmu(final DanmuModel) {if (adapter == null) {throw new Error("DanmuAdapter(an interface need to be implemented) can't be null,you should call setAdapter firstly"); } View dmView = null; if (adapter.getCacheSize() >= 1) { dmView = adapter.getView(model, adapter.removeViewFromCache(model.getType())); if (dmView == null) addTypeView(model, dmView, false); else addTypeView(model, dmView, true); } else { dmView = adapter.getView(model, null); addTypeView(model, dmView, false); } / / add to monitor dmView setOnClickListener (new an OnClickListener () {@ Override public void onClick (View v) { if(onItemClickListener ! = null) onItemClickListener.onItemClick(model); }}); }Copy the code

This method exposes the external method of setting the bullet-screen view. Note that itemView has cache, reuse, not reuse, ok.

G, the child thread calculates the time, sends the message, and the handler handles the view translation

private class MyRunnable implements Runnable { @Override public void run() { int count = 0; Message msg = null; while(true){ if(count < 7500){ count ++; } else{ count = 0; if(DanmuView.this.getChildCount() < adapter.getCacheSize() / 2){ adapter.shrinkCacheSize(); System.gc(); } } if(DanmuView.this.getChildCount() >= 0){ msg = new Message(); msg.what = 1; // Move view handler.sendMessage(MSG); } try { Thread.sleep(16); } catch (InterruptedException e) { e.printStackTrace(); }}}}Copy the code

Note: If the Adapter cache is too large, clear it in time. Refresh the itemView position every 16 milliseconds for a better visual effect; New Thread(new MyRunnable).start();

Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == 1) { for(int i=0; i<DanmuView.this.getChildCount(); i++){ View view = DanmuView.this.getChildAt(i); If (view.getx ()+ view.getwidth () >= 0) // Slide view.offsetLeftAndRight(0-speed); Else {// add to cache int Type = ((InnerEntity)view.getTag(R.i.D.tag_inner_entity)).model.getType(); adapter.addViewToCache(type,view); DanmuView.this.removeView(view); }}}}};Copy the code

No longer cumbersome, if you read the complete code, you can go to github to view the source and issue me ^_^github.com/shuaijia/Js…


Examples:

1. Entity classes

/** * Created by jia on 2017/9/25. */ public class MyDanmuModel extends DanmuModel {public String content; public int textColor; public String time; public String getTime() { return time; } public void setTime(String time) { this.time = time; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public int getTextColor() { return textColor; } public void setTextColor(int textColor) { this.textColor = textColor; }}Copy the code

2. Adapter

/** * Description: Bullet screen adapter * Created by JIA on 2017/9/25. */ public class MyDanmuAdapter extends DanmuAdapter<MyDanmuModel> {private Context Context; public MyDanmuAdapter(Context c){ super(); context = c; } @Override public int[] getViewTypeArray() { int type[] = {0}; return type; } @Override public int getSingleLineHeight() { View view = LayoutInflater.from(context).inflate(R.layout.item_danmu, null); Measure (0, 0); return view.getMeasuredHeight(); } @Override public View getView(MyDanmuModel entry, View convertView) { ViewHolder vh=null; if(convertView==null){ convertView= LayoutInflater.from(context).inflate(R.layout.item_danmu,null); vh=new ViewHolder(); vh.tv=convertView.findViewById(R.id.tv_danmu); convertView.setTag(vh); }else{ vh= (ViewHolder) convertView.getTag(); } vh.tv.setText(entry.getContent()); vh.tv.setTextColor(entry.getTextColor()); return convertView; } class ViewHolder{ TextView tv; }}Copy the code

Is there an Adapter that looks like ListView? I believe you will be able to understand, no more to say.

3. Configure basic information

jsplayer_danmu.setDanMuAdapter(new MyDanmuAdapter(this));
jsplayer_danmu.setDanMuGravity(3);
jsplayer_danmu.setDanMuSpeed(DanmuView.NORMAL_SPEED);Copy the code

4. Create the entity class and set it to DanmuView

MyDanmuModel danmuEntity = new MyDanmuModel();
danmuEntity.setType(0);
danmuEntity.setContent(DANMU[random.nextInt(8)]);
danmuEntity.setTextColor(COLOR[random.nextInt(4)]);
jsplayer_danmu.addDanmu(danmuEntity);Copy the code

More exciting content, please pay attention to my wechat public number – Android dry goods camp