Expect
The RecyclerView's cell layout of type ConstraintLayout to expand to the display's width and rendered content's height.
post_cell.xml
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/postImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:contentDescription="@string/post_image_alt"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="@string/feed_image_ratio"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/backgrounds/scenic" />
...
This layout implementation works as expected for the post_cell.xml in the CameraXFeed open-source app.
Observe
Using the same RecyclerView cell layout above, does not expand to the parent view display's width and rendered content's height in the code outlined below. The RecyclerView displays as empty despite the data being bound in the ViewHolder.
The issue is isolated to the layout, as the log below in FeedAdapter.kt displays the expected photo image URLs in bind
of the ViewHolder.
Implement
build.gradle
dependencies {
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.cardview:cardview:1.0.0"
}
The layout design is the same as in the Expect section.
photo_cell.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="@dimen/card_elevation"
android:focusable="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:translationZ="0dp">
<ImageView
android:id="@+id/postImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:contentDescription="@string/photo_image_alt"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="@string/feed_image_ratio"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/backgrounds/scenic" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
The Android Studio Design preview confirms the layout should work as described in the Expect section.
fragment_feed.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.FeedFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
FeedAdapter.kt
val DIFF_UTIL = object : DiffUtil.ItemCallback<Photo>() {
override fun areItemsTheSame(oldItem: Photo, newItem: Photo) = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Photo, newItem: Photo) = oldItem == newItem
}
class FeedAdapter : PagedListAdapter<Photo, FeedAdapter.ViewHolder>(DIFF_UTIL) {
inner class ViewHolder(private val binding: PhotoCellBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(photo: Photo) {
// Logs valid valid URLs as expected from 'photo.largeImageURL'.
Log.v(LOG_TAG, "Debug photo ${photo.largeImageURL}")
binding.postImage.setPostImage(photo.largeImageURL)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ViewHolder(PhotoCellBinding.inflate(inflater, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
getItem(position)?.let { holder.bind(it) }
}
}
Glide.kt
fun ImageView.setPostImage(image: String) {
Glide.with(context)
.load(image)
.placeholder(R.drawable.ic_content_placeholder)
.error(R.drawable.ic_error)
.into(this)
}
Attempt Solution
- Hard code the parent root CardView
layout_height
in the photo_cell.xml RecyclerView item layout and set ConstraintLayout toandroid:layout_height="match_parent"
instead ofandroid:layout_height="wrap_content"
.
This is not ideal as it requires specifying the dimensions for different devices as opposed to the layout rendering based upon the image height dynamically with wrap_content
.
photo_cell.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="500dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:translationZ="0dp">
<ImageView
android:id="@+id/postImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:contentDescription="@string/photo_image_alt"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="@string/feed_image_ratio"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/backgrounds/scenic" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
- Programmatically set the layout width and height.
This is not ideal because all of the attributes defined in the XML layout are overwritten.
FeedAdapter.kt
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = PhotoCellBinding.inflate(inflater, parent, false)
binding.card.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
// Sets the height to the display width to create a 1:1 aspect ratio.
Resources.getSystem().displayMetrics.widthPixels
)
return ViewHolder(binding)
}