Now, a normal activity page expects that after the dialog box pops up, the blank area can still be used for sliding and clicking operations, that is, it hopes to be transparently transmitted to the following activity, while the original click and sliding operations in the dialog view should not be affected. This requirement sounds redundant and tricky. The purpose of pop-ups is to enhance reminders and masking, and removing them now loses the point of using dialog boxes, but iOS can do it! So have to look at the source code implementation.
A Dialog in Android is known to be a view added to another Window instance, higher than the Widnow level on which the Activity resides. If only for effect, then Dialog should not be used at all! PopupWindow = PopupWindow = PopupWindow = PopupWindow = PopupWindow = PopupWindow = PopupWindow = PopupWindow So the problem with many implementations is not the implementation itself, but too much application context.
Only in view of the Dialog to modify, this post gives us a inspired (although the post has no answer), that is to use the Activity. The dispatchTouchEvent! Pass events that are not consumed to the activity instance as a whole, so that the event can be passed through without affecting the original dialog view operations! So where do Touch events come from? Create Dialog Dialog ();
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); final Window w = new PhoneWindow(mContext); mWindow = w; w.setCallback(this); w.setOnWindowDismissedCallback(this); w.setOnWindowSwipeDismissedCallback(() -> { if (mCancelable) { cancel(); }});Copy the code
The Dialog creates an instance of the Window, most crucially w.setCallback(this), All callbacks to the window are passed to the Dialog object, which has public Boolean dispatchTouchEvent(MotionEvent Event); AppCompatDialog, AlertDialog and other subclasses of dialog have methods that can be overridden: Public Boolean onTouchEvent(@nonnull MotionEvent event) public Boolean onTouchEvent(@nonnull MotionEvent Event) public Boolean onTouchEvent(@nonnull MotionEvent Event) A small delegate, so there’s:
class YourDialog(context: Context) : AlertDialog(context, resolveDialogTheme(context, 0)) {
private val activity = if (context is Activity) context else null
companion object {
// Copy from AlertDialog.resolveDialogTheme
fun resolveDialogTheme(context: Context.@StyleRes resid: Int): Int {
// Check to see if this resourceId has a valid package ID.
return if (resid ushr 24 and 0x000000ff> =0x00000001) { // start of real resource IDs.
resid
} else {
val outValue = TypedValue()
context.theme.resolveAttribute(android.R.attr.alertDialogTheme, outValue, true)
outValue.resourceId
}
}
}
override fun onTouchEvent(ev: MotionEvent): Boolean {
return super.onTouchEvent(ev) || passThrough(ev)
}
private fun passThrough(ev: MotionEvent): Boolean {
returnactivity? .dispatchTouchEvent(ev) ? :false}}Copy the code
resolveDialogTheme
This method is used to get the corresponding topic of the Dialog, because package Access can only be copied from it;- Another issue to be aware of is the activity instanceCan’tGets it from the dialog context, and its actual type is
ContextThemeWrapper
; - The third thing passed through to the activity is that the Dialog does not consume events, so
onTouchEvent
Called only if false is returnedpassThrough
. - Finally, and especially, there are two ways to set up a dialog:
setCanceledOnTouchOutside(false)
setCancelable(false)
Copy the code
What happens if you don’t have these 2 rows? If you actually run it, you’ll see that the last two problems are actually one problem
This implementation is simple, safe and even elegant
— — — — — — — — — — 0628
Sure enough, there is still a problem!
Found that scrolling is fine, but the click action is offset! The y coordinate is always offset by a distance of statusbarHeight, and the Activity passed to the lower level is clicked upwards, although the statusbar height can be obtained and the MotionEvent (x, Y), but the underlying activity may also be FULLSCREEN, which may introduce problems. Finally, we find that the actual y we need is the rawY of the MotionEvent, so we create a new MotionEvent object and pass it in.
private fun passThrough(ev: MotionEvent): Boolean {
- return activity? .dispatchTouchEvent(ev) ? : false
+ val e = MotionEvent.obtain(ev.downTime, ev.eventTime, ev.action,
+ ev.rawX, ev.rawY,
+ ev.pressure, ev.size, ev.metaState, ev.xPrecision, ev.yPrecision, ev.deviceId, ev.edgeFlags)
+ return activity? .dispatchTouchEvent(e) ? : false
}
Copy the code
The raw location (ev.rawx, ev.rawy)(instead of (ev.x, ev.y + statusBarHeight)) is the location we need to pass through to the activity so that it can decide how to handle touches without forcing changes to the location.
Another problem: The input method does not pop up when you click the input box in the lower activity view!
This one is a bit tricky, but usually input methods don’t pop because they don’t get focus (without any other errors), is there a way for dialog to remove focus or disallow focus? Fortunately, we have flag: FLAG_NOT_FOCUSABLE for Windows, so after creating the dialog, we get its Window and set it to:
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
Copy the code
In this way, the input method is expected to bounce up ~!