2

I want to add horizontal image scroller like an image attached below. I know there are solutions but all solutions are 4 to 5 years old and after Kotlin and constraint layout I am assuming that now there are some good methods to achieve this. Please help me out and share the best and easy way to do this. Thanks in advance. enter image description here

user9078185
  • 112
  • 6
SFAH
  • 624
  • 15
  • 35

2 Answers2

3

Create an Item:

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:id="@+id/card_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginBottom="16dp"
    android:background="@color/colorAccent"
    card_view:cardCornerRadius="8dp"
    card_view:cardUseCompatPadding="true">

    <LinearLayout
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:orientation="vertical">

        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/imageView"
            android:layout_width="match_parent"
            android:layout_height="150dp"
            android:scaleType="centerCrop"
            android:src="@mipmap/ic_launcher_round" />

        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:padding="8dp"
            android:text="@string/app_name"
            android:textColor="#000000"
            android:textSize="16sp" />

    </LinearLayout>

</androidx.cardview.widget.CardView>

Then add this item to your RecyclerView:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/colorPrimary" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="10dp" />

</LinearLayout>

Then call it from your Activity and set a HORIZONTAL LayoutManager to it and then its adapter:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        displayList()
    }

    private fun displayList() {
        val imageList = ArrayList<ImageDataModel>()
        imageList.clear()
        imageList.add(ImageDataModel("https://conversionxl.com/wp-content/uploads/2018/09/coding-language.jpg", "Test"))
        imageList.add(ImageDataModel("https://makeawebsitehub.com/wp-content/uploads/2016/02/learn-code-e1455713167295.jpg", "Test"))
        imageList.add(ImageDataModel("https://www.tecmint.com/wp-content/uploads/2016/11/Convert-PNG-to-JPG-Using-for-loop-Command.png", "Test"))
        imageList.add(ImageDataModel("https://conversionxl.com/wp-content/uploads/2018/09/coding-language.jpg", "Test"))
        imageList.add(ImageDataModel("https://www.tecmint.com/wp-content/uploads/2016/11/Convert-PNG-to-JPG-Using-for-loop-Command.png", "Test"))
        recyclerView.layoutManager = LinearLayoutManager(this, LinearLayout.HORIZONTAL, false)
        recyclerView.adapter = ViewAdapter(imageList)
    }
}

As you can see the RecyclerView needs an Adapter too, it's a simple Adapter which uses Glide to shown images like this:

class ViewAdapter(private val imageDataModelList: ArrayList<ImageDataModel>) : RecyclerView.Adapter<ViewAdapter.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false))
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bindItems(imageDataModelList[position])
    }

    override fun getItemCount(): Int {
        return imageDataModelList.size
    }

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bindItems(imageDataModel: ImageDataModel) {
            val textView = itemView.findViewById<TextView>(R.id.tvName)
            val imageView = itemView.findViewById<ImageView>(R.id.imageView)
            textView.text = imageDataModel.name

            Glide.with(itemView.context).load(imageDataModel.url).into(imageView)
        }
    }
}

Don't forget to add dependencies of Glide in your app-level build.gradle:

dependencies {
  implementation 'com.github.bumptech.glide:glide:4.10.0'
  annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
}

The app should look like this: https://i.stack.imgur.com/fpGuq.jpg

SaadAAkash
  • 3,065
  • 3
  • 19
  • 25
  • where you defined 'recylerView' ? – SFAH Oct 03 '19 at 10:58
  • 1
    @SFAH inside `MainActivity` inside the method `displayList()`, since it's in Kotlin I don't need to initialize like in Java, this class can access id `recyclerView` of the layout `activity_main` directly. – SaadAAkash Oct 03 '19 at 14:35
  • Learn more about how I used Kotlin Extensions by importing Kotlin Synthetics in the following official documentation & example: https://kotlinlang.org/docs/tutorials/android-plugin.html – SaadAAkash Oct 03 '19 at 14:45
  • One more question please , What is imageDataModelList? I can't used it .. – SFAH Oct 03 '19 at 15:12
  • Sure, go on, no problem, I'm here to help. In the `MainActivity`, inside `displayList()`, I have a `val imageList = ArrayList()`. Then I've passed this `ArrayList()` to Adapter in this line: `recyclerView.adapter = ViewAdapter(imageList)` – SaadAAkash Oct 03 '19 at 15:17
  • And then in the ViewAdapter, I have `class ViewAdapter(private val imageDataModelList: ArrayList)`, which means I've received that imageList as imageDataModelList here in the Adapter, I've just renamed while revceiving that's all. – SaadAAkash Oct 03 '19 at 15:18
  • Thankyou so much...BUt I cant understand this Datatype `ImageDataModel` What type of datatype this is? – SFAH Oct 03 '19 at 15:20
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/200354/discussion-between-saadaakash-and-sfah). – SaadAAkash Oct 03 '19 at 15:23
1

Not specifically for RecyclerViews but there are some nice Kotlin language features you can use.

I personally would not do this with a constraint layout but a RecyclerView. Kotlin offers a couple of nice options for working with collections so its worth using those if you need to do any manipulation. I would advice you to not make the list available as mutable outside the adapter. This can be achieved with the use of backing properties.

As for making them clickable you could use an OnItemTouchListener. However personally I prefer to use OnClickListener and a callback. The callback belongs to the adapter and the OnClickListener is added to the root view of the viewholder.

Besides this their are various nice tricks you can use like typealias.

Here is the code I could come up with quickly (I haven't done any non test app work with recyclerviews so don't have a properly worked out sample to draw on):

class ImageListAdapter : RecyclerView.Adapter<ImageListAdapter.ImageListItemVH>() {
    private val _items = mutableListOf<ImageItem>()
    val items: List<ImageItem>
        get() = _items

    var clickCallback: ImageClickCallback? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageListItemVH {
        val itemView = LayoutInflater.from(parent.context)
            .inflate(
                R.layout.viewholder_image_list_item,
                parent,
                false
            )
        return ImageListItemVH(itemView)
    }

    override fun getItemCount(): Int = _items.size

    override fun onBindViewHolder(holder: ImageListItemVH, position: Int) {
        holder.bind(_items[position]) { clickCallback?.invoke(it) }
    }

    fun addItem(newItem: ImageItem) {
        _items += newItem
        notifyItemInserted(_items.size - 1)
    }

    fun addItems(newItems: List<ImageItem>) {
        val sizeBefore = _items.size
        _items += newItems
        notifyItemRangeInserted(sizeBefore - 1, newItems.size)
    }

    fun clearItems() {
        val sizeBefore = _items.size
        _items.clear()
        notifyItemRangeRemoved(0, sizeBefore - 1)
    }

    fun setItems(items: List<ImageItem>) {
        _items.clear()
        _items += items
        notifyDataSetChanged()
    }

    class ImageListItemVH(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(imageItem: ImageItem, callback: (id: String) -> Unit) =
            itemView.setOnClickListener { callback(imageItem.id) }
    }
}

data class ImageItem(
    val id: String,
    val location: String
)

typealias ImageClickCallback = (id: String) -> Unit

If the image collection is either of a large size or not limited to a small amount of static items you should use the paging library, specifically in this case the PagedList