From da785c4ce2c424442ca3e1beee5434ab60cec84b Mon Sep 17 00:00:00 2001 From: cgspine Date: Wed, 12 Feb 2020 01:37:55 +0800 Subject: [PATCH] Dialog bugfix --- .../qmuiteam/qmui/util/QMUIWindowHelper.java | 23 ++++++++ .../qmui/widget/dialog/QMUIDialog.java | 11 +++- .../widget/dialog/QMUIDialogBlockBuilder.java | 7 +-- .../qmui/widget/dialog/QMUIDialogBuilder.java | 59 ++++++++----------- .../widget/dialog/QMUIDialogRootLayout.java | 59 ++++++++++++++++--- 5 files changed, 107 insertions(+), 52 deletions(-) diff --git a/qmui/src/main/java/com/qmuiteam/qmui/util/QMUIWindowHelper.java b/qmui/src/main/java/com/qmuiteam/qmui/util/QMUIWindowHelper.java index 37ab3643..0588f71a 100644 --- a/qmui/src/main/java/com/qmuiteam/qmui/util/QMUIWindowHelper.java +++ b/qmui/src/main/java/com/qmuiteam/qmui/util/QMUIWindowHelper.java @@ -74,4 +74,27 @@ public class QMUIWindowHelper { } return null; } + + @Nullable + @SuppressWarnings({"JavaReflectionMemberAccess"}) + public static Rect unSafeGetContentInsets(View view) { + try { + // Android P forbid the reflection for @hide filed, + // fortunately now it is in light greylist, just be warned. + Field field = View.class.getDeclaredField("mAttachInfo"); + field.setAccessible(true); + Object attachInfo = field.get(view); + Field visibleInsetsField = attachInfo.getClass().getDeclaredField("mContentInsets"); + visibleInsetsField.setAccessible(true); + Object visibleInsets = visibleInsetsField.get(attachInfo); + if (visibleInsets instanceof Rect) { + return (Rect) visibleInsets; + } + } catch (Throwable e) { + if (BuildConfig.DEBUG) { + e.printStackTrace(); + } + } + return null; + } } diff --git a/qmui/src/main/java/com/qmuiteam/qmui/widget/dialog/QMUIDialog.java b/qmui/src/main/java/com/qmuiteam/qmui/widget/dialog/QMUIDialog.java index 33673049..82b2ce7e 100644 --- a/qmui/src/main/java/com/qmuiteam/qmui/widget/dialog/QMUIDialog.java +++ b/qmui/src/main/java/com/qmuiteam/qmui/widget/dialog/QMUIDialog.java @@ -36,6 +36,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.ScrollView; import android.widget.TextView; import com.qmuiteam.qmui.R; @@ -52,6 +53,7 @@ import java.util.ArrayList; import java.util.BitSet; import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatEditText; import androidx.appcompat.widget.AppCompatImageView; @@ -143,7 +145,7 @@ public class QMUIDialog extends QMUIBaseDialog { @Nullable @Override - protected View onCreateContent(QMUIDialog dialog, QMUIDialogView parent, Context context) { + protected View onCreateContent(@NonNull QMUIDialog dialog, @NonNull QMUIDialogView parent, @NonNull Context context) { if (mMessage != null && mMessage.length() != 0) { QMUISpanTouchFixTextView tv = new QMUISpanTouchFixTextView(context); assignMessageTvWithAttr(tv, hasTitle(), R.attr.qmui_dialog_message_content_style); @@ -156,7 +158,7 @@ public class QMUIDialog extends QMUIBaseDialog { @Nullable @Override - protected View onCreateTitle(QMUIDialog dialog, QMUIDialogView parent, Context context) { + protected View onCreateTitle(@NonNull QMUIDialog dialog, @NonNull QMUIDialogView parent, @NonNull Context context) { View tv = super.onCreateTitle(dialog, parent, context); if (tv != null && (mMessage == null || mMessage.length() == 0)) { TypedArray a = context.obtainStyledAttributes(null, @@ -843,6 +845,8 @@ public class QMUIDialog extends QMUIBaseDialog { */ public static abstract class AutoResizeDialogBuilder extends QMUIDialogBuilder { + protected ScrollView mScrollView; + public AutoResizeDialogBuilder(Context context) { super(context); setCheckKeyboardOverlay(true); @@ -851,7 +855,8 @@ public class QMUIDialog extends QMUIBaseDialog { @Nullable @Override protected View onCreateContent(QMUIDialog dialog, QMUIDialogView parent, Context context) { - return wrapWithScroll(onBuildContent(dialog)); + mScrollView = wrapWithScroll(onBuildContent(dialog)); + return mScrollView; } public abstract View onBuildContent(QMUIDialog dialog); diff --git a/qmui/src/main/java/com/qmuiteam/qmui/widget/dialog/QMUIDialogBlockBuilder.java b/qmui/src/main/java/com/qmuiteam/qmui/widget/dialog/QMUIDialogBlockBuilder.java index 14fc459f..835ae5b1 100644 --- a/qmui/src/main/java/com/qmuiteam/qmui/widget/dialog/QMUIDialogBlockBuilder.java +++ b/qmui/src/main/java/com/qmuiteam/qmui/widget/dialog/QMUIDialogBlockBuilder.java @@ -103,12 +103,7 @@ public class QMUIDialogBlockBuilder extends QMUIDialogBuilder { protected List mActions = new ArrayList<>(); private QMUIDialogView.OnDecorationListener mOnDecorationListener; - - private int mContentAreaMaxHeight = -1; - @Orientation private int mActionContainerOrientation = HORIZONTAL; private boolean mChangeAlphaForPressOrDisable = true; private int mActionDividerThickness = 0; @@ -102,35 +100,18 @@ public abstract class QMUIDialogBuilder { private int mActionDividerInsetStart = 0; private int mActionDividerInsetEnd = 0; private boolean mCheckKeyboardOverlay = false; - private boolean mFollowSkin = true; + private QMUISkinManager mSkinManager; private float mMaxPercent = 0.75f; public QMUIDialogBuilder(Context context) { this.mContext = context; - } - - protected int getContentAreaMaxHeight() { - if (mContentAreaMaxHeight == -1) { - // 屏幕高度的0.85 - 预估的 title 和 action 高度 - return (int) (QMUIDisplayHelper.getScreenHeight(mContext) * 0.85) - QMUIDisplayHelper.dp2px(mContext, 100); - } - return mContentAreaMaxHeight; + mSkinManager = QMUISkinManager.defaultInstance(context); } public Context getBaseContext() { return mContext; } - /** - * 设置内容区域最高的高度 - * - * @param contentAreaMaxHeight - */ - public T setContentAreaMaxHeight(int contentAreaMaxHeight) { - mContentAreaMaxHeight = contentAreaMaxHeight; - return (T) this; - } - /** * 设置对话框顶部的标题文字 */ @@ -195,8 +176,8 @@ public abstract class QMUIDialogBuilder { } @SuppressWarnings("unchecked") - public T setFollowSkin(boolean followSkin) { - mFollowSkin = followSkin; + public T setSkinManager(@Nullable QMUISkinManager skinManager) { + mSkinManager = skinManager; return (T) this; } @@ -344,6 +325,12 @@ public abstract class QMUIDialogBuilder { mDialogView = onCreateDialogView(dialogContext); mRootView = new QMUIDialogRootLayout(dialogContext, mDialogView, onCreateDialogLayoutParams()); mRootView.setCheckKeyboardOverlay(mCheckKeyboardOverlay); + mRootView.setOverlayOccurInMeasureCallback(new QMUIDialogRootLayout.OverlayOccurInMeasureCallback() { + @Override + public void call() { + onOverlayOccurredInMeasure(); + } + }); mRootView.setMaxPercent(mMaxPercent); configRootLayout(mRootView); mDialogView = mRootView.getDialogView(); @@ -401,12 +388,16 @@ public abstract class QMUIDialogBuilder { ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); mDialog.setCancelable(mCancelable); mDialog.setCanceledOnTouchOutside(mCanceledOnTouchOutside); - mDialog.setFollowSkin(mFollowSkin); + mDialog.setSkinManager(mSkinManager); onAfterCreate(mDialog, mRootView, dialogContext); return mDialog; } - protected void onAfterCreate(QMUIDialog dialog, QMUIDialogRootLayout rootLayout, Context context){ + protected void onAfterCreate(@NonNull QMUIDialog dialog, @NonNull QMUIDialogRootLayout rootLayout, @NonNull Context context){ + + } + + protected void onOverlayOccurredInMeasure(){ } @@ -416,12 +407,12 @@ public abstract class QMUIDialogBuilder { } } - protected void configRootLayout(QMUIDialogRootLayout rootLayout){ + protected void configRootLayout(@NonNull QMUIDialogRootLayout rootLayout){ } @NonNull - protected QMUIDialogView onCreateDialogView(Context context){ + protected QMUIDialogView onCreateDialogView(@NonNull Context context){ QMUIDialogView dialogView = new QMUIDialogView(context); dialogView.setBackground(QMUIResHelper.getAttrDrawable(context, R.attr.qmui_skin_support_dialog_bg)); dialogView.setRadius(QMUIResHelper.getAttrDimen(context, R.attr.qmui_dialog_radius)); @@ -439,7 +430,7 @@ public abstract class QMUIDialogBuilder { } @Nullable - protected View onCreateTitle(QMUIDialog dialog, QMUIDialogView parent, Context context) { + protected View onCreateTitle(@NonNull QMUIDialog dialog, @NonNull QMUIDialogView parent, @NonNull Context context) { if (hasTitle()) { TextView tv = new QMUISpanTouchFixTextView(context); tv.setId(R.id.qmui_dialog_title_id); @@ -456,7 +447,7 @@ public abstract class QMUIDialogBuilder { } @NonNull - protected ConstraintLayout.LayoutParams onCreateTitleLayoutParams(Context context) { + protected ConstraintLayout.LayoutParams onCreateTitleLayoutParams(@NonNull Context context) { ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT); lp.leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID; lp.rightToRight = ConstraintLayout.LayoutParams.PARENT_ID; @@ -467,7 +458,7 @@ public abstract class QMUIDialogBuilder { @Nullable - protected abstract View onCreateContent(QMUIDialog dialog, QMUIDialogView parent, Context context); + protected abstract View onCreateContent(@NonNull QMUIDialog dialog, @NonNull QMUIDialogView parent, @NonNull Context context); protected QMUIWrapContentScrollView wrapWithScroll(@NonNull View view){ @@ -477,7 +468,7 @@ public abstract class QMUIDialogBuilder { return scrollView; } - protected ConstraintLayout.LayoutParams onCreateContentLayoutParams(Context context) { + protected ConstraintLayout.LayoutParams onCreateContentLayoutParams(@NonNull Context context) { ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT); lp.leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID; lp.rightToRight = ConstraintLayout.LayoutParams.PARENT_ID; @@ -487,7 +478,7 @@ public abstract class QMUIDialogBuilder { @Nullable - protected View onCreateOperatorLayout(final QMUIDialog dialog, QMUIDialogView parent, Context context) { + protected View onCreateOperatorLayout(@NonNull final QMUIDialog dialog, @NonNull QMUIDialogView parent, @NonNull Context context) { int size = mActions.size(); if (size > 0) { TypedArray a = context.obtainStyledAttributes(null, R.styleable.QMUIDialogActionContainerCustomDef, R.attr.qmui_dialog_action_container_style, 0); @@ -593,7 +584,7 @@ public abstract class QMUIDialogBuilder { } @NonNull - protected ConstraintLayout.LayoutParams onCreateOperatorLayoutLayoutParams(Context context) { + protected ConstraintLayout.LayoutParams onCreateOperatorLayoutLayoutParams(@NonNull Context context) { ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT); lp.leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID; lp.rightToRight = ConstraintLayout.LayoutParams.PARENT_ID; diff --git a/qmui/src/main/java/com/qmuiteam/qmui/widget/dialog/QMUIDialogRootLayout.java b/qmui/src/main/java/com/qmuiteam/qmui/widget/dialog/QMUIDialogRootLayout.java index db72ce77..094d749b 100644 --- a/qmui/src/main/java/com/qmuiteam/qmui/widget/dialog/QMUIDialogRootLayout.java +++ b/qmui/src/main/java/com/qmuiteam/qmui/widget/dialog/QMUIDialogRootLayout.java @@ -18,17 +18,18 @@ package com.qmuiteam.qmui.widget.dialog; import android.content.Context; import android.graphics.Rect; +import android.view.MotionEvent; import android.view.ViewGroup; import android.widget.FrameLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.qmuiteam.qmui.R; import com.qmuiteam.qmui.util.QMUIDisplayHelper; import com.qmuiteam.qmui.util.QMUIResHelper; import com.qmuiteam.qmui.util.QMUIWindowHelper; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - public class QMUIDialogRootLayout extends ViewGroup { private QMUIDialogView mDialogView; @@ -39,6 +40,9 @@ public class QMUIDialogRootLayout extends ViewGroup { private int mInsetVer; private boolean mCheckKeyboardOverlay = false; private float mMaxPercent = 0.75f; + private boolean isOverlayOccurEventNotified = false; + private OverlayOccurInMeasureCallback mOverlayOccurInMeasureCallback; + private int mLastContentInsetTop = 0; public QMUIDialogRootLayout(@NonNull Context context, @NonNull QMUIDialogView dialogView, @@ -73,6 +77,10 @@ public class QMUIDialogRootLayout extends ViewGroup { mInsetVer = insetVer; } + public void setOverlayOccurInMeasureCallback(OverlayOccurInMeasureCallback overlayOccurInMeasureCallback) { + mOverlayOccurInMeasureCallback = overlayOccurInMeasureCallback; + } + public void setCheckKeyboardOverlay(boolean checkKeyboardOverlay) { mCheckKeyboardOverlay = checkKeyboardOverlay; } @@ -84,11 +92,17 @@ public class QMUIDialogRootLayout extends ViewGroup { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int keyboardOverlayHeight = 0; + int contentInsetVer = 0; if (mCheckKeyboardOverlay) { Rect visibleInsetRect = QMUIWindowHelper.unSafeGetWindowVisibleInsets(this); + Rect contentInsetRect = QMUIWindowHelper.unSafeGetContentInsets(this); if (visibleInsetRect != null) { keyboardOverlayHeight = visibleInsetRect.bottom; } + if (contentInsetRect != null) { + mLastContentInsetTop = contentInsetRect.top; + contentInsetVer = contentInsetRect.top + contentInsetRect.bottom; + } } int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); @@ -111,13 +125,24 @@ public class QMUIDialogRootLayout extends ViewGroup { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(mDialogViewLp.height, MeasureSpec.EXACTLY); } else { int childMaxHeight; - if(keyboardOverlayHeight > 0){ - childMaxHeight = Math.max(heightSize - 2 * mInsetVer - keyboardOverlayHeight, 0); - }else{ + if (keyboardOverlayHeight > 0) { + if (getRootView() != null && getRootView().getHeight() > 0) { + // the overlay occurred with this height, we can't change it. + heightSize = getRootView().getHeight(); + if (!isOverlayOccurEventNotified) { + isOverlayOccurEventNotified = true; + if (mOverlayOccurInMeasureCallback != null) { + mOverlayOccurInMeasureCallback.call(); + } + } + } + childMaxHeight = Math.max(heightSize - 2 * mInsetVer - keyboardOverlayHeight - contentInsetVer, 0); + } else { // use maxPercent to keep dialog from being too high and calculated based on // screen height because height size while change to actual height when multi onMeasure. - childMaxHeight = Math.min(heightSize - 2 * mInsetVer, - (int)(QMUIDisplayHelper.getScreenHeight(getContext()) * mMaxPercent - 2 * mInsetVer)); + isOverlayOccurEventNotified = false; + childMaxHeight = Math.min(heightSize - 2 * mInsetVer - contentInsetVer, + (int) (QMUIDisplayHelper.getScreenHeight(getContext()) * mMaxPercent - 2 * mInsetVer)); } if (mDialogViewLp.height == LayoutParams.MATCH_PARENT) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childMaxHeight, MeasureSpec.EXACTLY); @@ -132,7 +157,7 @@ public class QMUIDialogRootLayout extends ViewGroup { } // InsetVer works when keyboard overlay occurs setMeasuredDimension(mDialogView.getMeasuredWidth(), - mDialogView.getMeasuredHeight() + 2 * mInsetVer + keyboardOverlayHeight); + mDialogView.getMeasuredHeight() + 2 * mInsetVer + keyboardOverlayHeight + contentInsetVer); } @Override @@ -147,4 +172,20 @@ public class QMUIDialogRootLayout extends ViewGroup { public QMUIDialogView getDialogView() { return mDialogView; } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + // I think this is a android system bug: + // When show keyboard In fullscreen and the content overlaps with keyboard, + // then the mAttachInfo.mContentInset.top equals notch's height + // but the event's y and draw position has different behavior if notch exist. + if (mLastContentInsetTop > 0) { + ev.offsetLocation(0, -mLastContentInsetTop); + } + return super.dispatchTouchEvent(ev); + } + + interface OverlayOccurInMeasureCallback { + void call(); + } }