ViewPager's height in Coordinator layout is more than available
Asked Answered
D

5

14

I have a CoordinatorLayout with a Toolbar and a TabLayout inside the AppBarLayout. Additionally, I have a ViewPager inside the CoordinatorLayout but outside the ViewPager.

The problem is that the ViewPager's height is bigger than what is actually available, resulting in some views from my Fragment being cut.

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/lightGray"
    android:fitsSystemWindows="true">

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

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

        <android.support.design.widget.TabLayout
            android:id="@+id/tabLayout"
            android:scrollbars="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fillViewport="false"/>

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

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

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

And this what I mean when I say that the ViewPager has the wrong height.
enter image description here

Daliladalis answered 28/9, 2016 at 6:44 Comment(2)
Possible duplicate of Part of fragment inside ViewPager getting cut off at bottom of screen(Android)Southernly
Please take a look at the explanation I have on https://mcmap.net/q/828165/-coordinatorlayout-moves-contentDistinctive
S
7

A possible hack can be adding same bottom margin as toolbar which is

?attr/actionBarSize . You can even fiddle around with other possible ui hacks of margins to give you best result.

Southernly answered 28/9, 2016 at 6:52 Comment(6)
Doing this solves the problem of the height being wrong, however all scrolling effects are removed. Also, what's the point in having "enterAlways" without "scroll"? I thought that scroll was mandatory in order to use "enterAlways|snap|etc",Daliladalis
Actually removing the layout_scrollFlags attribute in all will solve the height issue. As scroll is indeed mandatory for any scrolling to take place. This solution was more influenced by the possible duplicate. As it feels weird that same setup doesn't work for others too. If you want to keep scroll on then there is a slight hack which I would ask you to try as the wrong height is almost equal to height of toolbar. Try putting a bottom margin of ?attr/actionBarSize and keep scroll flags as original. If that gives you satisfactory results I will update this answer.Southernly
Try proposed bottom margin in view Pager.Southernly
Adding the margin fixes it. I would suggest using the same margin as the height of the tablayout to be more exact. So instead of using height="wrap_content" use height="x" and use x as the bottom margin of the view pager. Thanks.Daliladalis
Glad it worked for you. I updated the answer to reflect final solution please consider upvoting and accepting it. Good luck :)Southernly
Hello gentlemen. First of all that is a great question with beautiful pictures and code samples. Secondly the answer works and fixes the issue - I am very grateful, but it will be better to explicitly add to the answer that you suppose to add margin to the view pager. I spent some time trying to add it to the coordinator layout itself.Renascent
K
14

UDPATE: I do not recommend this anymore. I found that it was causing an infinite loop of calls to onDependentViewChanged.

Based on Yevhenii's answer above, I think I managed to solve this in an even simpler way:

class KeepWithinParentBoundsScrollingBehavior : AppBarLayout.ScrollingViewBehavior {

    constructor() : super()

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)


    override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
        if (dependency !is AppBarLayout) {
            return super.onDependentViewChanged(parent, child, dependency)
        }

        val layoutParams = child.layoutParams as CoordinatorLayout.LayoutParams
        layoutParams.height = parent.height - dependency.bottom
        child.layoutParams = layoutParams
        return super.onDependentViewChanged(parent, child, dependency)
    }
}

Then set app:layout_behavior="your.package.KeepWithinParentBoundsScrollingBehavior" on your ViewPager or whatever view you have below the AppBar.

Take note that this is not a generic solution for all CoordinatorLayouts, but it seems to work when you have a view below an app bar that you don't want to let extend beyond the bottom of the parent CoordinatorLayout.

UPDATE: You should also set app:layout_anchor="@id/app_bar" on your ViewPager for the situation when the keyboard disappears. If you don't the ViewPager layout will not be refreshed when the keyboard disappears and the ViewPager will appear cut off.

Kinlaw answered 9/10, 2019 at 2:49 Comment(1)
If this is no longer the recommended approach, do you have other work around?Delay
A
10

I've spent a lot of time googling and applying suggested answers, but I've not succeeded, so decided to dive deeper into the problem and find out a complete answer, which I want to share here. Maybe it will help somebody.

So the problem is that when ViewPager is used with Collapsing Toolbar, the first one doesn't calculate its height properly. And if you want the ViewPager fill all the space to the bottom of the screen, but not more - there is a problem. Just adding layout_marginBottom will not solve the problem because of Collapsing Toolbar will change its height when user scroll.

So if you want your ViewPager to adapt its height accordingly to Collapsing Toolbar height changes, you need 2 things:

  1. Custom scrolling behavior.
  2. GlobalLayoutListener, which will fix ViewPager height when it's drawn for first time.

Here they are (written in Kotlin, but it's just a separate file and can be placed into Java project directly):

import android.app.Activity
import android.content.Context
import android.graphics.Point
import android.support.design.widget.AppBarLayout
import android.support.design.widget.CoordinatorLayout
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver

/**
 * Custom extension of AppBarLayout.ScrollingViewBehavior to deal with ViewPager height
 * in a bunch with Collapsing Toolbar Layout. Works dynamically when AppBar Layout height is changing.
 */
class ViewPagerScrollingBehavior(context: Context, attributeSet: AttributeSet? = null) :
  AppBarLayout.ScrollingViewBehavior(context, attributeSet) {

  override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
    val layoutParams = child.layoutParams as CoordinatorLayout.LayoutParams
    layoutParams.height = child.height - (dependency.bottom - child.top)
    child.layoutParams = layoutParams
    child.requestLayout()
    return super.onDependentViewChanged(parent, child, dependency)
  }

}

/**
 * Custom implementation of ViewTreeObserver.OnGlobalLayoutListener to fix the View height
 * in a bunch with Collapsing Toolbar Layout. Works when View is drawn on the screen for first time.
 * To be used with ViewPagerScrollingBehavior.
 */
class FixHeightGlobalLayoutListener(val activity: Activity, val view: View) : ViewTreeObserver.OnGlobalLayoutListener {

  override fun onGlobalLayout() {
    val display = activity.windowManager.defaultDisplay
    val size = Point()
    display.getSize(size)
    val height = size.y

    val location = IntArray(2)
    view.getLocationOnScreen(location)

    view.post {
      val layoutParams = view.layoutParams as ViewGroup.LayoutParams
      layoutParams.height = height - location[1]
      view.layoutParams = layoutParams
      view.requestLayout()
    }

    view.viewTreeObserver.removeOnGlobalLayoutListener(this)
  }

}

And use them in your code:

  1. Add the custom behavior to your ViewPager: app:layout_behavior="your.package.ViewPagerScrollingBehavior"

  2. Add custom OnGlobalLayoutListener to your ViewPager: viewPager.getViewTreeObserver().addOnGlobalLayoutListener(new FixHeightGlobalLayoutListener(this, viewPager));

Aldus answered 7/5, 2018 at 12:48 Comment(3)
What is the different of viewPager and mViewPager?Rosebud
No difference :) just forgot to renameAldus
I managed to solve this without using a GlobalLayoutListener I think. See my answer belowKinlaw
S
7

A possible hack can be adding same bottom margin as toolbar which is

?attr/actionBarSize . You can even fiddle around with other possible ui hacks of margins to give you best result.

Southernly answered 28/9, 2016 at 6:52 Comment(6)
Doing this solves the problem of the height being wrong, however all scrolling effects are removed. Also, what's the point in having "enterAlways" without "scroll"? I thought that scroll was mandatory in order to use "enterAlways|snap|etc",Daliladalis
Actually removing the layout_scrollFlags attribute in all will solve the height issue. As scroll is indeed mandatory for any scrolling to take place. This solution was more influenced by the possible duplicate. As it feels weird that same setup doesn't work for others too. If you want to keep scroll on then there is a slight hack which I would ask you to try as the wrong height is almost equal to height of toolbar. Try putting a bottom margin of ?attr/actionBarSize and keep scroll flags as original. If that gives you satisfactory results I will update this answer.Southernly
Try proposed bottom margin in view Pager.Southernly
Adding the margin fixes it. I would suggest using the same margin as the height of the tablayout to be more exact. So instead of using height="wrap_content" use height="x" and use x as the bottom margin of the view pager. Thanks.Daliladalis
Glad it worked for you. I updated the answer to reflect final solution please consider upvoting and accepting it. Good luck :)Southernly
Hello gentlemen. First of all that is a great question with beautiful pictures and code samples. Secondly the answer works and fixes the issue - I am very grateful, but it will be better to explicitly add to the answer that you suppose to add margin to the view pager. I spent some time trying to add it to the coordinator layout itself.Renascent
R
0

You should not set behavior type "appbar_scrolling_view_behavior" to your ViewPager. But set it to the outer layout. Somethinglike below

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    >
    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/tabbed_fragments_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
</RelativeLayout>
Rapport answered 24/8, 2021 at 23:15 Comment(0)
N
0

Since the most upvoted solution is no longer recommended, I found another suggestion. Quoting the original author:

I simply update the bottom padding of the view that should stay fixed, e.g. like following:

binding.appbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
    // llBottomContent is my view that should be resized instead of moved
    binding.llBottomContent.updatePadding(bottom = appBarLayout.totalScrollRange+ verticalOffset)
})
Necrophilism answered 4/6 at 16:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.