Skip to content

Instantly share code, notes, and snippets.

@aqua30
Created May 1, 2021 09:29
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save aqua30/b7da69fc8d78c0b117a1fb6dd5bc1d00 to your computer and use it in GitHub Desktop.
Save aqua30/b7da69fc8d78c0b117a1fb6dd5bc1d00 to your computer and use it in GitHub Desktop.
Adjusting View Pager which adjusts its height according to the view's height in focus
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.aqua30.learningproject.AdjustingViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_heart"
android:layout_width="@dimen/width_action_icon"
android:layout_height="@dimen/height_action_icon"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="@dimen/margin_16"
android:layout_marginStart="@dimen/margin_16"
android:src="@drawable/ic_heart"
app:layout_constraintTop_toBottomOf="@id/view_pager" />
<ImageView
android:id="@+id/iv_comment"
android:layout_width="@dimen/width_action_icon"
android:layout_height="@dimen/height_action_icon"
android:src="@drawable/ic_comment"
android:layout_marginStart="@dimen/margin_24"
app:layout_constraintStart_toEndOf="@id/iv_heart"
app:layout_constraintTop_toTopOf="@id/iv_heart" />
<ImageView
android:id="@+id/iv_share"
android:layout_width="@dimen/width_action_icon"
android:layout_height="@dimen/height_action_icon"
android:src="@drawable/ic_share"
android:layout_marginStart="@dimen/margin_24"
app:layout_constraintStart_toEndOf="@id/iv_comment"
app:layout_constraintTop_toTopOf="@id/iv_heart" />
<TextView
android:id="@+id/tv_actions_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@id/iv_heart"
app:layout_constraintTop_toBottomOf="@id/iv_heart"
android:layout_marginTop="@dimen/margin_16"
android:text="@string/action_count"
android:textColor="#95a5a6"
android:textSize="@dimen/text_size_12" />
<TextView
android:id="@+id/tv_description"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="@id/iv_heart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_actions_count"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="@dimen/margin_16"
android:layout_marginTop="@dimen/margin_16"
android:text="@string/description"
android:textColor="#000000"
android:textSize="@dimen/text_size_16" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
package com.aqua30.learningproject
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.View.MeasureSpec
import androidx.viewpager.widget.ViewPager
class AdjustingViewPager: ViewPager {
private lateinit var currentView: View /* current page view in focus */
private val maxHeight= 480f /* maximum height that we want on screen */
private val allowedHeight = convertDpToPixel(maxHeight) /* allowed height in pixel */
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var newViewHeight = heightMeasureSpec
val mode = MeasureSpec.getMode(heightMeasureSpec)
/* until the parent has not assigned the size to the view
and it is either not determined or at its maximum, assign it
the new measurement as it comes in focus
*/
if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
if (currentView == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
return
}
try {
/* measuring the current dimensions */
currentView.measure(
widthMeasureSpec, MeasureSpec.makeMeasureSpec(
0,
MeasureSpec.UNSPECIFIED
)
)
var currentViewHeight = currentView.measuredHeight
/*
if the current view height is greater than allowed height then
set it to allowed height
*/
if (currentViewHeight > allowedHeight) {
currentViewHeight = allowedHeight.toInt()
}
newViewHeight = MeasureSpec.makeMeasureSpec(currentViewHeight, MeasureSpec.EXACTLY)
} catch (e: NullPointerException) {
Log.e(AdjustingViewPager::class.simpleName, ": " + e.message)
}
}
super.onMeasure(widthMeasureSpec, newViewHeight)
}
fun measureCurrentView(view: View) {
this.currentView = view
requestLayout()
}
private fun convertDpToPixel(dp: Float): Float {
val metrics = context.resources.displayMetrics
return dp * (metrics.densityDpi / 160f)
}
}
package com.aqua30.learningproject
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.viewpager.widget.PagerAdapter
import com.bumptech.glide.Glide
class ImagePagerAdapter: PagerAdapter {
var imagesList: List<Int>? = null
var context: Context? = null
var currentPosition = -1
constructor(context: Context, imagesList: List<Int>): super() {
this.imagesList = imagesList
this.context = context
}
override fun getCount(): Int {
return imagesList?.size!!
}
override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view == `object`
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val view = LayoutInflater.from(context).inflate(R.layout.view_item, container, false)
val imageView: ImageView = view.findViewById(R.id.iv_item)
container.addView(view)
Glide.with(context!!)
.load(imagesList?.get(position))
.into(imageView)
return view
}
override fun setPrimaryItem(container: ViewGroup, position: Int, `object`: Any) {
super.setPrimaryItem(container, position, `object`)
if (container !is AdjustingViewPager)
return
if (position != currentPosition) {
val viewPager: AdjustingViewPager = container
currentPosition = position;
viewPager.measureCurrentView(`object` as View)
}
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
container.removeView(`object` as View)
}
}
package com.aqua30.learningproject
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
lateinit var viewPager : AdjustingViewPager
lateinit var imageAdapter: ImagePagerAdapter
private val imagesList: List<Int> = listOf(
R.drawable.img_1,
R.drawable.img_2,
R.drawable.img_3
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewPager = findViewById(R.id.view_pager)
imageAdapter = ImagePagerAdapter(this, imagesList)
viewPager.adapter = imageAdapter
}
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="430dp">
<ImageView
android:id="@+id/iv_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</FrameLayout>
@kryanod
Copy link

kryanod commented May 15, 2021

This code has clearly been auto-migrated from Java and has some issues:

  1. AdjustingViewPager:28 - currentView cannot be null
  2. the name object in ImagePagerAdapter
  3. nullable vars in ImagePagerAdapter
  4. isViewFromObject should compare the two things with ===

@aqua30
Copy link
Author

aqua30 commented May 15, 2021

@kryanod This is not auto-migrated code from java. I wrote it in kotlin.
Let me know what issues you're facing.

For the point 1, are you calling viewPager.measureCurrentView(object as View) because otherwise you'll get currentView cannot be null

For point 2, as I'm learning kotlin so i used the defaults.

For point 3, please provide exact error you get.

@kryanod
Copy link

kryanod commented May 15, 2021

Ah OK sorry, I misunderstood the whole thing then. Let me clarify my points though.
1 - your property is a lateinit var and has a type of View, i.e. it can never be null. This means that the null check at AdjustingViewPager:28 can never return true
3 - It's just a convenience thing. You can declare the whole class as

class ImagePagerAdapter(private val context: Context, private val imagesList: List<Int>)

and get rid of an extra constructor and the first two vars

@aqua30
Copy link
Author

aqua30 commented May 15, 2021

Agreed @kryanod. Will update this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment