preface

Unread little red dot, unread number, many apps have this requirement, there are also many third-party libraries that provide this function. This article is called bubble View. I have looked at several third-party libraries and found none satisfactory. The main points are as follows:

  • Bubble View is written to death, such as fixed to only display text
  • Add bubble View and target View to the same container
  • The bubble View position cannot be customized

In order to solve the above problems, we tried another implementation scheme. Let’s have a look at the general effect and then introduce it below:

Implement process analysis

To avoid invading the target View, you need to display the bubble View in a separate container, but this causes the following problems:

Q.1. How do I synchronize visibility?

For example: when the target View changes from invisible to visible, do I need to display the bubble View? To analyze the situation:

  • If the unread number is 0, the App service hides the bubble View, and the target View becomes visible, but the bubble View cannot be displayed
  • When the unread number is greater than 0, App services display bubble View, and the target View status becomes visible, so bubble View should be displayed

When the target View status becomes visible in the library, there is no way to determine whether to display the bubble View, because the bubble View itself has a display and hide logic, which is the business logic of the App.

Solution: Add a parent container to the bubble View and synchronize the visible state to the parent container. When the target View is visible, the bubble View parent container is displayed. When the target View is not visible, the bubble View parent container is hidden.

Finally, add the bubble View parent to a container so that the bubble View’s display-hide logic is independent of the library logic.

Question 2. How to align?

  1. Gets the target View screen coordinates
  2. Gets the screen coordinates of the bubble View’s parent container
  3. Calculate the offsets of the two sets of coordinates in the x and y directions
  4. Set the bubble View position with the calculated offset

For example, if you want the bubble View to align with the upper left corner of the target View:

  1. Target View screen coordinates: x=100, y=100
  2. Bubble View parent container screen coordinates: x=50, y=50
  3. Subtract the target View coordinates from the bubble View parent container coordinates to calculate the offset: x=50, y=50

It is important to note that the offset calculated in step 3 is actually the offset of the Bubble View relative to the parent container, not the offset relative to the upper-left corner of the screen.

According to the calculation results in step 3, the coordinate of the bubble View relative to the parent container is x=50, y=50, so the coordinate relative to the screen is the current coordinate plus the screen coordinate of the parent container, that is, x=100, y=100. This way, the top left corner of the Bubble View is aligned with the target View.

Use pictures to illustrate the hierarchy:

Question 3. How to align in real time?

Although you know how to align, the target View may change in size and position, and the bubble View may change in size and then become misaligned. So you need to listen to the change of the target View and the bubble View, recalculate the coordinates and update the position.

We can use the following two interfaces to listen on views:

  • Listen to the target Viewandroid.view.ViewTreeObserver.OnPreDrawListener
  • Listen to bubble Viewandroid.View.OnLayoutChangeListener

These two interfaces, by default, are understood by the reader and will not be repeated. Now that you’re all clear, you can start writing code. Let’s move on.

The implementation code

The main implementation steps are: monitor the change of bubble View and target View, obtain coordinates, calculate offset, and set the position of the bubble View with offset. Because this article mainly introduces the implementation ideas, the specific implementation details did not expand the description, only provides the encapsulated example code.

1. Listen to the View refresh

The ViewUpdater interface is used to monitor the refresh of a View and obtain the size and position of the View. Sample code:

ViewUpdater updater = new OnPreDrawUpdater();
updater.setView(view); // Set the View to listen on
updater.setUpdatable(new ViewUpdater.Updatable() {
    @Override
    public void update(a) {
        // the callback triggers the calculation logic}}); updater.start();// Start listening
Copy the code

OnPreDrawUpdater by android. View. ViewTreeObserver. OnPreDrawListener interface, the default also provides OnLayoutChangeUpdater, OnGlobalLayoutChangeUpdater, the main difference is the refresh rate is different.

Readers can also implement custom refresh logic of the ViewUpdater interface.

2. Calculate coordinates

The ViewTrack interface calculates the offset of the bubble View relative to the parent container when the bubble View is aligned with the target View. Sample code:

ViewTracker tracker = new FViewTracker();
tracker.setSource(popView); // Set the bubble view
tracker.setTarget(targetView); // Set the target view
tracker.setPosition(Position.TopLeft); // Set the alignment
tracker.setCallback(new ViewTracker.Callback() {
    @Override
    public void onUpdate(int x, int y, @NonNull View source, @NonNull View target) {
        // Callback the bubble view's offset relative to the parent container}}); tracker.update();// Triggers a calculation
Copy the code

At this point, the listening and calculating code is wrapped, and then you can write the bubble View library, continue to see.

3. Bubble View library encapsulation

The current approach is to wrap the code for steps 1 and 2 into a separate library that the Bubble View library relies on to provide interfaces. Poper interface sample code:

Poper poper = new FPoper(this)
        .setPopView(popView) // Set the bubble View, which can be a layout ID or a View object
        .setTarget(targetView) // Set the target View
        .setPosition(Poper.Position.TopLeft)  // Set upper-left alignment, default upper-right alignment
        .setMarginX(0) > 0 to the right, < 0 to the left, default 0
        .setMarginY(0) // Set the spacing in the y direction after alignment, greater than 0 down, less than 0 up, default 0
        .attach(true); // true- attach target view, false- remove attachment
Copy the code

The bubble View library provides the interface that we’ll eventually use in our App, the Poper interface for a complete list of methods, click here.

Let’s take a look at some of the details or questions that haven’t been told.

Matters needing attention

1. Bubble View display range

Notice that the bubble View parent container and the bubble View parent container, the two containers, should not be confused.

  • Bubble View parent container

SimplePoperParent, which is essentially a FrameLayout, implements the PoperParent interface and rewrites the onLayout method. Currently, it does not support customization. Interested readers can look at the source code.

/** * Bubble View parent container */
interface PoperParent {
    /** * adds the current container to the specified container */
    void attachToContainer(@NonNull ViewGroup container);

    /** * Add bubble View to current container */
    void addPopView(@NonNull View popView);

    /** * Synchronize the visible state of the target View to the current container **@paramIsShown true- Target View visible, false- target View invisible */
    void synchronizeVisibilityWithTarget(boolean isShown);

    /** ** Remove yourself */
    void removeSelf(a);
}
Copy the code

The addPopView() method adds the bubble View to the parent, and the attachToContainer() method adds the bubble View parent to the specified display container, which is described below. Other methods to see the notes, better understanding will not repeat.

  • Bubble View’s parent container

The default implementation of the library is to add the bubble View parent to the Activity’s container with the ID android.r.D.C. Tent, which is match_parent in width and height, meaning that by default the bubble View is visible across the entire screen. The android.r.I. C tent container is intended for readers to understand by default.

  • How to handle the target View in the window?

Since the window is displayed at the top of the Activity, it will block the default container android.r.d.c. tent, so you need to specify the container in the window to display the bubble View, that is, the parent of the bubble View will be added to the container, Poper interface method:

/** * Set the display scope of the bubble View. The default is the container in the Activity whose ID is Android.r.d.c. tent */
@NonNull
Poper setContainer(@Nullable ViewGroup container);
Copy the code

2. Customize alignment

The library provides nine alignments by default, as shown at the beginning of this article. How do I customize it? For example, to make the bubble View appear below the target View, the top of the bubble View is aligned with the bottom of the target View.

First set poper.position. Bottom to align the Bottom of the bubble View with the Bottom of the target View. Then set margin to offset the height of the bubble View downward.

When poper. attach(true) is called, the bubble View and target View may not have been laid out, so we cannot get the size of the bubble View, so we need to dynamically obtain the Margin value through the Margin interface.

interface Margin {
    /** * returns spacing */
    int getMargin(a);
}
Copy the code
Poper poper = new FPoper(this)
        .setPopView(popView)
        .setTarget(targetView)
        .setPosition(Poper.Position.Bottom)
        .setMarginY(new Poper.Margin() {
            @Override
            public int getMargin(a) {
                // Offset the height of the bubble View
                return popView.getHeight();
            }
        })
        .attach(true);
Copy the code

3. Customize Layouter

After obtaining the offset, the library surface sets the position of the bubble View via the Layouter interface:

interface Layouter {
    /** * position callback **@paramX bubble View relative to the parent container x value *@paramY bubble View relative to the parent container y value *@paramPopView Bubble View *@paramTarget Target view */
    void layout(int x, int y, @NonNull View popView, @NonNull View target);
}
Copy the code
  • DefaultLayouter

DefaultLayouter is the default implementation of setting the position of the bubble View as follows:

public class DefaultLayouter implements Poper.Layouter {
    @Override
    public void layout(int x, int y, @NonNull View popView, @NonNull View target) { popView.offsetLeftAndRight(x - popView.getLeft()); popView.offsetTopAndBottom(y - popView.getTop()); }}Copy the code

The view.offsetLeftandRight () and view.offsettopAndBottom () methods are used to change the left and top attributes of the View.

  • FixBoundaryLayouter

If the width and height of the bubble View are large, DefaultLayouter’s implementation may result in the bubble View being offset beyond the parent layout and not fully displayed, such as the bubble View being a list with a lot of data.

At this point, FixBoundaryLayouter is needed to fix the viewGroup.layoutParams parameter value of the Bubble View so that its contents can be fully displayed. Sample code:

// CombineLayouter can combine multiple Layouter
Poper.Layouter layouter = new CombineLayouter(
        / / the default Layouter
        new DefaultLayouter(),
        // Modify bubble View height
        new FixBoundaryLayouter(FixBoundaryLayouter.Boundary.Height)
);

Poper poper = new FPoper(this)
        .setPopView(popView)
        .setTarget(targetView)
        .setPosition(Poper.Position.Bottom)
        .setLayouter(layouter) / / set the Layouter
        .attach(true);
Copy the code

If readers need to customize, they can implement the Layouter interface and set the object to Poper.

The end of the

I started to write this library three or four years ago. After iterating some versions one by one, I finally found time to write an introduction. If there is something wrong, I also asked readers to correct it and discuss and learn together.

Bubble View: Poper Email: [email protected]