In order to increase user experience in actual project development, design students will design empty data interface, error page, network loading page and so on. Is usually the way we handle for each page to write a layout file, and then the need to page through the include tag load in, in the need to display the local control of the hide and display, this kind of writing no problem, just need to manually modify the layout of the interface file, nested these layout. There are also a lot of small partners on the Internet for this problem control encapsulation, but ultimately can not avoid changing the XML code, so is there a good way to reduce the intrusion of the layout file?

In fact, it’s not difficult to solve this problem, at least in the majority of cases this solution will satisfy the requirements, why in the majority of cases? Look down.

The solution to this problem is to dynamically add or remove the View we want to display through Windows Manager.

First look at the Demo implementation

Use steps:

Application in the project

AresLayout. Init (this) / / cache Application Context reference AresLayout. SetEmptyLayout (R.l ayout. Layout_empty) / / set the layout of the empty data interface AresLayout. SetLoadingLayout (R.l ayout. Layout_loading) / / set the layout of the load AresLayout. SetNetworkErrorLayout (R.l ayout. Layout_network_error) / / set the layout of the network errorCopy the code

Just call the following three methods where you want to show them

AresLayout.showEmptyLayout(content, windowManager)

AresLayout.showLoadingLayout(content, windowManager)

AresLayout.showNetworkErrorLayout(content, windowManager)
Copy the code

The two arguments are the View to override and the windowManager for the current Activity.

Notice that we are not modifying the XML code for the current interface, which makes the code much less intrusive.

Here’s the idea (if you’re familiar with how Windows Manager works, you already know it)

ShowEmptyLayout, showLoadingLayout, showNetworkErrorLayout is the same principle, let’s take showEmptyLayout to load empty data page

First AresLayout is a singleton in which the class setEmptyLayout method gets a reference to the current layout file

Public fun setEmptyLayout(resId: Int) { emptyLayout = getLayout(resId) emptyLayout.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) } private lateinit var emptyLayout: ViewGroupCopy the code

Note that emptyLayout is a ViewGroup, not a specific subclass of it such as LinearLayout, because it is now uncertain what type of root layout is provided in the empty data XML. The corresponding getLayout(resId) returns a ViewGroup

private fun getLayout(resId: Int): ViewGroup {
    val inflater = mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
    return inflater.inflate(resId, null) as ViewGroup
}
Copy the code

Next up is the showEmptyLayout method

*/ Fun showEmptyLayout(Target: View, wm: WindowManager) { if (currentLayout ! = null) { wm.removeView(currentLayout) } isAresShowing = true currentLayout = emptyLayout wm.addView(currentLayout, setLayoutParams(target)) }Copy the code

In this method, we assign the emptyLayout set in the previous step to currentLayout, so that we can only manipulate currentLayout when switching between screens. Finally through

  wm.addView(currentLayout, setLayoutParams(target))
Copy the code

The setLayoutParams(Target) method controls the size and location of the layout displayed on the current Activity

private fun setLayoutParams(target: View): WindowManager.LayoutParams {
    val wlp = WindowManager.LayoutParams()
    wlp.format = PixelFormat.TRANSPARENT
    wlp.flags = (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
            or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
    var location = IntArray(2)
    target.getLocationOnScreen(location)
    wlp.x = location[0]
    wlp.y = location[1]
    wlp.height = target.height
    wlp.width = target.width
    wlp.type = WindowManager.LayoutParams.FIRST_SUB_WINDOW
    wlp.gravity = Gravity.LEFT or Gravity.TOP
    return wlp
}
Copy the code

The setLayoutParams() method needs to pass in a View, and the position and size of the View in the screen is the location and size of the empty data interface. In the list display page, the View can be RecyclerView. In other cases, the View can be passed in the corresponding position. So the content in the Demo, for example, is actually a TextView.

var location = IntArray(2)
target.getLocationOnScreen(location)
Copy the code

Gets the position of the View on the screen

    wlp.x = location[0]
    wlp.y = location[1]
    wlp.height = target.height
    wlp.width = target.width
Copy the code

Set the WindowManager. LayoutParams () in the screen shows the position and size

Notice these two lines of code

wlp.type = WindowManager.LayoutParams.FIRST_SUB_WINDOW
wlp.gravity = Gravity.LEFT or Gravity.TOP
Copy the code

The value of type is FIRST_SUB_WINDOW, which means Window is the panel Window displayed above the host Window. (There are many other values for type, others haven’t been tried yet.)

Gravity.LEFT or Gravity.TOP is used to solve the margin problem of the View. This way, our emptyLayout will completely overlap with the incoming View, which is our ultimate goal.

When no display is needed or the interface exits, simply remove the View from the current Window

fun onDestroy(wm: WindowManager) { isAresShowing = false currentLayout? .let { wm.removeView(currentLayout) currentLayout = null } }Copy the code

Why is this used in most cases? Because the bottom one doesn’t work

If the RecyclerView section is used to create an empty data page, the RecyclerView section will be used to create an empty data page. If the RecyclerView section is used to create an empty data page, the RecyclerView section will be used to create an empty data page. Because both components, while ultimately added through WindowManager, are not on the same view.

This problem has no good idea to solve, if you have a better solution, welcome to leave a message!

Finally, the demo address is attached:

Github.com/shiweibsw/A…