Androidアプリ開発でのこと
画像をこんな感じで表示したいです。
- 大量の写真・画像をグリッド表示したい
- スクロール時に写真・画像読み込み
- スムーズに動く快適性も欲しい
そんな時に便利なライブラリを見つけました。
その名も RecyclerView というもの
負荷なく大量のアイテム表示ができて便利なので、
このRecyclerViewによる画像写真表示の実装をまとめます。
※ Android Studioでの手順
このページの目次
1.RecyclerViewをbuild.gradleから導入する
初めにRecyclerViewライブラリを導入します。
▼ モジュールレベルのbuild.gradleを開く
▼ dependenciesブロック内に追加
1 2 |
/// RecyclerView implementation 'androidx.recyclerview:recyclerview:1.2.1' |
最新バージョンはv1.2.1
追加したら[Sync Now]を押して同期です。
2.画像写真表示用のアイテムViewレイアウトを作成
そしたら次のアイテムのレイアウト作成です。
RecyclerView内で画像を大量表示するのですが、
その1つ1つのアイテムのレイアウトを作ります。
名前は item_view_image_text.xml としました。
▼ そのレイアウトXMLの内容がこちら
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<?xml version="1.0" encoding="utf-8"?> <FrameLayout 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:gravity="center" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <ImageView android:id="@+id/image_view" android:layout_width="match_parent" android:layout_height="wrap_content" app:srcCompat="@drawable/ic_baseline_frame_viewer_24" android:scaleType="centerCrop" /> <TextView android:id="@+id/text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right" android:background="#a000" android:gravity="center" android:paddingLeft="4dp" android:paddingTop="2dp" android:paddingRight="4dp" android:paddingBottom="2dp" android:text="" android:textColor="#fff" android:visibility="visible" /> </FrameLayout> |
このレイアウトは次の2つで構成されます。
- 画像ビュー (@+id/image_view)
画像を表示するためのビュー。画像がグリッドに収まるように android:layout_width="match_parent" を指定している。具体的な幅・高さ調整はコードで行う(後述)
- テキストビュー (@+id/text_view)
もし画像だけを表示したいならテキストビューはいらない。ここでは画像の左下にオーバーレイとして重なるように配置した。
必要に応じてレイアウトは変えてください。
ただし後述するようにレイアウトを変えたら、RecyclerView.Adapter を継承したクラスでも色々修正が必要です。この意味は次から徐々に判明していきます。
ひとまずアイテムレイアウトは完成
3.RecyclerView.Adapterを継承したAdapter作成
ここからがRecyclerViewで肝心なところです。
RecyclerView.Adapterを継承したAdapterクラス作成
ここでは ImageTextItemAdapter というクラスにします。
▼ 作成したImageTextItemAdapterクラスの内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
class ImageTextItemAdapter( private val context: Context, private val dataSet: Array<JSONObject> ) : RecyclerView.Adapter<ImageTextItemAdapter.ViewHolder>() { private var screenWidth: Int = 0 private var screenHeight: Int = 0 init { val displayManager = (context as Activity).getSystemService<DisplayManager>() ?.getDisplay(Display.DEFAULT_DISPLAY) val displayContext = context.createDisplayContext(displayManager!!) screenWidth = displayContext.resources.displayMetrics.widthPixels screenHeight = displayContext.resources.displayMetrics.heightPixels } class ViewHolder(view: View): RecyclerView.ViewHolder(view) { val imageView: ImageView = view.findViewById(R.id.image_view) val textView: TextView = view.findViewById(R.id.text_view) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) val view = layoutInflater.inflate(R.layout.item_view_image_text, parent, false) return ViewHolder(view) } override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { val item = dataSet[position] val imagePath = item.getString("itemImagePath") val text = item.getString("itemText") val bitmap = BitmapFactory.decodeFile(imagePath) val dstWdth = screenWidth/2 val dstHeight = bitmap.height*dstWdth/bitmap.width val thumbBitmap = Bitmap.createScaledBitmap(bitmap, dstWdth, dstHeight, false) bitmap.recycle() viewHolder.imageView.setImageBitmap(thumbBitmap) viewHolder.textView.text = text } override fun getItemCount() = dataSet.size } |
枝葉の部分はあまり気にしないでください。
重要なのは以下のオーバライドメソッドです。
- onCreateViewHolder()
新しいViewHolderが必要な度に呼び出されるメソッド。ここでは先ほどのXMLレイアウトからRecyclerViewのアイテムを生成している
- onBindViewHolder()
外部から渡されるデータをViewHolderを結びつける(Bindする)ためのメソッド。ここでは渡されたJSONObjectから画像パス・テキストを受け取り、それぞれをビューに設定している
- getItemCount()
表示されているRecyclerView内のデータ数(=アイテム数)を取得するメソッド。もし後からアイテム追加・削除するなら工夫が必要。そこは各自で考えて
この3つだけオーバライドすればOKです。
4.ActivityなどでRecyclerViewを設定・表示
そしたらActivityでRecyclerViewを表示します。
※ ただしレイアウトにRecyclerView追加済みの前提
ここでは次のようなコードを書いてみました。
▼ RecyclerView設定・表示のコード例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class MainActivity : AppCompatActivity() { private lateinit var imageRecyclerView: RecyclerView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) /// レイアウトからRecyclerView取得 imageRecyclerView = findViewById(R.id.image_recycler_view) /// ここは各自の環境で置き換え!! /// RecyclerViewのデータセット作成 /// アプリフォルダの画像を一覧表示する var dataSet = arrayOf<JSONObject>() val imagesDir = File(getExternalFilesDir( Environment.DIRECTORY_PICTURES ), "images") val imageCount = imagesDir.listFiles().size for (i in 1..frameCount){ /// アイテムデータをJSONObjectで追加 dataSet += JSONObject("""{ "itemImagePath": "${imagesDir.absolutePath}/${i}.png", "itemText": "${i}" }""".trimMargin()) } // カスタムAdapterの設定 val adapter = ImageTextItemAdapter(this, dataSet) imageRecyclerView.adapter = adapter // 画像を1行2列のグリッドで表示 val layoutManager = StaggeredGridLayoutManager( 2, StaggeredGridLayoutManager.VERTICAL) imageRecyclerView.layoutManager = layoutManager } } |
ただしデータの用意は各自の目的ごとに違います。
各自のAdapterにあったデータセットを用意してください。
これでRecyclerViewで大量画像をグリッド表示できました。
5.実際にRecyclerViewを表示して動かした様子
見た目としてはこんな感じになりました。
▼ RecyclerViewで大量の画像をグリッド表示
RecyclerViewは表示されないビューはリサイクルされるので、表示が必要になる度に画像を読み込んでくれます。文字通りリサイクルできるビューという訳です。
そういうスマートな仕組みだから画像が大量でも重くならないし、メモリ不足でOutOfMemoryになる心配も恐らくありません。
RecyclerViewによる大量写真表示のまとめ
ここまでの手順を箇条書きでまとめ
- RecyclerViewをbuild.gradleに追加
- 画像表示用のアイテムレイアウト作成
- RecyclerView.Adapterを継承したAdapter作成
- ActivityなどでRecyclerViewを設定・表示
大量の写真・画像をメモリの心配なく表示できます。
以上、RecyclerViewで大量写真表示でした。ではまた