Изменения плавающего действия кнопки привязки при создании вида

У меня проблема с FAB, привязанным к списку (См. Пример видео), кажется, мерцает на ничью, изменяя якорь положение. Проблема возникает в эмуляторе, а также на любом устройстве, которое я тестировал на уровне API 19 после публикации.

У меня есть основное действие:

<LinearLayout
    android:id="@+id/main_layout"
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!-- our toolbar -->
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        android:elevation="4dp"/>

    <!-- our tablayout to display tabs  -->
    <android.support.design.widget.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        android:elevation="4dp"/>

    <!-- View pager to swipe views -->
    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="fill_parent" />

</LinearLayout>

В этом случае нижний nav

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#fff">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/svBody"
        android:background="#fff">
    </FrameLayout>
    <android.support.design.widget.BottomNavigationView
        android:id="@+id/bottomNavigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        app:itemBackground="@color/colorPrimary"
        android:background="@color/colorPrimary"
        app:itemIconTint="@color/cardview_light_background"
        app:itemTextColor="@color/cardview_light_background"
        app:menu="@menu/bottom_navigation_main">
    </android.support.design.widget.BottomNavigationView>
</LinearLayout>

Что содержит фрагмент:

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/lvWords"
    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:orientation="vertical"
        android:background="#fff">

        <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal"
            android:background="#fff"
            android:elevation="4dp">

        <TextView
            android:text="Lipshapes"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:id="@+id/lipshapes_titlestrip"
            android:background="#33b5e5"
            android:textColor="#fff"
            android:textSize="32sp"
            android:paddingTop="4dp"
            android:paddingBottom="4dp"
            android:elevation="6dp"
            android:paddingLeft="@dimen/margin_medium"
            android:paddingRight="@dimen/margin_medium"/>

            <TextView
                android:id="@+id/words_titlestrip"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_gravity="bottom|right"
                android:layout_weight="1"
                android:background="#059FD8"
                android:elevation="10dp"
                android:paddingBottom="4dp"
                android:paddingLeft="@dimen/margin_medium"
                android:paddingRight="@dimen/margin_medium"
                android:paddingTop="4dp"
                android:text="Words"
                android:textColor="#fff"
                android:textSize="32sp" />

        </LinearLayout>

        <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:orientation="vertical"
        android:layout_weight="1"
        android:elevation="0dp"
        android:background="#fff">


        <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="0dp"
            android:layout_weight="1"
            />

        <View
            android:id="@+id/view"
            android:layout_width="match_parent"
            android:layout_height="4dp"
            android:background="@drawable/shadow" />

    </LinearLayout>
    </LinearLayout>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|right"
        android:layout_margin="16dp"
        android:src="@drawable/plus_white_48"
        app:layout_anchor="@android:id/list"
        app:layout_anchorGravity="bottom|right|end"
        app:fabSize="normal" />

И вот код фрагментов:

public static class WordListSectionFragment extends ListFragment implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener, View.OnClickListener, LoaderManager.LoaderCallbacks<Cursor> {

        public SimpleCursorAdapter wordAdapter;
        final static int LIST_VIEW = 0;
        private FloatingActionButton fab;
        String lipshape = null;
        int lipshape_id = 0;

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_section_wordlist, container, false);
            fab = (FloatingActionButton) rootView.findViewById(R.id.fab);


            Bundle args = getArguments();

            lipshape = args.getString("lipshape_selected_desc");

            lipshape_id = Integer.valueOf(args.getString("lipshape_selected_id"));

            ((TextView) rootView.findViewById(R.id.words_titlestrip)).setText(
                    lipshape
            );

            TextView lipshapesTV = (TextView) rootView.findViewById(R.id.lipshapes_titlestrip);
            lipshapesTV.setOnClickListener(this);


            return rootView;
        }

        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);

            //  if you only want one column do it like this
            // String[] projection = new String[]{BaseColumns._ID, VideoFilesContract.videoFiles.FILENAME};
            wordAdapter =
                    new SimpleCursorAdapter(
                            getContext(),
                            R.layout.item_word,
                            null,
                            new String[] {WordContract.wordItems.DESCRIPTION, "number_of_videos"},
                            new int[] {R.id.tvWord, R.id.tvVideoCount},
                            LIST_VIEW);


            // Setup cursor adapter using cursor from last step

            setListAdapter(wordAdapter);
            //getListView().setOnItemClickListener(this);

            getLoaderManager().initLoader(LIST_VIEW, null, this);




        }

        @Override
        public void onViewCreated(View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            getListView().setOnItemClickListener(this);
            getListView().setOnItemLongClickListener(this);


            fab.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    // Click action
                    //Toast.makeText(getActivity(), "Button Clicked!", Toast.LENGTH_SHORT).show();

                    Intent intent = new Intent(getActivity(), AddWord.class);
                    intent.putExtra("lipshape_selected_id", lipshape_id);
                    intent.putExtra("lipshape_selected_desc", lipshape);


                    startActivityForResult(intent, 1);
                }
            });
        }

        public void restartLoader()
        {
            wordAdapter.swapCursor(null);
            getLoaderManager().restartLoader(LIST_VIEW, null, this);
        }

        @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            switch(requestCode) {
                case (1) : {
                    if (resultCode == Activity.RESULT_OK) {

                        Boolean returnValue = data.getBooleanExtra("added_word", false);

                        if (returnValue)
                        {
                            restartLoader();
                            Snackbar.make(getView().getRootView().findViewById(R.id.lvWords), "Word Added Successfully!", Snackbar.LENGTH_LONG)
                                    .setAction("Action", null).show();
                        }
                    }
                    break;
                }
            }
        }

        @Override
        public void onClick(View v)
        {
            if (v.getId() == R.id.lipshapes_titlestrip)
            {
                LipshapeSectionFragment fragment = new LipshapeSectionFragment();

                FragmentManager fragmentManager = getFragmentManager();
                fragmentManager.beginTransaction().replace(R.id.svBody, fragment).commit();
            }
        }

        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int position,long id)
        {
            Cursor _cursor  = ((Cursor) getListView().getItemAtPosition(position));
            Cursor cursor;
            int count =0;

            //do a count for videos if 0

            int word_id = _cursor.getInt(_cursor.getColumnIndex("_id"));
            String word  = _cursor.getString(_cursor.getColumnIndex(WordContract.wordItems.DESCRIPTION));

            ContentResolver resolver = getContext().getContentResolver();

            if (word_id!=0) {
                cursor =
                        resolver.query(VideoFilesContract.videoFiles.CONTENT_URI,
                                VideoFilesContract.videoFiles.PROJECTION_ALL,
                                "word_id=?",
                                new String[]{Integer.toString(word_id)},
                                null,
                                null);

                count = cursor.getCount();
            }


            switch(count)
            {
                case 0:

                    Snackbar.make(view, "There are no videos recorded with this word.", Snackbar.LENGTH_LONG)
                            .setAction("Action", null).show();

                    break;
                default:
                    Intent intent = new Intent(getActivity(), CollectionDemoActivity.class);
                    intent.putExtra("word_id", word_id);
                    intent.putExtra("word", word);
                    startActivity(intent);

            }



            //Toast.makeText(getActivity(), "Item: " + test, Toast.LENGTH_SHORT).show();
        }

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
            CursorLoader loader = null;

            Bundle args = getArguments();



            switch (id) {


                case LIST_VIEW:
                    loader = new CursorLoader(
                            this.getActivity(),
                            WordContract.wordItems.CONTENT_TEST,
                            new String[]{WordContract.wordItems._ID, WordContract.wordItems.DESCRIPTION, "number_of_videos"},
                            null,
                            new String[]{args.getString("lipshape_selected_id")},
                            null);

                    return loader;
                default:
                    return loader;

            }
        }

            @Override
            public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
            ((SimpleCursorAdapter)this.getListAdapter()).
                    swapCursor(cursor);
        }

            @Override
            public void onLoaderReset(Loader<Cursor> loader) {
            ((SimpleCursorAdapter)this.getListAdapter()).
                    swapCursor(null);
        }

        public void showDialog(final int word_id, String word_clicked) {
            AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
            builder.setTitle("Delete Word: "+ word_clicked + "?");
            builder.setMessage("Deleting this word will also delete all videos tagged by this word.");


            String positiveText = "Delete";
            builder.setPositiveButton(positiveText,
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {

                           deleteWord(word_id);
                            //reload

                        }
                    });

            String negativeText = getString(android.R.string.cancel);
            builder.setNegativeButton(negativeText,
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            // negative button logic
                        }
                    });

            AlertDialog dialog = builder.create();
            // display dialog
            dialog.show();
        }

        public boolean deleteWord(int word_id)
        {
            ContentResolver resolver = getContext().getContentResolver();

            Uri delUri = ContentUris.withAppendedId(WordContract.wordItems.CONTENT_URI, word_id);

            //resolver.delete(VideoFilesContract.videoFiles.CONTENT_URI, "_id=?", selectionArgs);

            long resultCount = resolver.delete(delUri, null, null);

            if (resultCount == 0)
            {
                //couldn't delete word with that id
                return false;
            }
            else
            {
                restartLoader();
                Snackbar.make(getListView(), "Word deleted successfully.", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
                return true;
            }
        }

        @Override
        public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l)
        {

            //Snackbar.make(view, "Item has been long clicked!", Snackbar.LENGTH_LONG)
              //      .setAction("Action", null).show();
            Cursor _cursor  = ((Cursor) getListView().getItemAtPosition(i));
            Cursor cursor;
            int count =0;

            //do a count for videos if 0

            int word_id = _cursor.getInt(_cursor.getColumnIndex("_id"));
            String word  = _cursor.getString(_cursor.getColumnIndex(WordContract.wordItems.DESCRIPTION));

            showDialog(word_id, word);

            return true;
        }
    }

Ответ 1

Я не смог найти более чистое исправление, чем сначала установить видимость FAB на gone, а установить привязку к списку:

<android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|right|end"
        android:layout_margin="16dp"
        android:src="@drawable/plus_white_48"
        android:visibility="gone"
        app:layout_anchor="@android:id/list"
        app:layout_anchorGravity="bottom|right|end"
        app:fabSize="normal" />

И если список не пуст после того, как курсор закончил загрузку, просто сделайте его видимым, и он привяжется, как указано выше, и в случаях, когда lis пуст, мы устанавливаем параметры макета для привязки в пустом списке:

if (wordAdapter.getCursor().getCount()<1)
{
//the linear layout for the empty list view state
                ll.setVisibility(View.VISIBLE); 
//set the text of this to be visible too, default is invisible as otherwise it visible when the list is loading
                tvFeedback.setVisibility(View.VISIBLE);

                CoordinatorLayout.LayoutParams p (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
                p.setAnchorId(R.id.empty_list_view);
                fab.setLayoutParams(p);
                fab.setVisibility(View.VISIBLE);

}

Это приводит к не мерцанию при загрузке, также приводит к тому, что привязка всегда находится в правильном месте, даже если элементы из списка удалены.

Ответ 2

Вы видите FAB в левом верхнем углу, потому что в этой точке ListView не выложены на ListView. Когда элементы заполняются, ListView соответствует его родительскому элементу, и FAB позиционируется правильно.

Прикрепите якорь FAB к родительскому, который уже соответствует всему пространству и удерживает ваш ListView.

Что-то вроде этого

<LinearLayout 
    **android:id="@+id/list_parent"**
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#fff">

    <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:orientation="horizontal"
        android:background="#fff"
        android:elevation="4dp">

    <TextView
        android:text="Lipshapes"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:id="@+id/lipshapes_titlestrip"
        android:background="#33b5e5"
        android:textColor="#fff"
        android:textSize="32sp"
        android:paddingTop="4dp"
        android:paddingBottom="4dp"
        android:elevation="6dp"
        android:paddingLeft="@dimen/margin_medium"
        android:paddingRight="@dimen/margin_medium"/>

        <TextView
            android:id="@+id/words_titlestrip"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_gravity="bottom|right"
            android:layout_weight="1"
            android:background="#059FD8"
            android:elevation="10dp"
            android:paddingBottom="4dp"
            android:paddingLeft="@dimen/margin_medium"
            android:paddingRight="@dimen/margin_medium"
            android:paddingTop="4dp"
            android:text="Words"
            android:textColor="#fff"
            android:textSize="32sp" />

    </LinearLayout>

    <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="0dp"
    android:orientation="vertical"
    android:layout_weight="1"
    android:elevation="0dp"
    android:background="#fff">


    <ListView
    android:id="@android:id/list"
    android:layout_width="match_parent"
    android:layout_height="0dp"
        android:layout_weight="1"
        />

    <View
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="4dp"
        android:background="@drawable/shadow" />

</LinearLayout>
</LinearLayout>

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|right"
    android:layout_margin="16dp"
    android:src="@drawable/plus_white_48"
    app:layout_anchor="@+id/list_parent"
    app:layout_anchorGravity="bottom|right|end"
    app:fabSize="normal" />

Дружественный совет, переключитесь на RecyclerView.