In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-04-02 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
Editor to share with you how to achieve Android suspension window, I believe that most people do not know much about it, so share this article for your reference, I hope you can learn a lot after reading this article, let's go to understand it!
The effect is as follows:
Display floating window
The native ViewManager interface provides a way to add and manipulate View to windows:
Public interface ViewManager {/ / 'add view to window' public void addView (View view, ViewGroup.LayoutParams params); / / 'update view in window' public void updateViewLayout (View view, ViewGroup.LayoutParams params); / / 'remove view in window' public void removeView (View view);} copy code
The template code for using this API to display the window is as follows:
/ / 'parse the layout file as a view' val windowView = LayoutInflater.from (context) .resolate (R.id.window_view Null) / / 'get WindowManager system Services' val windowManager = context.getSystemService (Context.WINDOW_SERVICE) as WindowManager//' build window layout parameter 'WindowManager.LayoutParams (). Apply {type = WindowManager.LayoutParams.TYPE_APPLICATION width = WindowManager.LayoutParams.WRAP_CONTENT height = Gravity.START or Gravity.TOP x = 0 y = 0} .let {layoutParams- > / /' add View to window 'windowManager.addView (windowView LayoutParams)} copy the code
The above code displays the layout defined in R.id.window_view.xml in the upper-left corner of the current interface.
To avoid repetition, abstract this code into a function in which the window view content and display location will change according to the requirements, and then parameterize it:
Object FloatWindow {private var context: Context? = null / / 'current window parameters' var windowInfo: WindowInfo? = null / / 'package the parameters related to the Window layout into an inner class' class WindowInfo (var view: View?) {var layoutParams: WindowManager.LayoutParams? = null / / 'window width' var width: Int = 0 / / 'window height' Var height: Int = 0 / / 'whether there is a view in the window' fun hasView () = view! = null & & layoutParams! = null / / 'whether the view in the window has a parent' fun hasParent () = hasView () & & view?.parent! = null} / / 'display window' fun show (context: Context WindowInfo: WindowInfo?, x: Int = windowInfo?.layoutParams?.x.value (), y: Int = windowInfo?.layoutParams?.y.value (),) {if (windowInfo = = null) {return} if (windowInfo.view = = null) {return} this.windowInfo = windowInfo this.context = context / / 'create window layout parameter' windowInfo.layoutParams = createLayoutParam (x Y) / 'display window' if (! windowInfo.hasParent (). Value ()) {val windowManager = this.context?.getSystemService (Context.WINDOW_SERVICE) as WindowManager windowManager.addView (windowInfo.view, windowInfo.layoutParams)}} / / 'create window layout parameters' private fun createLayoutParam (x: Int) Y: Int): WindowManager.LayoutParams {if (context = = null) {return WindowManager.LayoutParams ()} return WindowManager.LayoutParams (). Apply {/ / 'this type does not require application permission' type = WindowManager.LayoutParams.TYPE_APPLICATION format = PixelFormat.TRANSLUCENT flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS Gravity = Gravity.START or Gravity.TOP width = windowInfo?.width.value () height = windowInfo?.height.value () this.x = x this.y = y}} / / 'provide default value for empty Int' fun Int?.value () = this?: 0} copy code
FloatWindow is declared as a singleton so that floating windows can be easily displayed in any interface throughout the life cycle of app.
To facilitate the unified management of window parameters, the inner class WindowInfo is abstracted.
Now you can display a floating window in the upper left corner of the screen like this:
Val windowView = LayoutInflater.from (context) .propagate (R.id.window_view, null) WindowInfo (windowView) .apply {width = 100height = 100} .let {windowInfo-> FloatWindow.show (context, windowInfo, 0,0)} copy the code float background color
The product requires that the screen be dimmed when the floating window is displayed. By setting WindowManager.LayoutParams.FLAG_DIM_BEHIND tags with dimAmount, you can easily achieve:
Object FloatWindow {/ / current window parameter var windowInfo: WindowInfo? = null private fun createLayoutParam (x: Int Y: Int): WindowManager.LayoutParams {if (context = = null) {return WindowManager.LayoutParams ()} return WindowManager.LayoutParams (). Apply {type = WindowManager.LayoutParams.TYPE_APPLICATION format = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or / / 'set the floating window background to darken 'WindowManager.LayoutParams.FLAG_DIM_BEHIND / /' set the default darkening degree to 0 That is, it doesn't get dark. 1 means all black 'dimAmount = 0f gravity = Gravity.START or Gravity.TOP width = windowInfo?.width.value () height = windowInfo?.height.value () this.x = x this.y = y}} / /' for the business interface to adjust the light and dark background of the floating window when needed 'fun setDimAmount (amount) : Float) {windowInfo?.layoutParams?.let {it.dimAmount = amount}} copy code settings floating window click event
Setting a click event for a floating window is equivalent to setting a click event for a floating window view, but if you use setOnClickListener () directly for the floating window view, the touch event of the floating window will not be responded to, and drag and drop cannot be achieved. So we can only start with the touch event at the bottom:
Object FloatWindow: View.OnTouchListener {/ / 'display window' fun show (context: Context, windowInfo: WindowInfo?, x: Int = windowInfo?.layoutParams?.x.value (), y: Int = windowInfo?.layoutParams?.y.value () ) {if (windowInfo = = null) {return} if (windowInfo.view = = null) {return} this.windowInfo = windowInfo this.context = context / / 'set touch listener for floating window view' windowInfo.view?.setOnTouchListener (this) windowInfo.layoutParams = createLayoutParam (x Y) if (! windowInfo.hasParent (). Value ()) {val windowManager = this.context?.getSystemService (Context.WINDOW_SERVICE) as WindowManager windowManager.addView (windowInfo.view, windowInfo.layoutParams)}} override fun onTouch (v: View, event: MotionEvent): Boolean {return false}} copy the code
More detailed touch events, such as ACTION_DOWN,ACTION_MOVE and ACTION_UP, can be found in onTouch (v: View, event: MotionEvent). This facilitates the implementation of drag-and-drop, but the capture of click events becomes complicated because you need to define the sequence in which the above three ACTION appear before they are determined as click events. Fortunately, GestureDetector did this for us:
Public class GestureDetector {public interface OnGestureListener {/ / 'ACTION_DOWN event' boolean onDown (MotionEvent e); / / 'click event' boolean onSingleTapUp (MotionEvent e); / / 'drag event' boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);.} copy the code
Building a GestureDetector instance and passing the MotionEvent to it parses the touch event into an upper-level event of interest:
Object FloatWindow: View.OnTouchListener {private var gestureDetector: GestureDetector = GestureDetector (context, GestureListener ()) private var clickListener: WindowClickListener? = null private var lastTouchX: Int = 0 private var lastTouchY: Int = 0 / / 'set click listener for floating window' fun setClickListener (listener: WindowClickListener) {clickListener = listener} override fun onTouch (v: View Event: MotionEvent): Boolean {/ / 'passes touch events to GestureDetector parsing' gestureDetector.onTouchEvent (event) return true} / / 'memory initial touch point coordinates' private fun onActionDown (event: MotionEvent) {lastTouchX = event.rawX.toInt () lastTouchY = event.rawY.toInt ()} private class GestureListener: GestureDetector.OnGestureListener {/ / 'memory Starting touchpoint coordinates' override fun onDown (e: MotionEvent): Boolean {onActionDown (e) return false} override fun onSingleTapUp (e: MotionEvent): Boolean {/ / 'when the click event occurs Call the listener 'return clickListener?.onWindowClick (windowInfo)?: false}...} / /' floating window Click listener 'interface WindowClickListener {fun onWindowClick (windowInfo: WindowInfo?): Boolean}} copy the code to drag the floating window
ViewManager provides updateViewLayout (View view, ViewGroup.LayoutParams params) to update the floating window position, so you only need to listen for ACTION_MOVE events and update the floating window view position in real time to achieve drag and drop. The ACTION_MOVE event is parsed by GestureDetector into an OnGestureListener.onScroll () callback:
Object FloatWindow: View.OnTouchListener {private var gestureDetector: GestureDetector = GestureDetector (context, GestureListener ()) private var lastTouchX: Int = 0 private var lastTouchY: Int = 0 override fun onTouch (v: View) Event: MotionEvent): Boolean {/ / 'passes touch events to GestureDetector parsing' gestureDetector.onTouchEvent (event) return true} private class GestureListener: GestureDetector.OnGestureListener {override fun onDown (e: MotionEvent): Boolean {onActionDown (e) return false} override fun onScroll (E1: MotionEvent,e2: MotionEvent,distanceX: Float DistanceY:Float): Boolean {/ / 'respond to finger scrolling event' onActionMove (E2) return true}} private fun onActionMove (event: MotionEvent) {/ / 'get current finger coordinates' val currentX = event.rawX.toInt () val currentY = event.rawY.toInt () / / 'get finger movement Increment 'val dx = currentX-lastTouchX val dy = currentY-lastTouchY / /' apply move increment to window layout parameters. X + = dx windowInfotainment. LayoutParamsincrements. Y + = dy val windowManager = context?.getSystemService (Context.WINDOW_SERVICE) as WindowManager var rightMost = screenWidth-windowInfootes. LayoutParamslayoutParamslays.width var leftMost = 0 val topMost = 0 val bottomMost = screenHeight-windowInfotainment .layoutParamsroom.height-getNavigationBarHeight (context) / / 'restrict the floating window movement area to the on-screen' if (windowInfooth.layoutParamsfloat.x
< leftMost) { windowInfo?.layoutParams!!.x = leftMost } if (windowInfo?.layoutParams!!.x >RightMost) {windowInfooth.layoutParamsroom.x = rightMost} if (windowInfooth.layoutParamsroom.y
< topMost) { windowInfo?.layoutParams!!.y = topMost } if (windowInfo?.layoutParams!!.y >BottomMost) {windowInfostructures .layoutParamslocations. Y = bottomMost} / / 'Update floating window location' windowManager.updateViewLayout (windowInfo?.view, windowInfo?.layoutParams) lastTouchX = currentX lastTouchY = currentY}} copy code floating window automatic edge pasting
New demand comes, after dragging and dragging the floating window to let go, it needs to be pasted automatically.
Understand the trimming as a horizontal displacement animation. Find out the Abscissa of the starting point and end point of the drawing when you release your hand, and constantly update the position of the floating window with animation values::
Object FloatWindow: View.OnTouchListener {private var gestureDetector: GestureDetector = GestureDetector (context, GestureListener ()) private var lastTouchX: Int = 0 private var lastTouchY: Int = 0 / / 'Edge Animation' private var weltAnimator: ValueAnimator? = null override fun onTouch (v: View Event: MotionEvent): Boolean {/ / 'passes touch events to GestureDetector parsing' gestureDetector.onTouchEvent (event) / / 'handles ACTION_UP events' val action = event.action when (action) {MotionEvent.ACTION_UP-> onActionUp (event, screenWidth) WindowInfo?.width?: 0) else-> {}} return true} private fun onActionUp (event: MotionEvent, screenWidth: Int Width: Int) {if (! windowInfo?.hasView (). Value ()) {return} / / 'record hand lift Abscissa' val upX = event.rawX.toInt () / / 'Edge Animation end Abscissa' val endX = if (upX > screenWidth / 2) {screenWidth-width} else {0} / / 'build Edge Animation' if (weltAnimator = = null) {weltAnimator = ValueAnimator.ofInt (windowInfotainment. LayoutParamsanimation animation .x EndX) .apply {interpolator = LinearInterpolator () duration = 300addUpdateListener {animation-> val x = animation.animatedValue as Int if (windowInfo?.layoutParams! = null) {windowInfotainment .layoutParamsroom.x = x} Val windowManager = context?.getSystemService (Context.WINDOW_SERVICE) as WindowManager / / 'update window location' if (windowInfo?.hasParent () .value ()) {windowManager.updateViewLayout (windowInfo?.view) WindowInfo?.layoutParams)} weltAnimator?.setIntValues (windowInfooth.layoutParamsroom.x, endX) weltAnimator?.start ()} / / provide the default value for empty Boolean fun Boolean?.value () = this?: false} copy code
The ACTION_UP event is swallowed after GestureDetector parsing, so it can only be intercepted in onTouch ().
According to the relationship between the Abscissa of the raised hand and the Abscissa of the point in the screen, determine whether the floating window should be attached to the left or right.
Manage multiple floating windows
If different business interfaces of app need to display floating window at the same time: display floating window A when entering interface A, then it is dragged to the lower right corner, exit interface An and enter interface B, display floating window B. when entering interface An again, it is expected to restore the position of floating window A when it left last time.
Currently, windowInfo members are used in FloatWindow to store a single floating window parameter. In order to manage multiple floating windows at the same time, all floating window parameters need to be saved in the Map structure and distinguished by tag:
Object FloatWindow: View.OnTouchListener {/ / 'floating window parameter container' private var windowInfoMap: HashMap = HashMap () / / 'current floating window parameter' var windowInfo: WindowInfo? = null / / 'display floating window' fun show (context: Context, / / 'floating window tag' tag: String / /'if the floating window parameter is not provided, the last saved parameter of the tag is obtained from the parameter container 'windowInfo: WindowInfo? = windowInfoMap [tag], x: Int = windowInfo?.layoutParams?.x.value () Y: Int = windowInfo?.layoutParams?.y.value () {if (windowInfo = = null) {return} if (windowInfo.view = = null) {return} / / 'Update current floating window parameter' this.windowInfo = windowInfo / / 'store floating window parameter in container' windowInfoMap [tag] = windowInfo windowInfo.view?.setOnTouchListener (this) This.context = context windowInfo.layoutParams = createLayoutParam (x Y) if (! windowInfo.hasParent () .value ()) {val windowManager = this.context?.getSystemService (Context.WINDOW_SERVICE) as WindowManager windowManager.addView (windowInfo.view, windowInfo.layoutParams)}} copy the code
When the floating window is displayed, the tag tag parameter is added to uniquely identify the floating window, and the default parameter is provided for windowInfo. When the original floating window is restored, the windowInfo parameter may not be provided, and FloatWindow will go to windowInfoMap to find the corresponding windowInfo based on the given tag.
Monitor the event of clicking outside the floating window
When the new demand comes, when you click on the floating window, the edge floating window is displayed like a drawer, and when you click on the area outside the floating window, the drawer is folded.
When I first received this new demand, I had no idea. On second thought, PopupWindow has a setOutsideTouchable ():
Public class PopupWindow {/ *
Controls whether the pop-up will be informed of touch events outside * of its window. * * @ param touchable true if the popup should receive outside * touch events, false otherwise * / public void setOutsideTouchable (boolean touchable) {mOutsideTouchable = touchable;}} copy the code
This function is used to set whether touch events outside the window boundary are allowed to be passed to the window. Tracking the mOutsideTouchable variable should lead to more clues:
Public class PopupWindow {private int computeFlags (int curFlags) {curFlags & = ~ (WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH) If it is touchable out of bounds, assign FLAG_WATCH_OUTSIDE_TOUCH to flag' if (mOutsideTouchable) {curFlags | = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;}...} copy the code
Continue to trace up where the computeFlags () call is made:
Public class PopupWindow {protected final WindowManager.LayoutParams createPopupLayoutParams (IBinder token) {final WindowManager.LayoutParams p = new WindowManager.LayoutParams (); p.gravity = computeGravity (); / / 'calculates the window layout parameter flag attribute and assigns the value' p.flags = computeFlags (p.flags); p.type = mWindowLayoutType; p.token = token;.}} copy the code
CreatePopupLayoutParams () is called when the window is displayed:
Public class PopupWindow {public void showAtLocation (IBinder token, int gravity, int x, int y) {if (isShowing () | mContentView = = null) {return;} TransitionManager.endTransitions (mDecorView); detachFromAnchor (); mIsShowing = true; mIsDropdown = false; mGravity = gravity; / / 'build window layout parameters' final WindowManager.LayoutParams p = createPopupLayoutParams (token); preparePopup (p) P.X = x; p.y = y; invokePopup (p);}} copy the code
Want to continue to search in the source code, but when it comes to FLAG_WATCH_OUTSIDE_TOUCH, the clue is broken. All we know now is that in order for the out-of-bounds click event to be passed to window, you must set FLAG_WATCH_OUTSIDE_TOUCH for the layout parameter. But where should the event response logic be written?
When PopupWindow.setOutsideTouchable (true) is called, the window disappears when clicked outside the window bounds. This must be a call to dismiss () to find the response logic for the out-of-bounds click event along the call chain of dismiss ():
Public class PopupWindow {/ / 'window root view' private class PopupDecorView extends FrameLayout {/ / 'window root view touch event' @ Override public boolean onTouchEvent (MotionEvent event) {final int x = (int) event.getX (); final int y = (int) event.getY () If ((event.getAction ()) = = MotionEvent.ACTION_DOWN) & ((x)
< 0) || (x >= getWidth () | | (y)
< 0) || (y >= getHeight ()) {dismiss (); return true; / / 'dissolve the window if an out-of-bounds touch event occurs'} else if (event.getAction () = = MotionEvent.ACTION_OUTSIDE) {dismiss (); return true;} else {return super.onTouchEvent (event) Copy the code
So you only need to capture ACTION_OUTSIDE in the touch event callback in the window root view:
Object FloatWindow: View.OnTouchListener {/ / 'out-of-bounds touch event callback' private var onTouchOutside: (()-> Unit)? = null / / 'set whether to respond to out-of-bounds click events' fun setOutsideTouchable (enable: Boolean) OnTouchOutside: (()-> Unit)? = null) {windowInfo?.layoutParams?.let {layoutParams-> layoutParams.flags = layoutParams.flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH this.onTouchOutside = onTouchOutside}} override fun onTouch (v: View Event: MotionEvent): Boolean {/ / 'out of bounds Touch event handling' if (event.action = = MotionEvent.ACTION_OUTSIDE) {onTouchOutside?.invoke () return true} / / 'Click and drop event handling' gestureDetector.onTouchEvent (event). TakeIf {! it}? .also {/ / there is no ACTION_UP event in GestureDetector val action = event.action when (action) {MotionEvent.ACTION_UP-> onActionUp (event ScreenWidth, windowInfo?.width?: 0) else-> {} return true}} copy the code above is all the content of the article "how to implement Android suspension window" Thank you for reading! I believe we all have a certain understanding, hope to share the content to help you, if you want to learn more knowledge, welcome to follow the industry information channel!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.