AutoHeightViewPager: Dynamically Adjusting ViewPager Height in Android
The article introduces AutoHeightViewPager, a custom ViewPager subclass that measures the current page’s view height and overrides onMeasure to resize itself, interpolating between pages during scroll via an AutoHeightPager‑compatible adapter, thereby eliminating blank space or clipping and providing smooth height transitions for variable‑sized content.
In many Android projects, the default ViewPager has a fixed height, which cannot adapt to varying content heights of its pages. This often results in unnecessary blank space or clipped content when page heights differ.
To address this, a custom AutoHeightViewPager is designed to measure the height of the currently displayed page and adjust its own height dynamically, ensuring that content is fully visible and eliminating extra whitespace.
Effect
The solution provides a smooth transition between pages with different heights, eliminating layout gaps. (Screenshots in the original article illustrate the before‑and‑after effect.)
Implementation Idea
Dynamic height adjustment: create a custom ViewPager subclass ( AutoHeightViewPager ) and override onMeasure to compute height based on the current page.
Listen to page scroll events: during scrolling, interpolate the height between the source and destination pages to achieve a smooth height transition.
Custom adapter: define a PagerAdapter that implements an AutoHeightPager interface, allowing the AutoHeightViewPager to obtain the view for any position and measure its height.
Implementation Code
AutoHeightViewPager (Kotlin)
package com.yxlh.androidxy.demo.ui.viewpager.vp
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
interface AutoHeightPager {
fun getView(position: Int): View?
}
class AutoHeightViewPager @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : ViewPager(context, attrs) {
private var lastWidthMeasureSpec: Int = 0
private var currentHeight: Int = 0
private var lastPosition: Int = 0
private var isScrolling: Boolean = false
init {
addOnPageChangeListener(object : SimpleOnPageChangeListener() {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
if (positionOffset == 0f) {
isScrolling = false
requestLayout()
return
}
val srcPosition = if (position >= lastPosition) position else position + 1
val destPosition = if (position >= lastPosition) position + 1 else position
val srcHeight = getViewHeight(srcPosition)
val destHeight = getViewHeight(destPosition)
currentHeight = (srcHeight + (destHeight - srcHeight) *
if (position >= lastPosition) positionOffset else 1 - positionOffset).toInt()
isScrolling = true
requestLayout()
}
override fun onPageScrollStateChanged(state: Int) {
if (state == SCROLL_STATE_IDLE) {
lastPosition = currentItem
}
}
})
}
override fun setAdapter(adapter: PagerAdapter?) {
require(adapter == null || adapter is AutoHeightPager) { "PagerAdapter must implement AutoHeightPager." }
super.setAdapter(adapter)
}
private fun getViewHeight(position: Int): Int {
val adapter = adapter as? AutoHeightPager ?: return 0
return run {
val view = adapter.getView(position) ?: return 0
view.measure(
lastWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
)
view.measuredHeight
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
lastWidthMeasureSpec = widthMeasureSpec
var heightSpec = heightMeasureSpec
if (isScrolling) {
heightSpec = MeasureSpec.makeMeasureSpec(currentHeight, MeasureSpec.EXACTLY)
} else {
getViewHeight(currentItem).takeIf { it > 0 }?.let {
heightSpec = MeasureSpec.makeMeasureSpec(it, MeasureSpec.EXACTLY)
}
}
super.onMeasure(widthMeasureSpec, heightSpec)
}
}AutoHeightPagerAdapter (Kotlin)
package com.yxlh.androidxy.demo.ui.viewpager
import android.view.View
import android.view.ViewGroup
import androidx.viewpager.widget.PagerAdapter
import com.yxlh.androidxy.demo.ui.viewpager.vp.AutoHeightPager
class AutoHeightPagerAdapter : PagerAdapter(), AutoHeightPager {
private val viewList = mutableListOf
()
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val view = viewList[position]
val parent = view.parent as? ViewGroup
parent?.removeView(view)
container.addView(view)
return view
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
container.removeView(`object` as View)
}
fun setViews(views: List
) {
viewList.clear()
viewList.addAll(views)
notifyDataSetChanged()
}
override fun getView(position: Int): View? {
return viewList.getOrNull(position)
}
override fun getCount(): Int {
return viewList.size
}
override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view == `object`
}
}Activity Usage (Kotlin)
package com.yxlh.androidxy.demo.ui.viewpager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.yxlh.androidxy.R
import com.yxlh.androidxy.demo.ui.viewpager.vp.AutoHeightViewPager
class VpActivity : AppCompatActivity() {
private var mAutoHeightVp: AutoHeightViewPager? = null
private var mAdapter: AutoHeightPagerAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_vp)
val viewList: MutableList
= ArrayList()
viewList.add(LayoutInflater.from(this).inflate(R.layout.view_demo_1, null))
viewList.add(LayoutInflater.from(this).inflate(R.layout.view_demo_2, null))
mAutoHeightVp = findViewById(R.id.viewpager)
mAutoHeightVp?.setAdapter(AutoHeightPagerAdapter().also { mAdapter = it })
mAdapter?.setViews(viewList)
mAutoHeightVp?.setCurrentItem(1)
}
}Layout XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.yxlh.androidxy.demo.ui.viewpager.vp.AutoHeightViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitXY"
android:src="@drawable/vp_content" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>Summary
By customizing ViewPager to adjust its height dynamically, layout problems caused by inconsistent page content heights are resolved. The approach provides smooth height transitions and is well‑suited for scenarios where page content varies in size.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.