视图创建时浮动操作按钮锚点更改

Posted

技术标签:

【中文标题】视图创建时浮动操作按钮锚点更改【英文标题】:Floating Action Button Anchor Changes on View Creation 【发布时间】:2017-12-26 17:47:39 【问题描述】:

我在将 FAB 锚定到列表视图 (See Video Example) 时遇到问题,它似乎通过更改锚位置在绘制时闪烁。问题出现在模拟器中,也出现在我在 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_
    android:layout_
    tools:context=".MainActivity">

    <!-- our toolbar -->
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_
        android:layout_
        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_
        android:layout_
        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_
        android:layout_ />

</LinearLayout>

然后保持底部导航

<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_
    android:layout_
    android:orientation="vertical"
    android:background="#fff">

    <FrameLayout
        android:layout_
        android:layout_
        android:layout_weight="1"
        android:id="@+id/svBody"
        android:background="#fff">
    </FrameLayout>
    <android.support.design.widget.BottomNavigationView
        android:id="@+id/bottomNavigation"
        android:layout_
        android:layout_
        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_
    android:layout_
    android:orientation="vertical">


    <LinearLayout
        android:layout_
        android:layout_
        android:orientation="vertical"
        android:background="#fff">

        <LinearLayout
        android:layout_
        android:layout_
        android:orientation="horizontal"
            android:background="#fff"
            android:elevation="4dp">

        <TextView
            android:text="Lipshapes"
            android:layout_
            android:layout_
            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_
                android:layout_
                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_
        android:layout_
        android:orientation="vertical"
        android:layout_weight="1"
        android:elevation="0dp"
        android:background="#fff">


        <ListView
        android:id="@android:id/list"
        android:layout_
        android:layout_
            android:layout_weight="1"
            />

        <View
            android:id="@+id/view"
            android:layout_
            android:layout_
            android:background="@drawable/shadow" />

    </LinearLayout>
    </LinearLayout>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_
        android:layout_
        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,因为此时 ListView 没有布置在 ListView 上。填充项目后,ListView 匹配其父项,并且 FAB 已正确定位。

将 FAB 的锚附加到已匹配整个空间并持有您的ListView 的父级。

类似的东西

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

    <LinearLayout
    android:layout_
    android:layout_
    android:orientation="horizontal"
        android:background="#fff"
        android:elevation="4dp">

    <TextView
        android:text="Lipshapes"
        android:layout_
        android:layout_
        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_
            android:layout_
            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_
    android:layout_
    android:orientation="vertical"
    android:layout_weight="1"
    android:elevation="0dp"
    android:background="#fff">


    <ListView
    android:id="@android:id/list"
    android:layout_
    android:layout_
        android:layout_weight="1"
        />

    <View
        android:id="@+id/view"
        android:layout_
        android:layout_
        android:background="@drawable/shadow" />

</LinearLayout>
</LinearLayout>

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    android:layout_
    android:layout_
    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

【讨论】:

这似乎会导致同样的问题,我想是因为当第一次换入片段时,新视图仍处于“0”高度。【参考方案2】:

我找不到比最初将 FAB 的可见性设置为 gone 而是将锚点设置为列表视图的更简洁的解决方法:

<android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_
        android:layout_
        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's 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);


这导致加载时不会闪烁,即使列表中的项目被删除,也会导致锚点始终位于正确的位置。

【讨论】:

以上是关于视图创建时浮动操作按钮锚点更改的主要内容,如果未能解决你的问题,请参考以下文章

弹出框未附加到锚点

使用自动布局时,如何调整 CALayer 的锚点?

约束或其锚点是不是引用不同视图层次结构中的项目?这是违法的

锚点盒子随滚动条浮动

浮动列表项内的垂直居中锚点

拖动视图时设置自定义锚点