ภาพรวม
HorizontalScrollView เป็น ViewGroup ที่เลื่อนเนื้อหาในแนวนอน (Horizontal) ใช้แสดงรายการภาพ, Card, หรือ Item แบบ Carousel/Gallery เหมาะสำหรับรายการที่ไม่คงที่หรือไม่ต้องการ RecyclerView
ขั้นตอนการสร้าง
1. Layout (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Horizontal Gallery Demo"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginBottom="16dp" />
<HorizontalScrollView
android:id="@+id/horizontalScrollView"
android:layout_width="match_parent"
android:layout_height="200dp"
android:scrollbars="none"
android:fillViewport="false"
android:overScrollMode="never">
<LinearLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<!-- Item Views จะเพิ่มด้วยโค้ด หรือ Hardcode ได้ -->
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
2. Item Layout (res/layout/item_image.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="150dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:layout_marginEnd="12dp"
android:background="@drawable/item_background"
android:padding="8dp">
<ImageView
android:id="@+id/ivImage"
android:layout_width="120dp"
android:layout_height="120dp"
android:scaleType="centerCrop"
android:contentDescription="@string/image_desc" />
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="12sp"
android:maxLines="1"
android:ellipsize="end"
android:gravity="center" />
</LinearLayout>
3. Drawable Background (res/drawable/item_background.xml)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="https://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF" />
<corners android:radius="12dp" />
<stroke android:width="1dp" android:color="#E0E0E0" />
<shadow android:color="#80000000" android:radius="4dp" android:dx="0" android:dy="2" />
</shape>
4. MainActivity.java
package com.example.myapplication;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Arrays;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private LinearLayout container;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
container = findViewById(R.id.container);
// ข้อมูลตัวอย่าง
List- items = Arrays.asList(
new Item(R.drawable.img1, "ภาพที่ 1"),
new Item(R.drawable.img2, "ภาพที่ 2"),
new Item(R.drawable.img3, "ภาพที่ 3"),
new Item(R.drawable.img4, "ภาพที่ 4"),
new Item(R.drawable.img5, "ภาพที่ 5"),
new Item(R.drawable.img6, "ภาพที่ 6")
);
// Inflate และเพิ่ม Item เข้า Container
LayoutInflater inflater = LayoutInflater.from(this);
for (Item item : items) {
View itemView = inflater.inflate(R.layout.item_image, container, false);
ImageView iv = itemView.findViewById(R.id.ivImage);
TextView tv = itemView.findViewById(R.id.tvTitle);
iv.setImageResource(item.imageRes);
tv.setText(item.title);
itemView.setOnClickListener(v ->
Toast.makeText(this, "คลิก: " + item.title, Toast.LENGTH_SHORT).show()
);
container.addView(itemView);
}
}
static class Item {
int imageRes;
String title;
Item(int imageRes, String title) {
this.imageRes = imageRes;
this.title = title;
}
}
}
ปรับปรุง UX (Enhancements)
1. Snap to Center (Android 11+ / API 30+)
// ใน onCreate หลัง setContentView
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
horizontalScrollView.setOnScrollChangeListener((v, scrollX, scrollY, oldX, oldY) -> {
// Logic snap หรือใช้ RecyclerView แทน (แนะนำ)
});
}
แนะนำ: ใช้ **RecyclerView + LinearLayoutManager.HORIZONTAL + SnapHelper** แทน HorizontalScrollView สำหรับ Production
2. RecyclerView แทน HorizontalScrollView (Best Practice)
// Layout
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="200dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
// Activity
RecyclerView rv = findViewById(R.id.recyclerView);
rv.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
rv.setAdapter(new MyAdapter(items));
// Snap to center
SnapHelper snapHelper = new PagerSnapHelper(); // หรือ LinearSnapHelper
snapHelper.attachToRecyclerView(rv);
3. เพิ่ม Gradient Fade ที่ขอบ (Edge Fade Effect)
<HorizontalScrollView
...
android:requiresFadingEdge="horizontal"
android:fadingEdgeLength="40dp"
android:scrollbars="none" />
เปรียบเทียบ: HorizontalScrollView vs RecyclerView Horizontal
| คุณสมบัติ | HorizontalScrollView | RecyclerView (Horizontal) |
|---|---|---|
| View Recycling | ไม่มี (Inflate ทั้งหมด) | มี (ViewHolder Pattern) |
| Memory (Item เยอะ) | สูง (OOM Risk) | ต่ำคงที่ |
| Snap/Fling Physics | Manual/ไม่มี | SnapHelper, PagerSnapHelper |
| Adapter Pattern | ไม่มี | มี (Adapter, DiffUtil) |
| Nested Scrolling | ยาก | รองรับดี |
| ใช้งานเหมาะกับ | Item น้อย (<10), Static | Item เยอะ, Dynamic, List |
สรุป
HorizontalScrollView ใช้งานง่ายสำหรับ **Item น้อย (<10-15), Static Content** แต่ **ไม่มี View Recycling** → เสี่ยง OOM หาก Item เยอะ สำหรับ Production/Gallery จริง ให้ใช้ **RecyclerView + LinearLayoutManager.HORIZONTAL + SnapHelper** ซึ่งเป็นมาตรฐานปัจจุบัน ประสิทธิภาพดี รองรับ Animation, DiffUtil, Paging ได้ครบครัน