视图创建时浮动操作按钮锚点更改
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);
这导致加载时不会闪烁,即使列表中的项目被删除,也会导致锚点始终位于正确的位置。
【讨论】:
以上是关于视图创建时浮动操作按钮锚点更改的主要内容,如果未能解决你的问题,请参考以下文章