Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธีสร้าง Horizontal ListView ใน Android ด้วย RecyclerView

ภาพรวม

บทความนี้สาธิตวิธาสร้างรายการแนวนอน (Horizontal List) ใน Android โดยใช้ RecyclerView พร้อม LinearLayoutManager.HORIZONTAL ซึ่งเป็นวิธีมาตรฐานปัจจุบัน แทน HorizontalListView (Deprecated) และ ListView ที่ไม่รองรับ Horizontal Scrolling ได้ดี

ขั้นตอนที่ 1: สร้างโปรเจกต์และเพิ่ม Dependencies

ใน build.gradle (Module: app):

dependencies {
    // RecyclerView & CardView (AndroidX)
    implementation "androidx.recyclerview:recyclerview:1.3.2"
    implementation "androidx.cardview:cardview:1.0.0"
    
    // หรือ Material Components (รวม RecyclerView ไปแล้ว)
    implementation "com.google.android.material:material:1.11.0"
}

ขั้นตอนที่ 2: Layout หลัก (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>

ขั้นตอนที่ 3: Item Layout (res/layout/item_number.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 
    xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:card_view="https://schemas.android.com/apk/res-auto"
    android:layout_width="160dp"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    card_view:cardCornerRadius="8dp"
    card_view:cardElevation="4dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="8dp">

        <ImageView
            android:id="@+id/imgThumbnail"
            android:layout_width="match_parent"
            android:layout_height="120dp"
            android:scaleType="centerCrop"
            android:src="@drawable/placeholder" />

        <TextView
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:gravity="center"
            android:text="Number"
            android:textColor="#333"
            android:textSize="16sp"
            android:textStyle="bold" />

    </LinearLayout>

</androidx.cardview.widget.CardView>

ขั้นตอนที่ 4: Adapter Class (MyAdapter.java)

import android.content.Context;
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.List;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

    private final List<String> numberNames;
    private final List<Integer> numberImages;
    private final Context context;
    private final OnItemClickListener listener;

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

    public MyAdapter(Context context, List<String> numberNames, List<Integer> numberImages, OnItemClickListener listener) {
        this.context = context;
        this.numberNames = numberNames;
        this.numberImages = numberImages;
        this.listener = 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(numberNames.get(position));
        holder.imgThumbnail.setImageResource(numberImages.get(position));
        
        holder.itemView.setOnClickListener(v -> {
            if (listener != null) listener.onItemClick(position, numberNames.get(position), false);
        });
        
        holder.itemView.setOnLongClickListener(v -> {
            if (listener != null) listener.onItemClick(position, numberNames.get(position), true);
            return true;
        });
    }

    @Override
    public int getItemCount() {
        return numberNames.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);
        }
    }
}

ขั้นตอนที่ 5: 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;
import java.util.List;

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);

        // ข้อมูลตัวอย่าง
        List<String> numberNames = Arrays.asList("One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten");
        List<Integer> numberImages = Arrays.asList(
            R.drawable.one, R.drawable.two, R.drawable.three, R.drawable.four, R.drawable.five,
            R.drawable.six, R.drawable.seven, R.drawable.eight, R.drawable.nine, R.drawable.ten
        );

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

        // จุดสำคัญ: LinearLayoutManager.HORIZONTAL
        LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
        recyclerView.setLayoutManager(layoutManager);

        adapter = new MyAdapter(this, numberNames, numberImages, (position, name, isLongClick) -> {
            String msg = "#" + position + " - " + name + (isLongClick ? " (Long Click)" : "");
            Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
        });
        
        recyclerView.setAdapter(adapter);
    }
}

ขั้นตอนที่ 6: 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>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

ฟีเจอร์เพิ่มเติม (Enhancements)

1. Snap Helper - จับ Item ให้อยู่ตรงกลาง

import androidx.recyclerview.widget.LinearSnapHelper;
import androidx.recyclerview.widget.PagerSnapHelper;

// จับให้ Item อยู่ตรงกลาง (Center Lock)
new LinearSnapHelper().attachToRecyclerView(recyclerView);

// หรือแบบ Pager (เลื่อนทีละหน้า)
new PagerSnapHelper().attachToRecyclerView(recyclerView);

2. Item Decoration - Spacing ระหว่าง Item

recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, 
                               @NonNull RecyclerView parent, @NonNull State state) {
        outRect.set(16, 8, 16, 8); // left, top, right, bottom
    }
});

3. Horizontal Scroll Indicator

// แสดง Scrollbar แนวนอน
recyclerView.setHorizontalScrollBarEnabled(true);
recyclerView.setHorizontalScrollbarThumbDrawable(
    ContextCompat.getDrawable(this, R.drawable.scrollbar_thumb));
recyclerView.setHorizontalScrollbarTrackDrawable(
    ContextCompat.getDrawable(this, R.drawable.scrollbar_track));

4. RTL Support (ภาษาไทย/อาหรับ)

// ใน Manifest: android:supportsRtl="true"
// RecyclerView จะ Reverse เองอัตโนมัติ
// หรือบังคับ:
layoutManager.setReverseLayout(true);
layoutManager.setStackFromEnd(true);

ตัวเลือกอื่น: HorizontalScrollView (Simple Case)

สำหรับรายการน้อยและ Static ใช้ HorizontalScrollView + LinearLayout ได้:

<HorizontalScrollView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:scrollbars="none">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="16dp">

        <!-- Items ที่นี่ -->

    </LinearLayout>
</HorizontalScrollView>

สรุป

  • RecyclerView + LinearLayoutManager.HORIZONTAL = มาตรฐาน ปัจจุบัน รองรับ ViewHolder, Animation, DiffUtil
  • HorizontalScrollView = ใช้ได้เฉพาะรายการน้อย Static ไม่มี Recycling
  • ViewPager2 = สำหรับ Full-screen Pages (Swipe ทีละหน้า)
  • SnapHelper = เพิ่ม UX ให้ Item ตรงกลางอัตโนมัติ