Search code examples
android-viewpagerdrawerlayoutandroid-appbarlayout

Nonsibling Constraint for a ViewPager BELOW its Tabs, where TabLayout in AppbarLayout, and ViewPager in a DrawerLayout


I have a main activity ConstraintLayout which needs to display ViewPager LiveData in the area left visible below the top AppbarLayout which houses the scroll TabLayout items names of the ViewPager. My difficulty is that the top portion of the ViewPager Fragment text is obscured by the AppbarLayout Toolbar and Tabs. I'm trying to get the ViewPager data to be constrained to appear BELOW the AppbarLayout area.

This I can do as in the TabLayout activity template that comes with AS and it works as desired when the TabLayout is by itself. Unfortunately, my Activity also caters to a DrawerLayout NavigationView menu, and the only way the Drawer and ViewPager respond to swipes alternately is when I nest ViewPager in the DrawerLayout, placed before its NavigationView. So my ViewPager is already closed in in a DrawerLayout in the ActivityLayout at the bottom of the XML specification:

<ConstraintLayout
        ...
    <androidx.drawerlayout.widget.DrawerLayout
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        tools:openDrawer="start"
        app:layout_constraintTop_toBottomOf="@+id/cal_appbar">
    <androidx.viewpager.widget.ViewPager
            android:id="@+id/view_pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:focusableInTouchMode="true"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            android:visibility="visible">
        </androidx.viewpager.widget.ViewPager>

        <com.google.android.material.navigation.NavigationView
            android:id="@+id/nav_view"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:fitsSystemWindows="true"
            app:headerLayout="@layout/nav_header_refresh"
            app:menu="@menu/activity_calib_drawer" />

    </androidx.drawerlayout.widget.DrawerLayout>
</ConstraintLayout>

Between it and the root tag where I show ellipses the ConstraintLayout has an AppbarLayout, housing the Toolbar and TabLayout, followed by a RelativeLayout for other Fragments. The problem does not involve the RelativeLayout but the AppbarLayout overlapping the display of the ViewPager

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:fitsSystemWindows="true"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/cal_appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay"
        android:fitsSystemWindows="true"
        tools:context=".Calibsense"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/cal_toolbar"
            android:layout_width="match_parent"
            android:layout_height="?android:attr/actionBarSize"
            android:background="@color/colorPrimary"
            android:visibility="visible"
            app:layout_collapseMode="pin"
            app:popupTheme="@style/AppTheme.PopupOverlay" >

            <TextView
                android:id="@+id/title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:minHeight="?actionBarSize"
                android:padding="@dimen/appbar_padding_top"
                android:text="@string/intro_name"
                android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"/>
        </androidx.appcompat.widget.Toolbar>

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?attr/colorPrimary"
            android:visibility="visible"
            app:layout_constraintTop_toBottomOf="@id/cal_appbar">
        </com.google.android.material.tabs.TabLayout>

    </com.google.android.material.appbar.AppBarLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/overlap">
        ...

    </RelativeLayout>
    ...
</ConstraintLayout>

ConstraintLayout insists on constraining siblings, so my constraints above specify containing views DrawerLayout constrained against AppbarLayout and not the ultimate targets: ViewPager against TabLayout.

Is there a constraint heuristic that can be used to specify that the fragment (ViewPager) displays below the AppbarLayout-TabLayout?

As a possible workaround, is there a way to specify a top-padding for ViewPager with the height of AppbarLayout to push its contents into the viewable area?

I've also looked into trying the CollapsingToolbarLayout but have managed to show the entire ViewPager absent the AppBarLayout altogether.

Addendum:

Looking further at the programmatic invocation of the above during the Activity's onCreate() method call to setGuide(), I expose the AppbarLayout parameters below and the debugger shows me the same values as the XML settings: height -1 (match_parent) and width -2 (wrap_content), and the value remains the same after expansion with child views at the end of setGuide(). The measuredHeight shows value zero throughout. I do not know the significance of these values; I was hoping to see geometrical values such as non-zero px / dp quantities. Can anyone shed some light? Does the measuredHeight of zero also imply the ViewPager is being anchored at px (0,0), and if so how does one change the anchor y-value to below the AppbarLayout's visible bottom? But I'd need to extract that value programmatically first. How can that be achieved?

DrawerLayout drwLyt;
SectionsPagerAdapter sectionsPagerAdapter;
ViewPager viewPager;
AppBarLayout aBL;
CollapsingToolbarLayout cTBL;
TabLayout tabs;
TextView title;
ConstraintLayout mainGuideView;
Calibsense guideParent;

void setGuide(Calibsense guideModule){
    guideParent = guideModule;
    // AppbarLayout
    aBL = guideParent.findViewById(R.id.cal_appbar);
    ViewGroup.LayoutParams aBLParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    aBL.setLayoutParams(aBLParams);
    aBL.setExpanded(true);
    // measure AppbarLayout using params copy of internal value
    ViewGroup.LayoutParams appBarLayoutParams = aBL.getLayoutParams();
    int aBLHeight = appBarLayoutParams.height;
    int aBLmHeight =  aBL.getMeasuredHeight();

    drwLyt =  findViewById(R.id.drawer_layout);
    ViewGroup.LayoutParams drawerLayoutParams = drwLyt.getLayoutParams();
    int drwLytHeight = drawerLayoutParams.height;

    // setting-up of viewPager
    sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
    viewPager = findViewById(R.id.view_pager);
    // this could be redundant,
    // somewhere it was suggested a new LayoutParams() is required
    ViewGroup.LayoutParams viewPagerParams = viewPager.getLayoutParams();
    viewPager.setAdapter(sectionsPagerAdapter);
    int viewPagerHeight = viewPager.getHeight();

    View spacerView = findViewById(R.id.pager_spacer);
    int spacerHeight = spacerView.getHeight();
    spacerView.setMinimumHeight(aBLHeight);
    // Start set-up of tabs
    TabLayout tabs = findViewById(R.id.tabs);

    // complete set-up of tabs by insertion of viewPager
    tabs.setupWithViewPager(viewPager);
    int tabsHeight = tabs.getHeight();
    relL.setVisibility(View.INVISIBLE);
    // measure AppbarLayout using params copy of internal value
    appBarLayoutParams = aBL.getLayoutParams();
    aBLHeight = appBarLayoutParams.height;
    aBLmHeight =  aBL.getMeasuredHeight();
}

Solution

  • It seems the specification:

    android:fitsSystemWindows="true"

    overrides the other constraints and anchors the viewable item at (0,0), but the reach and scope of that override is not clear to me so I shall just avoid it for now.

    Further, after many trials-and-error (including a few faux pas into hide/show SystemUI and decorView Insets, etc) the solution to this subtle problem is in this subtlety:

    For toolbar to show on top of content, declare it last, and for ViewPager content to not be obscured by systemDecor, declare it first. So current solution in xml tag macro-code is:

    <CoordinatorLayout>
       <DrawerLayout>
       <ViewPager>...</ViewPager>
       <RelativeLayout>...</RelativeLayout>
       <AppBarLayout>
          <TabLayout>...</TabLayout>
       </AppBarLayout>
       <ToolBar/>
       <NavigationView/>
       </DrawerLayout>
    </CoordinatorLayout>
    

    Then all components behave as advertised (apart from TabLayout not exposing its last Page title). Simple problems demand simple solutions, not a delve into API internals!