appcompat-v7 v23.0.0 лентата на състоянието е черна, когато е в ActionMode

АКТУАЛИЗАЦИЯ

Същият проблем присъства в най-новото приложение Gmail. Все още не разбирам защо Google прави такава неприятна промяна на потребителския интерфейс. Обсесивното в мен полудява, когато го видя

ВЪПРОС

Имам този странен проблем с appcompat-v7 23. Проблемът, който ще опиша, не се случва с 22 серия

Можете да получите изходен код, който възпроизвежда този проблем чрез формуляр https://github.com/devserv/t/ Веднъж изграден, можете да докоснете и задържите елемент в списъка, за да активирате ActionMode

Проблем:

Когато е в ActionMode, appcompat превръща лентата на състоянието в черна. Това не се случва, ако не използвам follow

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

Горният код не създаде/избегна почерняване на лентата на състоянието, но не работи правилно на v23 на appcompat. Вместо това виждате кратка черна лента на състоянието, докато ActionMode е унищожен. Изглежда, че е свързано с анимацията, която се възпроизвежда при унищожаване на ActionMode.

Опитах се да отворя доклади за грешки, но беше отказано с коментар

Don't re-create bugs.

Изпускам ли нещо?

Ето екранните снимки за нормален и режим на действие.

въведете описание на изображението тук въведете описание на изображението тук


person nLL    schedule 31.08.2015    source източник
comment
Опитахте ли с версия 23.0.1? Той е разгърнат днес. Какво е API нивото на вашето устройство?   -  person Mimmo Grottoli    schedule 04.09.2015
comment
Да, все същото. Те дори не приемат доклада за грешка   -  person nLL    schedule 04.09.2015
comment
Този проблем е решен с Това е решен с com.android.support:design:28.0.0-rc01.   -  person Soren Stoutner    schedule 16.08.2018


Отговори (3)


В случай, че проблемът е само в цвета, можете да го промените. Само до фиксиран цветен ресурс.

<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.

person Eugen Pechanec    schedule 01.08.2016
comment
В androidx.appcompat 1.2.0 името на съответния цвят е abc_decor_view_status_guard. - person aax; 13.08.2020

Версия 23.0.0 на v7 appcompat library въведе анимация, която изчезва и изчезва от режима на действие, когато е стартиран и завършен, както можете да прочетете тук:

Режимът на действие се включва и работи по предназначение.

Промените се правят в метода onDestroyActionMode в AppCompatDelegateImplV7:

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 само когато анимацията наистина приключи. Междувременно можете да промените това поведение с малко размисли. Необходимо е да замените 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();
    }
}
person Mattia Maestrini    schedule 06.09.2015
comment
Бях сигурен, че анимацията го причинява (вижте отговорите ми по-долу), вашият отговор потвърждава това. Това, което не разбирам, е какво настояват, че работи по предназначение. Очевидно не е така. - person nLL; 06.09.2015
comment
Мисля, че не са разбрали ясно проблема. Проблемът не е самата анимация (която очевидно работи по предназначение), а фактът, че onDestroyActionMode се извиква твърде рано. - person Mattia Maestrini; 06.09.2015
comment
Надяваме се, че @chris-bane е наясно с това - person nLL; 06.09.2015
comment
Мислех, че няма да го прилагам. Приех вашия отговор, тъй като той ясно обяснява какво причинява това. Благодаря за отделеното време и усилия - person nLL; 06.09.2015

Никой? Ето заобиколното решение, което измислих. Закъснение.

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

    }
person nLL    schedule 01.09.2015