【问题标题】:appcompat-v7 v23.0.0 statusbar color black when in ActionModeappcompat-v7 v23.0.0 在 ActionMode 时状态栏颜色为黑色
【发布时间】:2015-08-31 19:29:03
【问题描述】:

更新

最新的 Gmail 应用中存在相同的问题。我仍然不明白为什么 Google 会做出如此不愉快的 UI 更改。每当我看到它时,我的强迫症就会发疯

问题

我在 appcompat-v7 23 中遇到了这个奇怪的问题。我要描述的问题不会发生在 22 系列中

您可以获得重现此问题表单的源代码 https://github.com/devserv/t/ 构建后,您可以点击并按住列表中的某个项目来激活 ActionMode

问题:

在 ActionMode 下,appcompat 将状态栏变为黑色。如果我不使用以下功能,则不会发生这种情况

<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;
}

以上代码未创建/避免状态栏变黑,但在 appcompat 的 v23 上无法正常工作。相反,当 ActionMode 被破坏时,您会看到一个简短的黑色状态栏。看起来和 ActionMode 销毁时播放的动画有关。

我曾尝试打开错误报告,但已被拒绝并发表评论

Don't re-create bugs.

我错过了什么吗?

以下是普通动作模式的截图。

【问题讨论】:

  • 你试过 23.0.1 版本吗?今天已经部署了。您的设备的 api 级别是多少?
  • 是的,还是一样。他们甚至不接受错误报告
  • 这个问题已经用com.android.support:design:28.0.0-rc01修复了。

标签: android android-actionmode android-appcompat


【解决方案1】:

如果只有颜色是问题,您可以更改它。仅限于固定颜色资源。

<color name="abc_input_method_navigation_guard" tools:override="true">@color/primary_dark</color>

显然 ?colorPrimaryDark 不起作用,即使在 API 21 上也不行。


负责黑色状态栏背景的视图存储在AppCompatDelegateImplV7.mStatusGuard中。您可以通过从您的活动中调用getDelegate() 来获取委托,并通过反射访问mStatusGuard 字段。启动动作模式后,您可以获得对该视图的引用并根据需要对其进行自定义。

这是在 AppCompat 24.1.1 中找到的。

【讨论】:

  • 在 androidx.appcompat 1.2.0 中对应的颜色名称是abc_decor_view_status_guard
【解决方案2】:

v7 appcompat library 的 23.0.0 版本引入了一个动画,它可以在动作模式开始和结束时淡入淡出,正如您可以阅读 here

动作模式已淡入并按预期工作。

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 等其他应用中出现黑色状态栏的原因。

您找到的解决方法有效,但不幸的是不可靠,因为如果动画需要更长的时间,您仍然可以看到黑色状态栏。

我认为谷歌应该更正这个问题,并且只有在动画真正结束时才调用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();
    }
}

【讨论】:

  • 我确定是动画造成的,(请参阅下面的回复)您的回复证实了这一点。我不明白的是他们坚持认为它按预期工作。显然没有。
  • 我认为他们没有清楚地理解这个问题。问题不在于动画本身(显然可以按预期工作),而是onDestroyActionMode 被调用得太早这一事实。
  • 希望@chris-bane 意识到这一点
  • 我以为我不会实现它。我已经接受了您的回答,因为它清楚地解释了造成这种情况的原因。感谢您的时间和精力
【解决方案3】:

没人吗?这是我想出的解决方法。延迟。

@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;

    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-29
    • 2014-08-31
    相关资源
    最近更新 更多