RecyclerViewで「画像ファイルをグリッド表示」する方法を、まとめます。
スマートフォンのアプリを作っていると、何度も遭遇するテクニックです。
※環境:Android Studio Ladybug | 2024.2.1 Patch 1
Kotlin 2.0.0
Compose Compilerプラグイン 2.0.0
androidx.recyclerview:recyclerview:1.1.0
画像ファイルをグリッド表示
RecyclerView+GridLayoutManagerを用います。
※RecyclerViewの詳細は「RecyclerViewの実装」を参照
サンプルの仕様
サンプルは以下の仕様で作成しています。開発するアプリに従い、置き換えてください。
- assetsフォルダに保存されたJpegファイルが対象
- 画像サイズは4000×3000
- グリッド表示の積み上げ方向は縦
- グリッド表示の列数は4
データの準備と入力
通常、画像ファイルの保存先は、アプリが管理するフォルダになります。ここでは安易にassetsフォルダを用いています。

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_photo_list)
...
val _folder = "4000x3000"
val _row = 4
val _photos = assets.list(_folder)
val _items = List(100) { PhotoData(it, "${_folder}/${_photos!![it]}") }
val _adapter = PhotoAdapter(_items)
val _manager = GridLayoutManager(this, _row)
val _photoList = findViewById<RecyclerView>(R.id.rcyPhotoList)
_photoList.adapter = _adapter
_photoList.layoutManager = _manager
}
data class PhotoData(
val id: Int = 0,
val filename: String = "default"
)
グリッド表示のポイント
GridLayoutManagerがアイテムを格子状に並べてくれます。
ただし、画像を隙間なく敷き詰めるためには、レイアウト(photo.item.xml)のトップにConstraintLayoutを用いる事と、ハイライトで示したポイントが必要です。
ImageViewの横縦アスペクト比を「4:3(長方形)」にしています。正方形にしたければ「1:1」に変えてください。。
<?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="wrap_content"
android:padding="1dp">
<ImageView
android:id="@+id/imgPhoto"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="4:3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@android:drawable/ic_menu_gallery" />
</androidx.constraintlayout.widget.ConstraintLayout>
画像ファイルの扱い
表示の対象はJpegファイルです。
JPEGは不可逆の画像圧縮データなので、JPEG(データ)⇒Bitmap(画像)へ変換するデコード処理が必要になります。
この処理をonBindViewHolder内で行っています。
class PhotoAdapter(val items: List<PhotoData>)
: RecyclerView.Adapter<PhotoAdapter0.PhotoViewHolder>() {
// ----- ビューホルダー ------------------------------------------
class PhotoViewHolder(val view: View)
: RecyclerView.ViewHolder(view) {
val photo = view.findViewById<ImageView>(R.id.imgPhoto)
}
// ----- アダプター本体 ------------------------------------------
override fun getItemCount(): Int { return items.size }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
: PhotoViewHolder {
val _view = LayoutInflater.from(parent.context)
.inflate(R.layout.photo_item, parent, false)
return PhotoViewHolder(_view)
}
override fun onBindViewHolder(holder: PhotoViewHolder, position: Int) {
val _filename = items[position].filename
val _iStream = holder.view.context.assets.open(_filename)
val _bitmap = BitmapFactory.decodeStream(_iStream)
holder.photo.setImageBitmap(_bitmap)
}
}
サンプルの結果
サンプルを実行した結果、グリッド表示は以下のようになります。


スクロールに問題⇒出来ない・遅い
グリッド表示はできましたが、このサンプルは問題があります。スムーズなスクロールが出来ません。
スクロールを試みると、膨大なフレームのスキップを引き起こし、最後にANRとなってアプリが落ちます。
Skipped 129 frames! The application may be doing too much work on its main thread. Skipped 139 frames! The application may be doing too much work on its main thread. ANR in com.example.res.recyclerview (com.example.res.recyclerview/.PhotoListActivity)
この原因は次に上げる通りです。
- (1)画像サイズが大きく、ファイルアクセスに時間がかかる
- (2)画像サイズが大きく、JPEG⇒Bitmap変換に時間がかかる
- (3)上記により、Mainスレッドが重くなり、フレーム処理が間に合わない
サンプルの実行に用いたエミュレータ―は、メモリーを増強(RAM:1024→4096MB)しています。それでも、市販のデバイスに比べて非力なために、問題の発生が顕著です。

また、これよりも非力にすると、グリッド表示の直後にANRが発生します。
ANRが発生すると、Androidは「待機するか・アプリを終了するか」を選択するダイアログを開き、ユーザーに指示を求めます。
場合によっては、ダイアログを開く前に、アプリが落ちてしまう場合もあります。
※スムーズなスクロールを実現する方法は「RecyclerView:グリッド表示をスムーズにスクロール」を参照。
関連記事:
