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…