Ошибка расширения двоичного XML в PopupMenu.show() Android

Я пытаюсь раздуть простое PopupMenu для параметров переименования/удаления, когда элемент RecylerView является longClicked. По какой-то причине я получаю сообщение об ошибке наполнения XML, когда вызываю mPopup.show() после загрузки моего XML-файла в средство надувания.

Я использую аналогичную логику в другом месте своего приложения, чтобы создать PopupMenu, и она отлично работает. Я даже пытался загрузить работающее PopupMenu из несвязанной части приложения в этот инфлятор и вижу ту же ошибку android.view.InflateException: Binary XML file line #17: Failed to resolve attribute at index 1 в logcat, так что, может быть, проблема не в файле XML?

Как я могу заставить это PopupMenu раздуваться и показывать себя?

Фатальное исключение Logcat

05-31 23:02:27.421 19597-20019/? E/AndroidRuntime: FATAL EXCEPTION: main
                                               Process: com.example.foo, PID: 19597
                                               android.view.InflateException: Binary XML file line #17: Failed to resolve attribute at index 1: TypedValue{t=0x2/d=0x7f01005d a=-1}
                                               Caused by: java.lang.UnsupportedOperationException: Failed to resolve attribute at index 1: TypedValue{t=0x2/d=0x7f01005d a=-1}
                                                   at android.content.res.TypedArray.getLayoutDimension(TypedArray.java:761)
                                                   at android.view.ViewGroup$LayoutParams.setBaseAttributes(ViewGroup.java:7060)
                                                   at android.view.ViewGroup$MarginLayoutParams.<init>(ViewGroup.java:7241)
                                                   at android.widget.FrameLayout$LayoutParams.<init>(FrameLayout.java:438)
                                                   at android.widget.FrameLayout.generateLayoutParams(FrameLayout.java:370)
                                                   at android.widget.FrameLayout.generateLayoutParams(FrameLayout.java:369)
                                                   at android.view.LayoutInflater.inflate(LayoutInflater.java:505)
                                                   at android.view.LayoutInflater.inflate(LayoutInflater.java:426)
                                                   at android.support.v7.view.menu.MenuAdapter.getView(MenuAdapter.java:93)
                                                   at android.support.v7.view.menu.MenuPopup.measureIndividualMenuWidth(MenuPopup.java:160)
                                                   at android.support.v7.view.menu.StandardMenuPopup.tryShow(StandardMenuPopup.java:153)
                                                   at android.support.v7.view.menu.StandardMenuPopup.show(StandardMenuPopup.java:187)
                                                   at android.support.v7.view.menu.MenuPopupHelper.showPopup(MenuPopupHelper.java:290)
                                                   at android.support.v7.view.menu.MenuPopupHelper.tryShow(MenuPopupHelper.java:175)
                                                   at android.support.v7.view.menu.MenuPopupHelper.show(MenuPopupHelper.java:141)
                                                   at android.support.v7.widget.PopupMenu.show(PopupMenu.java:233)
                                                   at com.example.foo.FragmentChordMenu.showChordOptionsMenu(FragmentChordMenu.java:132)
                                                   at com.example.foo.CustomChordAdapter$ChordViewHolder$2.onLongClick(CustomChordAdapter.java:138)
                                                   at android.view.View.performLongClickInternal(View.java:5687)
                                                   at android.view.View.performLongClick(View.java:5645)
                                                   at android.view.View.performLongClick(View.java:5663)
                                                   at android.view.View$CheckForLongPress.run(View.java:22234)
                                                   at android.os.Handler.handleCallback(Handler.java:751)
                                                   at android.os.Handler.dispatchMessage(Handler.java:95)
                                                   at android.os.Looper.loop(Looper.java:154)
                                                   at android.app.ActivityThread.main(ActivityThread.java:6077)
                                                   at java.lang.reflect.Method.invoke(Native Method)
                                                   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)
                                                   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)

Фрагментальная активность

public class FragmentChordMenu extends Fragment implements CustomChordAdapter.onItemClickListener {    
    private static RecyclerView mCustomChordList;
    private static CustomChordAdapter mRecyclerViewAdapter;
    private static Context mContext;

    private FloatingActionButton mFAB;
    private View mPopupView;
    private PopupWindow mCustomChordMenu;

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mRecyclerViewAdapter = new CustomChordAdapter(this);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        mContext = getActivity().getApplicationContext();   //stores application context for later use in fragment without risk
                                                            //of detachment

        View v = inflater.inflate(R.layout.menu_fragment_chord, container, false);
        LayoutInflater layoutInflater = (LayoutInflater)getActivity().getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        ...

        mFAB = (FloatingActionButton) v.findViewById(R.id.addChord);
        mFAB.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                mCustomChordMenu.showAtLocation(mPopupView, Gravity.CENTER, 10, 10);
                mCustomChordList = (RecyclerView) mPopupView.findViewById(R.id.rv_userChords);
                LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
                mCustomChordList.setLayoutManager(layoutManager);
                mCustomChordList.setAdapter(mRecyclerViewAdapter);
            }
        });

        return v;
    }

    public static void showChordOptionsMenu(final int position){
        View anchorView = mCustomChordList.findViewHolderForAdapterPosition(position).itemView;
        PopupMenu mPopup = new PopupMenu(mContext, anchorView);
        mPopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                switch (item.getItemId()){
                    case R.id.delete:
                        mRecyclerViewAdapter.deleteChord(position);
                        return true;
                    case R.id.rename:
                        Log.d("FragmentChordMenu: ", "Rename clicked");
                }
                return true;
            }
        });

        MenuInflater popupInflater = mPopup.getMenuInflater();
        popupInflater.inflate(R.menu.popup_delete_chord, mPopup.getMenu());
        mPopup.show();              //ERROR HERE
    }

    ...
}

XML всплывающего меню

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content">
    <item
        android:id="@+id/rename"
        android:title="@string/rename"/>

    <item
        android:id="@+id/delete"
        android:title="@string/delete"/>
</menu>

XML фрагмента активности

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                xmlns:android.support.design="http://schemas.android.com/tools"
                android:id="@+id/chordMenu"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

    <ScrollView
        android:id="@+id/scrollview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:id="@+id/chordButtons"
                android:orientation="vertical"  >
            </LinearLayout>

            <android.support.design.widget.FloatingActionButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_margin="16dp"
                android:clickable="true"
                app:fabSize="mini"
                android:id="@+id/addChord"
                app:borderWidth="0dp"
                app:useCompatPadding="false"
                android:src="@drawable/ic_add_black_24dp"/>
        </LinearLayout>

    </ScrollView>

</RelativeLayout>

person Cody    schedule 01.06.2017    source источник
comment
mContext = getActivity().getApplicationContext(); - Могу поспорить, что это твоя проблема. Вы используете appcompat версии 7 PopupMenu, и в приложении Context не будут установлены правильные ресурсы темы. Просто используйте getActivity(). Вам действительно не нужно сохранять поле Context, кстати. Вы можете звонить getActivity() везде, где вам нужно.   -  person Mike M.    schedule 01.06.2017
comment
@МайкМ. как я могу использовать getActivity() в нестатическом контексте? showChordOptionsMenu() — это статический метод, отсюда и проблема.   -  person Cody    schedule 02.06.2017
comment
А, я не заметил, что это было. Да, вы действительно не хотите, чтобы все эти static участники были в вашем Fragment классе. Обычно вы хотите добавить параметр Context в метод, чтобы передать его, но этот метод использует другие статические члены с Contexts, поэтому здесь это спорный вопрос. Вам придется изменить способ общения с этим Fragment. Во всяком случае, текущая проблема, вероятно, неправильная Context, поэтому простое удаление вызова getApplicationContext() в коде, который у вас есть сейчас, позволит вам узнать, действительно ли это проблема.   -  person Mike M.    schedule 02.06.2017
comment
@МайкМ. Хорошее предложение, однако, теперь я получаю новую ошибку времени выполнения. android.view.WindowManager$BadTokenException: Unable to add window -- token android.view.ViewRootImpl$W@f847ac3 is not valid; is your activity running? Я думаю, что, возможно, я пытаюсь защитить неправильный контекст, я думаю, что вместо этого я должен попытаться получить контекст PopupWindow, в котором происходит эта транзакция. Знаете, как бы я это сделал? Или, точнее, каков источник проблемы?   -  person Cody    schedule 02.06.2017
comment
О, вы пытаетесь показать PopupMenu из PopupWindow? Я не думаю, что это сработает. Я не верю, что вы можете привязать всплывающее окно к другому всплывающему окну.   -  person Mike M.    schedule 02.06.2017
comment
О, о. Я задам новый вопрос об этом. Надеюсь, это выполнимо, я хочу избежать серьезной переработки кода.   -  person Cody    schedule 02.06.2017
comment
Просто используйте Dialog вместо PopupWindow.   -  person Mike M.    schedule 02.06.2017
comment
Это нежелательно по нескольким причинам, самая главная из которых заключается в том, что мне не нужны кнопки действий или методы взаимодействия с родительским представлением, которые предоставляет класс Dialog.   -  person Cody    schedule 02.06.2017
comment
Dialog не имеет никаких кнопок - или любого вида View - по умолчанию. Я думаю, вы думаете о AlertDialog. Вы можете сделать его просто плавающим RecyclerView, если хотите. Я не знаю, что вы подразумеваете под методами взаимодействия с родительским представлением.   -  person Mike M.    schedule 02.06.2017
comment
Я полагаю, что думал об этом ответе. Если вы хотите добавить больше контроля и обратной связи между своим представлением, используйте диалоговое окно. Если вы, как и я, хотите получить полный контроль над всем, я бы предложил PopupWindow, так как в нем меньше очевидных для пользователя методов по умолчанию, которые нужно переопределить. отсюда stackoverflow.com/questions /4710361/. Вы уверены, что PopupMenu работает при привязке к диалоговому окну? Если на самом деле не так много компромиссов, я буду использовать это вместо этого.   -  person Cody    schedule 02.06.2017
comment
Конечно, я делал это раньше. Конечно, есть способ убедиться в этом самостоятельно. :-) Похоже, вам придется изменить всего около трех строк кода.   -  person Mike M.    schedule 02.06.2017
comment
Спасибо. Если вы опубликуете предлагаемые изменения для ~ 3 соответствующих строк, я дам вам принятый ответ.   -  person Cody    schedule 02.06.2017
comment
На самом деле вы можете сделать это довольно просто с помощью AlertDialog; просто не устанавливайте никаких кнопок. Удалите/закомментируйте строку mCustomChordMenu.showAtLocation() и вставьте - просто для примера - new AlertDialog.Builder(getActivity()).setView(mPopupView).show();.   -  person Mike M.    schedule 02.06.2017
comment
Удивительно, работает. Спасибо. Если вы хотите опубликовать это, я могу принять это.   -  person Cody    schedule 03.06.2017
comment
Круто, рад, что сработало. Сделаю. Однако пройдет немного времени, прежде чем я получу возможность собрать ответ. Ваше здоровье!   -  person Mike M.    schedule 03.06.2017


Ответы (1)


Причиной непосредственной проблемы здесь — InflateException — было использование приложения Context для appcompat-v7 PopupMenu.

mContext = getActivity().getApplicationContext();
...
PopupMenu mPopup = new PopupMenu(mContext, anchorView);

В этом Context не будут установлены правильные ресурсы темы для виджета v7, что приведет к ошибке InflateException. Тем не менее, у Activity есть соответствующая тема, и ее использование вместо этого решает эту конкретную проблему.

mContext = getActivity();

Однако после исправления этого возникло WindowManager$BadTokenException из-за того, что PopupMenu передал якорь View из PopupWindow. Всплывающие окна должны быть привязаны к View в Window верхнего уровня, а классы Popup* в основном являются простыми View, поэтому Exception.

Простое решение для этого — заменить PopupWindow на Dialog, у которого есть Window. Например:

AlertDialog dlg = new AlertDialog.Builder(getActivity()).setView(mPopupView).show();

Наконец, я бы посоветовал вам изменить настройки, чтобы исключить необходимость в членах static в вашем классе Fragment. Эти static поля, скорее всего, вызовут утечку памяти, и ваша IDE вполне может предупредить вас об этом сейчас. Интерфейс слушателя, подобный тому, что у вас есть в CustomChordAdapter, будет достаточен.

person Mike M.    schedule 03.06.2017
comment
Я хотел бы узнать больше о последней части вашего ответа; у вас есть ссылка на какие-либо ресурсы о предотвращении утечек памяти в статических элементах? - person Cody; 03.06.2017
comment
Хм, не совсем. Я имею в виду, что есть только два способа предотвратить утечку с ними: 1.) Не используйте их. 2.) Убедитесь, что вы установили ссылки на null, как только вы закончите с ними. Если вы просто ищете общую информацию о статических утечках и тому подобном, на ней написаны тома. Вот несколько сообщений на сайте, но вы можете найти более подробные объяснения в некоторых блогах, руководствах и т. д.: stackoverflow.com/a/11908685, stackoverflow.com/a/641473, stackoverflow.com/a/28091135. На последнем есть ссылка на старую страницу блога разработчиков Android. - person Mike M.; 03.06.2017
comment
Спас мой день, большое спасибо. У меня была аналогичная проблема при попытке раздуть макет в моем адаптере, и после прочтения вашего комментария я понял, что создал этот адаптер с контекстом приложения, а не с действием. - person Vladimir Kondenko; 19.08.2018