Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Tạo Horizontal ListView (RecyclerView ngang) trên Android

Giới thiệu

Bài viết này hướng dẫn cách tạo danh sách cuộn ngang (Horizontal ListView) trên Android sử dụng RecyclerView với LinearLayoutManager.HORIZONTAL. Đây là cách hiện đại, thay thế cho HorizontalListView cũ và Gallery (deprecated).

Các bước thực hiện

Bước 1: Tạo dự án mới & Thêm dependency

Mở Android Studio, chọn File → New Project. Thêm dependency vào build.gradle (Module: app):

dependencies {
    // RecyclerView & CardView (AndroidX)
    implementation 'androidx.recyclerview:recyclerview:1.3.2'
    implementation 'androidx.cardview:cardview:1.0.0'
    
    // Nếu dùng material design
    implementation 'com.google.android.material:material:1.12.0'
}

Bước 2: Layout chính (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="6dp"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" />

</RelativeLayout>

Bước 3: Layout item (res/layout/item_number.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:card_view="https://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <androidx.cardview.widget.CardView
        android:layout_width="160dp"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        card_view:cardCornerRadius="8dp"
        card_view:cardElevation="4dp">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="160dp">

            <ImageView
                android:id="@+id/imgThumbnail"
                android:layout_width="match_parent"
                android:layout_height="150dp"
                android:layout_above="@id/textView"
                android:layout_centerHorizontal="true"
                android:scaleType="centerCrop" />

            <TextView
                android:id="@+id/textView"
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:layout_alignParentBottom="true"
                android:background="#CC000000"
                android:gravity="center_vertical"
                android:paddingStart="8dp"
                android:paddingEnd="8dp"
                android:text="Test"
                android:textColor="#FFFFFF"
                android:textSize="16sp" />

        </RelativeLayout>
    </androidx.cardview.widget.CardView>

</LinearLayout>

Bước 4: Adapter (MyAdapter.java)

import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter {

    private final ArrayList numberName;
    private final ArrayList numberImage;
    private final Context context;
    private final OnItemClickListener clickListener;

    public interface OnItemClickListener {
        void onItemClick(int position, String name, boolean isLongClick);
    }

    public MyAdapter(Context context, ArrayList numberName, 
                     ArrayList numberImage, OnItemClickListener listener) {
        this.context = context;
        this.numberName = numberName;
        this.numberImage = numberImage;
        this.clickListener = listener;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_number, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.textView.setText(numberName.get(position));
        holder.imgThumbnail.setImageResource(numberImage.get(position));
        
        holder.itemView.setOnClickListener(v -> {
            if (clickListener != null) {
                clickListener.onItemClick(position, numberName.get(position), false);
            }
        });
        
        holder.itemView.setOnLongClickListener(v -> {
            if (clickListener != null) {
                clickListener.onItemClick(position, numberName.get(position), true);
                return true;
            }
            return false;
        });
    }

    @Override
    public int getItemCount() {
        return numberName.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        ImageView imgThumbnail;
        TextView textView;

        ViewHolder(View itemView) {
            super(itemView);
            imgThumbnail = itemView.findViewById(R.id.imgThumbnail);
            textView = itemView.findViewById(R.id.textView);
        }
    }
}

Bước 5: Activity chính (MainActivity.java)

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.Arrays;

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private MyAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Dữ liệu mẫu
        ArrayList numberName = new ArrayList<>(Arrays.asList(
            "Four", "Nine", "Seven", "Six", "Ten", "Three", "Two"
        ));
        ArrayList numberImage = new ArrayList<>(Arrays.asList(
            R.drawable.four, R.drawable.nine, R.drawable.seven,
            R.drawable.six, R.drawable.ten, R.drawable.three, R.drawable.two
        ));

        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setHasFixedSize(true);

        // QUAN TRỌNG: LinearLayoutManager.HORIZONTAL
        LinearLayoutManager layoutManager = new LinearLayoutManager(
            this, LinearLayoutManager.HORIZONTAL, false
        );
        recyclerView.setLayoutManager(layoutManager);

        adapter = new MyAdapter(this, numberName, numberImage, (position, name, isLongClick) -> {
            if (isLongClick) {
                Toast.makeText(this, "#" + position + " - " + name + " (Long click)", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "#" + position + " - " + name, Toast.LENGTH_SHORT).show();
            }
        });
        
        recyclerView.setAdapter(adapter);
        
        // Optional: Snap helper để căn giữa item khi cuộn
        // new PagerSnapHelper().attachToRecyclerView(recyclerView);
    }
}

Bước 6: Cấu hình Manifest (androidManifest.xml)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.sample">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                
                
            </intent-filter>
        </activity>

    </application>
</manifest>

Chạy ứng dụng

Chuẩn bị 7 hình ảnh (four, nine, seven, six, ten, three, two) trong res/drawable/. Kết nối thiết bị, nhấn Run. Cuộn ngang để xem danh sách.

Mở rộng nâng cao

1. Snap to center (PagerSnapHelper)

// Item sẽ tự động snap vào giữa màn hình khi cuộn dừng
new PagerSnapHelper().attachToRecyclerView(recyclerView);

2. Carousel effect (vòng tròn)

// Override getItemCount trả về Integer.MAX_VALUE
// Trong onBindViewHolder: position % actualSize

3. StaggeredGridLayoutManager ngang

StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(
    2, StaggeredGridLayoutManager.HORIZONTAL
);

4. ItemDecoration cho khoảng cách

recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
                               @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        outRect.left = 8;
        outRect.right = 8;
        if (parent.getChildAdapterPosition(view) == 0) outRect.left = 16;
        if (parent.getChildAdapterPosition(view) == state.getItemCount() - 1) outRect.right = 16;
    }
});

So sánh: RecyclerView ngang vs ViewPager2

Đặc điểm RecyclerView HORIZONTAL ViewPager2
Cuộn tự do (free scroll) ✅ Có ❌ Chỉ trang (page)
Snap to page Cần PagerSnapHelper Mặc định
Hiển thị một phần item tiếp theo ✅ Dễ dàng (peek) Khó hơn
Phù hợp cho Danh sách ngang, chips, categories, carousel Onboarding, tabs, full-screen pages

Lưu ý

  • Luôn set setHasFixedSize(true) nếu kích thước item không đổi để tối ưu hiệu năng.
  • Dùng wrap_content cho width RecyclerView nếu muốn nó chỉ bao vừa nội dung.
  • Nhớ thêm hình ảnh vào drawable trước khi chạy.
  • Xử lý click/long click qua interface callback (như ví dụ) thay vì setOnClickListener trong ViewHolder trực tiếp.