Home » Android » ActionBar and Toolbar in a fragment

ActionBar and Toolbar in a fragment

Posted by: admin November 1, 2017 Leave a comment

Questions:

I am struggling to get my fragment child views to take ActionBar/Toolbar into account when displaying.

I have followed a sample from here which has very nicely partitioned the layout into activity layout and fragments with include tags for AppBar/Toolbar and, in my case, floating action button (fab). I have refactored my already working code from having the AppBar/Toolbar and fab on the activity layout with just the fragment being in a separate layout. But the approach of including the AppBar/Toolbar and fab on the fragment gives me the ability to have a clean activity which can accomodate any fragment, with or without AppBar/Toolbar or fab (or any other UI elements). Below is my basic layout setup. The problem I am straggling with is that my RecyclerView is obscured on the the by the Appbar/Toolbar.

activity_main.axml

<?xml version="1.0" encoding="utf-8"?>
<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/main_coordinator_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <!-- Actual content of the screen -->
    <FrameLayout
        android:id="@+id/main_content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true" />

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

fragment_list.axml

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

    <include
        layout="@layout/include_toolbar_actionbar" />

    <MvvmCross.Droid.Support.V4.MvxSwipeRefreshLayout
        android:id="@+id/listsRefresher"
        android:layout_width="match_parent"
        android:layout_height="match_parent"    
        app:MvxBind="Refreshing IsLoading; RefreshCommand ReloadCommand">

        <MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView
            android:id="@+id/listsRecyclerView"
            android:layout_width="match_parent"   
            android:layout_height="match_parent"
            app:MvxItemTemplate="@layout/item_list"
            app:MvxBind="ItemsSource Lists; ItemClick ShowListItemsCommand" />

    </MvvmCross.Droid.Support.V4.MvxSwipeRefreshLayout>

    <include
        layout="@layout/include_floatingactionbutton" />

</FrameLayout>

include_toolbar_actionbar.axml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_app_bar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/AppTheme.ActionBar">

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

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

include_floatingactionbutton.axml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.FloatingActionButton
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|right"
    android:layout_margin="@dimen/margin_medium"
    android:src="@drawable/ic_add_white_24dp"        
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    app:layout_anchor="@id/main_content_frame"
    app:layout_anchorGravity="bottom|right|end" />

MainActivity.cs

namespace List.Mobile.Droid.Activities
{
    [Activity(
        Label = "@string/applicationName",
        Icon = "@drawable/ic_icon", 
        Theme = "@style/AppTheme.Default",
        LaunchMode = LaunchMode.SingleTop,
        ScreenOrientation = ScreenOrientation.Portrait,
        Name = "list.droid.mobile.activities.MainActivity")]
    public class MainActivity : MvxAppCompatActivity<MainViewModel>
    {
        ...    
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            SetContentView(Resource.Layout.activity_main);

            ViewModel.ShowLists();
        }
    }
}

ListsFragment.cs

namespace List.Mobile.Droid.Views
{
    [MvxFragmentPresentation(typeof(MainViewModel), Resource.Id.main_content_frame, true)]
    [Register("list.mobile.droid.views.ListsFragment")]
    public class ListsFragment : BaseFragment<ListsViewModel>, ActionMode.ICallback
    {
        ...    
        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            base.OnCreateView(inflater, container, savedInstanceState);

            var swipeToRefresh = FragmentView.FindViewById<MvxSwipeRefreshLayout>(Resource.Id.refresher);
            if (AppBar != null)
                AppBar.OffsetChanged += (sender, args) => swipeToRefresh.Enabled = args.VerticalOffset == 0;

            var listsRecyclerView = FragmentView.FindViewById<MvxRecyclerView>(Resource.Id.listsRecyclerView);
            ...    
            return FragmentView;
        }
   }
}

BaseFragment.cs

namespace List.Mobile.Droid.Views
{
    public abstract class BaseFragment<T> : MvxFragment<T> where T : MvxViewModel
    {    
        protected abstract int FragmentResourceId { get; }
        protected View FragmentView { get; set; }
        protected AppBarLayout AppBar { get; set; }
        protected FloatingActionButton Fab { get; set; }
        protected Toolbar Toolbar { get; set; } 

        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            var view = base.OnCreateView(inflater, container, savedInstanceState);

            FragmentView = this.BindingInflate(FragmentResourceId, null);

            AppBar = FragmentView.FindViewById<AppBarLayout>(Resource.Id.main_app_bar);
            Fab = FragmentView.FindViewById<FloatingActionButton>(Resource.Id.fab);
            Toolbar = FragmentView.FindViewById<Toolbar>(Resource.Id.main_tool_bar);
            AppCompatActivity parentActivity = ((AppCompatActivity)Activity);
            parentActivity.SetSupportActionBar(Toolbar);
            parentActivity.SupportActionBar.SetDisplayHomeAsUpEnabled(false);
            parentActivity.SupportActionBar.SetHomeButtonEnabled(false);

            return view;
        }
    }
}

Obstructing AppBar/Toolbar

Answers:

If you combine the layout structure of activity+fragment+include you’ll see something like this:

CoordinatorLayout
--FrameLayout -> id=main_content_frame
----FrameLayout -> id=list_frame
------AppBarLayout
--------Toolbar
------SwipeRefresh
--------Recycler
------Fab

That means, you have the toolbar and swipe/recycler in a FrameLayout, and this one on top of the other is exactly the expected behavior.

To fix you should make the AppBar+Swipe+Fab as children of the CoordinatorLayout (which is the layout that properly handles interactions between Toolbar/Fab/Scrolling Content. So change your activity to be just the FrameLayout and re-order the fragment to be:

CoordinatorLayout
--AppBarLayout
----Toolbar
--SwipeRefresh
----RecyclerView
--Fab

and don’t forget to add app:layout_behavior="@string/appbar_scrolling_view_behavior" to the SwipeRefreshLayout, that’s for the Coordinator to properly position it.