appcompat-v7 v23.0.0 处于 ActionMode 时状态栏颜色为黑色
appcompat-v7 v23.0.0 statusbar color black when in ActionMode
更新
最新的 Gmail 应用程序中存在同样的问题。我仍然不明白为什么 Google 会做出如此令人不快的 UI 改变。强迫症一看到就发疯
问题
我在 appcompat-v7 23 上遇到了这个奇怪的问题。我要描述的问题在 22 系列中没有发生
您可以获得重现此问题的源代码
https://github.com/devserv/t/
建成后,您可以点击并按住列表中的项目以激活 ActionMode
问题:
在 ActionMode 下,appcompat 将状态栏变为黑色。如果我不使用 following
就不会发生这种情况
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
在我的 v21 风格中,但我必须使用它,因为我希望我的导航抽屉在状态栏后面查看。
我曾经使用下面的方法来避免在 ActionMode 开始和结束时出现黑色状态栏
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.appColorPrimaryDark));
}
}
public void onDestroyActionMode(ActionMode actionMode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getActivity().getWindow().setStatusBarColor(getResources().getColor(android.R.color.transparent));
}
mMode = null;
}
以上代码没有create/avoided状态栏变黑,但在appcompat v23上不能正常工作。相反,当 ActionMode 被销毁时,您会看到一个黑色的短状态栏。好像和ActionMode销毁时播放的动画有关
我曾尝试打开错误报告,但已被拒绝并发表评论
Don't re-create bugs.
我是不是漏掉了什么?
这是普通和动作模式的屏幕截图。
没人吗?这是我想出的解决方法。延迟。
@Override
public void onDestroyActionMode(ActionMode mode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
try {
getActivity().getWindow().setStatusBarColor(getResources().getColor(android.R.color.transparent));
}
catch(Exception e)
{
e.printStackTrace();
}
}
}, 400);
}
mActionMode = null;
}
v7 appcompat library
的 23.0.0 版引入了一个动画,可以在动作模式开始和结束时淡入淡出,您可以阅读 here:
The action mode has fades in and is working as intended.
在AppCompatDelegateImplV7
中的方法onDestroyActionMode
中进行了修改:
public void onDestroyActionMode(ActionMode mode) {
mWrapped.onDestroyActionMode(mode);
if (mActionModePopup != null) {
mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
mActionModePopup.dismiss();
} else if (mActionModeView != null) {
mActionModeView.setVisibility(View.GONE);
if (mActionModeView.getParent() != null) {
ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
}
}
if (mActionModeView != null) {
mActionModeView.removeAllViews();
}
if (mAppCompatCallback != null) {
mAppCompatCallback.onSupportActionModeFinished(mActionMode);
}
mActionMode = null;
}
在版本 23.0.0 中更改为:
public void onDestroyActionMode(ActionMode mode) {
mWrapped.onDestroyActionMode(mode);
if (mActionModePopup != null) {
mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
}
if (mActionModeView != null) {
endOnGoingFadeAnimation();
mFadeAnim = ViewCompat.animate(mActionModeView).alpha(0f);
mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
@Override
public void onAnimationEnd(View view) {
mActionModeView.setVisibility(View.GONE);
if (mActionModePopup != null) {
mActionModePopup.dismiss();
} else if (mActionModeView.getParent() instanceof View) {
ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
}
mActionModeView.removeAllViews();
mFadeAnim.setListener(null);
mFadeAnim = null;
}
});
}
if (mAppCompatCallback != null) {
mAppCompatCallback.onSupportActionModeFinished(mActionMode);
}
mActionMode = null;
}
如您所见,mWrapped.onDestroyActionMode(mode);
被立即调用,而不是在动画结束时调用。这就是导致您的应用程序以及 Gmail 和 Keep 等其他应用程序出现黑色状态栏的原因。
您找到的解决方法有效,但不幸的是并不可靠,因为如果动画时间更长,您仍然会看到黑色状态栏。
我认为 Google 应该更正这个问题,只有在动画真正结束时才调用 onDestroyActionMode
。同时,您可以通过一些反思来改变这种行为。有必要在 activity 中覆盖 onSupportActionModeStarted
并调用方法 fixActionModeCallback
:
@Override
public void onSupportActionModeStarted(ActionMode mode) {
super.onSupportActionModeStarted(mode);
//Call this method
fixActionModeCallback(this, mode);
}
private void fixActionModeCallback(AppCompatActivity activity, ActionMode mode) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
return;
if (!(mode instanceof StandaloneActionMode))
return;
try {
final Field mCallbackField = mode.getClass().getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
final Object mCallback = mCallbackField.get(mode);
final Field mWrappedField = mCallback.getClass().getDeclaredField("mWrapped");
mWrappedField.setAccessible(true);
final ActionMode.Callback mWrapped = (ActionMode.Callback) mWrappedField.get(mCallback);
final Field mDelegateField = AppCompatActivity.class.getDeclaredField("mDelegate");
mDelegateField.setAccessible(true);
final Object mDelegate = mDelegateField.get(activity);
mCallbackField.set(mode, new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(android.support.v7.view.ActionMode mode, Menu menu) {
return mWrapped.onCreateActionMode(mode, menu);
}
@Override
public boolean onPrepareActionMode(android.support.v7.view.ActionMode mode, Menu menu) {
return mWrapped.onPrepareActionMode(mode, menu);
}
@Override
public boolean onActionItemClicked(android.support.v7.view.ActionMode mode, MenuItem item) {
return mWrapped.onActionItemClicked(mode, item);
}
@Override
public void onDestroyActionMode(final android.support.v7.view.ActionMode mode) {
Class mDelegateClass = mDelegate.getClass().getSuperclass();
Window mWindow = null;
PopupWindow mActionModePopup = null;
Runnable mShowActionModePopup = null;
ActionBarContextView mActionModeView = null;
AppCompatCallback mAppCompatCallback = null;
ViewPropertyAnimatorCompat mFadeAnim = null;
android.support.v7.view.ActionMode mActionMode = null;
Field mFadeAnimField = null;
Field mActionModeField = null;
while (mDelegateClass != null) {
try {
if (TextUtils.equals("AppCompatDelegateImplV7", mDelegateClass.getSimpleName())) {
Field mActionModePopupField = mDelegateClass.getDeclaredField("mActionModePopup");
mActionModePopupField.setAccessible(true);
mActionModePopup = (PopupWindow) mActionModePopupField.get(mDelegate);
Field mShowActionModePopupField = mDelegateClass.getDeclaredField("mShowActionModePopup");
mShowActionModePopupField.setAccessible(true);
mShowActionModePopup = (Runnable) mShowActionModePopupField.get(mDelegate);
Field mActionModeViewField = mDelegateClass.getDeclaredField("mActionModeView");
mActionModeViewField.setAccessible(true);
mActionModeView = (ActionBarContextView) mActionModeViewField.get(mDelegate);
mFadeAnimField = mDelegateClass.getDeclaredField("mFadeAnim");
mFadeAnimField.setAccessible(true);
mFadeAnim = (ViewPropertyAnimatorCompat) mFadeAnimField.get(mDelegate);
mActionModeField = mDelegateClass.getDeclaredField("mActionMode");
mActionModeField.setAccessible(true);
mActionMode = (android.support.v7.view.ActionMode) mActionModeField.get(mDelegate);
} else if (TextUtils.equals("AppCompatDelegateImplBase", mDelegateClass.getSimpleName())) {
Field mAppCompatCallbackField = mDelegateClass.getDeclaredField("mAppCompatCallback");
mAppCompatCallbackField.setAccessible(true);
mAppCompatCallback = (AppCompatCallback) mAppCompatCallbackField.get(mDelegate);
Field mWindowField = mDelegateClass.getDeclaredField("mWindow");
mWindowField.setAccessible(true);
mWindow = (Window) mWindowField.get(mDelegate);
}
mDelegateClass = mDelegateClass.getSuperclass();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
if (mActionModePopup != null) {
mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
}
if (mActionModeView != null) {
if (mFadeAnim != null) {
mFadeAnim.cancel();
}
mFadeAnim = ViewCompat.animate(mActionModeView).alpha(0.0F);
final PopupWindow mActionModePopupFinal = mActionModePopup;
final ActionBarContextView mActionModeViewFinal = mActionModeView;
final ViewPropertyAnimatorCompat mFadeAnimFinal = mFadeAnim;
final AppCompatCallback mAppCompatCallbackFinal = mAppCompatCallback;
final android.support.v7.view.ActionMode mActionModeFinal = mActionMode;
final Field mFadeAnimFieldFinal = mFadeAnimField;
final Field mActionModeFieldFinal = mActionModeField;
mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
public void onAnimationEnd(View view) {
mActionModeViewFinal.setVisibility(View.GONE);
if (mActionModePopupFinal != null) {
mActionModePopupFinal.dismiss();
} else if (mActionModeViewFinal.getParent() instanceof View) {
ViewCompat.requestApplyInsets((View) mActionModeViewFinal.getParent());
}
mActionModeViewFinal.removeAllViews();
mFadeAnimFinal.setListener((ViewPropertyAnimatorListener) null);
try {
if (mFadeAnimFieldFinal != null) {
mFadeAnimFieldFinal.set(mDelegate, null);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
mWrapped.onDestroyActionMode(mode);
if (mAppCompatCallbackFinal != null) {
mAppCompatCallbackFinal.onSupportActionModeFinished(mActionModeFinal);
}
try {
if (mActionModeFieldFinal != null) {
mActionModeFieldFinal.set(mDelegate, null);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
});
}
}
});
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
如果只是颜色问题,您可以更改它。仅限固定颜色资源。
<color name="abc_input_method_navigation_guard" tools:override="true">@color/primary_dark</color>
很明显 ?colorPrimaryDark
不会工作,甚至在 API 21 上也不会。
负责黑色状态栏背景的视图存储在AppCompatDelegateImplV7.mStatusGuard
中。您可以通过从 activity 调用 getDelegate()
并通过反射访问 mStatusGuard
字段来获取委托。启动操作模式后,您可以获得对此视图的引用并根据需要对其进行自定义。
这是在 AppCompat 24.1.1 中找到的。
更新
最新的 Gmail 应用程序中存在同样的问题。我仍然不明白为什么 Google 会做出如此令人不快的 UI 改变。强迫症一看到就发疯
问题
我在 appcompat-v7 23 上遇到了这个奇怪的问题。我要描述的问题在 22 系列中没有发生
您可以获得重现此问题的源代码 https://github.com/devserv/t/ 建成后,您可以点击并按住列表中的项目以激活 ActionMode
问题:
在 ActionMode 下,appcompat 将状态栏变为黑色。如果我不使用 following
就不会发生这种情况<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
在我的 v21 风格中,但我必须使用它,因为我希望我的导航抽屉在状态栏后面查看。
我曾经使用下面的方法来避免在 ActionMode 开始和结束时出现黑色状态栏
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.appColorPrimaryDark));
}
}
public void onDestroyActionMode(ActionMode actionMode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getActivity().getWindow().setStatusBarColor(getResources().getColor(android.R.color.transparent));
}
mMode = null;
}
以上代码没有create/avoided状态栏变黑,但在appcompat v23上不能正常工作。相反,当 ActionMode 被销毁时,您会看到一个黑色的短状态栏。好像和ActionMode销毁时播放的动画有关
我曾尝试打开错误报告,但已被拒绝并发表评论
Don't re-create bugs.
我是不是漏掉了什么?
这是普通和动作模式的屏幕截图。
没人吗?这是我想出的解决方法。延迟。
@Override
public void onDestroyActionMode(ActionMode mode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
try {
getActivity().getWindow().setStatusBarColor(getResources().getColor(android.R.color.transparent));
}
catch(Exception e)
{
e.printStackTrace();
}
}
}, 400);
}
mActionMode = null;
}
v7 appcompat library
的 23.0.0 版引入了一个动画,可以在动作模式开始和结束时淡入淡出,您可以阅读 here:
The action mode has fades in and is working as intended.
在AppCompatDelegateImplV7
中的方法onDestroyActionMode
中进行了修改:
public void onDestroyActionMode(ActionMode mode) {
mWrapped.onDestroyActionMode(mode);
if (mActionModePopup != null) {
mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
mActionModePopup.dismiss();
} else if (mActionModeView != null) {
mActionModeView.setVisibility(View.GONE);
if (mActionModeView.getParent() != null) {
ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
}
}
if (mActionModeView != null) {
mActionModeView.removeAllViews();
}
if (mAppCompatCallback != null) {
mAppCompatCallback.onSupportActionModeFinished(mActionMode);
}
mActionMode = null;
}
在版本 23.0.0 中更改为:
public void onDestroyActionMode(ActionMode mode) {
mWrapped.onDestroyActionMode(mode);
if (mActionModePopup != null) {
mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
}
if (mActionModeView != null) {
endOnGoingFadeAnimation();
mFadeAnim = ViewCompat.animate(mActionModeView).alpha(0f);
mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
@Override
public void onAnimationEnd(View view) {
mActionModeView.setVisibility(View.GONE);
if (mActionModePopup != null) {
mActionModePopup.dismiss();
} else if (mActionModeView.getParent() instanceof View) {
ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
}
mActionModeView.removeAllViews();
mFadeAnim.setListener(null);
mFadeAnim = null;
}
});
}
if (mAppCompatCallback != null) {
mAppCompatCallback.onSupportActionModeFinished(mActionMode);
}
mActionMode = null;
}
如您所见,mWrapped.onDestroyActionMode(mode);
被立即调用,而不是在动画结束时调用。这就是导致您的应用程序以及 Gmail 和 Keep 等其他应用程序出现黑色状态栏的原因。
您找到的解决方法有效,但不幸的是并不可靠,因为如果动画时间更长,您仍然会看到黑色状态栏。
我认为 Google 应该更正这个问题,只有在动画真正结束时才调用 onDestroyActionMode
。同时,您可以通过一些反思来改变这种行为。有必要在 activity 中覆盖 onSupportActionModeStarted
并调用方法 fixActionModeCallback
:
@Override
public void onSupportActionModeStarted(ActionMode mode) {
super.onSupportActionModeStarted(mode);
//Call this method
fixActionModeCallback(this, mode);
}
private void fixActionModeCallback(AppCompatActivity activity, ActionMode mode) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
return;
if (!(mode instanceof StandaloneActionMode))
return;
try {
final Field mCallbackField = mode.getClass().getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
final Object mCallback = mCallbackField.get(mode);
final Field mWrappedField = mCallback.getClass().getDeclaredField("mWrapped");
mWrappedField.setAccessible(true);
final ActionMode.Callback mWrapped = (ActionMode.Callback) mWrappedField.get(mCallback);
final Field mDelegateField = AppCompatActivity.class.getDeclaredField("mDelegate");
mDelegateField.setAccessible(true);
final Object mDelegate = mDelegateField.get(activity);
mCallbackField.set(mode, new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(android.support.v7.view.ActionMode mode, Menu menu) {
return mWrapped.onCreateActionMode(mode, menu);
}
@Override
public boolean onPrepareActionMode(android.support.v7.view.ActionMode mode, Menu menu) {
return mWrapped.onPrepareActionMode(mode, menu);
}
@Override
public boolean onActionItemClicked(android.support.v7.view.ActionMode mode, MenuItem item) {
return mWrapped.onActionItemClicked(mode, item);
}
@Override
public void onDestroyActionMode(final android.support.v7.view.ActionMode mode) {
Class mDelegateClass = mDelegate.getClass().getSuperclass();
Window mWindow = null;
PopupWindow mActionModePopup = null;
Runnable mShowActionModePopup = null;
ActionBarContextView mActionModeView = null;
AppCompatCallback mAppCompatCallback = null;
ViewPropertyAnimatorCompat mFadeAnim = null;
android.support.v7.view.ActionMode mActionMode = null;
Field mFadeAnimField = null;
Field mActionModeField = null;
while (mDelegateClass != null) {
try {
if (TextUtils.equals("AppCompatDelegateImplV7", mDelegateClass.getSimpleName())) {
Field mActionModePopupField = mDelegateClass.getDeclaredField("mActionModePopup");
mActionModePopupField.setAccessible(true);
mActionModePopup = (PopupWindow) mActionModePopupField.get(mDelegate);
Field mShowActionModePopupField = mDelegateClass.getDeclaredField("mShowActionModePopup");
mShowActionModePopupField.setAccessible(true);
mShowActionModePopup = (Runnable) mShowActionModePopupField.get(mDelegate);
Field mActionModeViewField = mDelegateClass.getDeclaredField("mActionModeView");
mActionModeViewField.setAccessible(true);
mActionModeView = (ActionBarContextView) mActionModeViewField.get(mDelegate);
mFadeAnimField = mDelegateClass.getDeclaredField("mFadeAnim");
mFadeAnimField.setAccessible(true);
mFadeAnim = (ViewPropertyAnimatorCompat) mFadeAnimField.get(mDelegate);
mActionModeField = mDelegateClass.getDeclaredField("mActionMode");
mActionModeField.setAccessible(true);
mActionMode = (android.support.v7.view.ActionMode) mActionModeField.get(mDelegate);
} else if (TextUtils.equals("AppCompatDelegateImplBase", mDelegateClass.getSimpleName())) {
Field mAppCompatCallbackField = mDelegateClass.getDeclaredField("mAppCompatCallback");
mAppCompatCallbackField.setAccessible(true);
mAppCompatCallback = (AppCompatCallback) mAppCompatCallbackField.get(mDelegate);
Field mWindowField = mDelegateClass.getDeclaredField("mWindow");
mWindowField.setAccessible(true);
mWindow = (Window) mWindowField.get(mDelegate);
}
mDelegateClass = mDelegateClass.getSuperclass();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
if (mActionModePopup != null) {
mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
}
if (mActionModeView != null) {
if (mFadeAnim != null) {
mFadeAnim.cancel();
}
mFadeAnim = ViewCompat.animate(mActionModeView).alpha(0.0F);
final PopupWindow mActionModePopupFinal = mActionModePopup;
final ActionBarContextView mActionModeViewFinal = mActionModeView;
final ViewPropertyAnimatorCompat mFadeAnimFinal = mFadeAnim;
final AppCompatCallback mAppCompatCallbackFinal = mAppCompatCallback;
final android.support.v7.view.ActionMode mActionModeFinal = mActionMode;
final Field mFadeAnimFieldFinal = mFadeAnimField;
final Field mActionModeFieldFinal = mActionModeField;
mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
public void onAnimationEnd(View view) {
mActionModeViewFinal.setVisibility(View.GONE);
if (mActionModePopupFinal != null) {
mActionModePopupFinal.dismiss();
} else if (mActionModeViewFinal.getParent() instanceof View) {
ViewCompat.requestApplyInsets((View) mActionModeViewFinal.getParent());
}
mActionModeViewFinal.removeAllViews();
mFadeAnimFinal.setListener((ViewPropertyAnimatorListener) null);
try {
if (mFadeAnimFieldFinal != null) {
mFadeAnimFieldFinal.set(mDelegate, null);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
mWrapped.onDestroyActionMode(mode);
if (mAppCompatCallbackFinal != null) {
mAppCompatCallbackFinal.onSupportActionModeFinished(mActionModeFinal);
}
try {
if (mActionModeFieldFinal != null) {
mActionModeFieldFinal.set(mDelegate, null);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
});
}
}
});
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
如果只是颜色问题,您可以更改它。仅限固定颜色资源。
<color name="abc_input_method_navigation_guard" tools:override="true">@color/primary_dark</color>
很明显 ?colorPrimaryDark
不会工作,甚至在 API 21 上也不会。
负责黑色状态栏背景的视图存储在AppCompatDelegateImplV7.mStatusGuard
中。您可以通过从 activity 调用 getDelegate()
并通过反射访问 mStatusGuard
字段来获取委托。启动操作模式后,您可以获得对此视图的引用并根据需要对其进行自定义。
这是在 AppCompat 24.1.1 中找到的。