在片段或活动中使用工具栏的协调器布局

随着新的设计库有几个新的布局,改变了很多工具栏的行为,如果开发人员希望的话。由于不同的片段有不同的行为和目标,例如一个画廊片段的折叠工具栏显示一个重要的照片,或片段没有滚动视图,只是不需要应用程序布局来隐藏工具栏,在活动中有一个单一的工具栏可以证明是困难的。

那么,我应该将工具栏移动到每个片段吗?如果是这样的话,我必须在每次显示片段时设置 support ActionBar,并且在片段中有一个活动的引用,这使得片段的独立性失效。如果我把工具栏单独放在 Activity 中,我必须为每个片段中的每种行为定义多种布局。最好的办法是什么?

44166 次浏览

This is a realy good question: should Toolbars that need to act like an ActionBar be kept in a Activity or a Fragment? Having searched around different questions and documentation, I couldn't find a solution that covers all cases. It therefore really depends on your situation wich way to go.

Case 1: Toolbar must be ActionBar replacement

If the Toolbar has to behave like a normal ActionBar (or if max 1 fragment is shown from time to time), I think the best/simplest way is to use traditional Activities with there own Toolbar and put your Fragment in there. This way you don't have to worry about when which Toolbar must be shown.

Changing the ActionBar (-behaviour) from Fragments is also possible, but I would not recommend it, since that forces you to keep track which Fragment changed the ActionBar when. I don't even know if setting the ActionBar can be done multiple times.

Case 2: Each Fragment should have its own (part of) Toolbar

You could also choose to put different stand alone Toolbars in different Fragments, with their own actions. This way you could display different Fragments next to each other - each with their own actions in their Toolbar - and suggest that it is 1 Toolbar (maybe like the Gmail-app, although I am unsure). This however means that you would have to inflate those Toolbars yourself, but it must not be very difficult.

I hope this will help making a choice.

(Sorry if I made any (language-)mistakes)

As for me it sounds too weird to have appbar and toolbar in each fragment. So I've chosen to have single appbar with toolbar in activity.

To solve that issue with CoordinatorLayout you will have to set different behaviour of your FrameLayout (or any other Layout) that supposed to hold fragments from each fragment that you want to override default behaviour.

Lets assume, that your default behaviour is app:layout_behavior="@string/appbar_scrolling_view_behavior"

Then in your fragment_activity_layout.xml you may have something like that:

<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent">


<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">


<android.support.v7.widget.Toolbar
android:id="@+id/dashboard_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.Toolbar"
app:layout_scrollFlags="scroll|enterAlways"/>
</android.support.design.widget.AppBarLayout>


<FrameLayout
android:id="@+id/dashboard_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>

And in each fragment you wish not to implement app:layout_behavior="@string/appbar_scrolling_view_behavior" you will have to override onAttach and onDetach methods that will change behaviour of your FrameLayout:

CoordinatorLayout.Behavior behavior;


@Override
public void onAttach(Activity activity) {
super.onAttach(activity);


if(behavior != null)
return;


FrameLayout layout =(FrameLayout) getActivity().findViewById(R.id.dashboard_content);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) layout.getLayoutParams();


behavior = params.getBehavior();
params.setBehavior(null);


}


@Override
public void onDetach() {
super.onDetach();
if(behavior == null)
return;


FrameLayout layout =(FrameLayout) getActivity().findViewById(R.id.dashboard_content);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) layout.getLayoutParams();


params.setBehavior(behavior);


layout.setLayoutParams(params);


behavior = null;
}

After that CoordinatorLayout won't collapse appbar, etc. and will allow fragment layouts to be full-height.

Here's my solution

<!-- Put your fragment inside a Framelayout and set the behavior for this FrameLayout -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">


<!-- Your fragment -->
<include layout="@layout/content_main" />


</FrameLayout>


<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">


<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/AppTheme.PopupOverlay" />


</android.support.design.widget.AppBarLayout>

That's what the navigation documentation says:

Adding the top app bar to your activity works well when the app bar’s layout is similar for each destination in your app. If, however, your top app bar changes substantially across destinations, then consider removing the top app bar from your activity and defining it in each destination fragment, instead.

Actually it's very easy to setup a Toolbar using NavigationUI, and this solution doesn't require the Fragment to have any knowledge about its parent. For example:

<!-- my_fragment.xml -->
<androidx.constraintlayout.widget.ConstraintLayout ...>


<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
... />


</androidx.constraintlayout.widget.ConstraintLayout>
class MyFragment : Fragment() {
...


override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)


val navController = findNavController()
binding.toolbar.setupWithNavController(navController)
}
}

You can find the full GitHub example here. There's also a relative question that might be interested Is setSupportActionbar required anymore?