Custom ListView ใน Android: รูปภาพ + ข้อความ + Custom Adapter
หมายเหตุ: เนื้อหาบทความนี้ซ้ำกับ Article ID 1004060804 เกือบทั้งหมด นี่คือเวอร์ชันที่ปรับปรุงและเพิ่มรายละเอียด
ขั้นตอนที่ 1: Main Layout (activity_main.xml)
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#000"
android:dividerHeight="1dp" />
ขั้นตอนที่ 2: Row Layout (list_row.xml)
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:id="@+id/list_image"
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="centerCrop"
android:contentDescription="@string/app_name" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_gravity="center_vertical"
android:textSize="18sp"
android:textColor="@android:color/black" />
</LinearLayout>
ขั้นตอนที่ 3: Data Model
public class SubjectData {
public final String subjectName;
public final String link;
public final String imageUrl;
public SubjectData(String subjectName, String link, String imageUrl) {
this.subjectName = subjectName;
this.link = link;
this.imageUrl = imageUrl;
}
}
ขั้นตอนที่ 4: Custom Adapter (Implements ListAdapter)
public class CustomAdapter implements ListAdapter {
private final Context context;
private final List<SubjectData> items;
public CustomAdapter(Context context, List<SubjectData> items) {
this.context = context;
this.items = items;
}
@Override public boolean areAllItemsEnabled() { return true; }
@Override public boolean isEnabled(int position) { return true; }
@Override public void registerDataSetObserver(DataSetObserver observer) {}
@Override public void unregisterDataSetObserver(DataSetObserver observer) {}
@Override public int getCount() { return items.size(); }
@Override public Object getItem(int position) { return items.get(position); }
@Override public long getItemId(int position) { return position; }
@Override public boolean hasStableIds() { return false; }
@Override public int getViewTypeCount() { return 1; }
@Override public int getItemViewType(int position) { return 0; }
@Override public boolean isEmpty() { return items.isEmpty(); }
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.list_row, parent, false);
holder = new ViewHolder();
holder.title = convertView.findViewById(R.id.title);
holder.image = convertView.findViewById(R.id.list_image);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
SubjectData data = items.get(position);
holder.title.setText(data.subjectName);
Picasso.get().load(data.imageUrl).placeholder(R.drawable.ic_placeholder).into(holder.image);
return convertView;
}
static class ViewHolder {
TextView title;
ImageView image;
}
}
ขั้นตอนที่ 5: MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = findViewById(R.id.list);
List<SubjectData> items = Arrays.asList(
new SubjectData("JAVA", "https://tutorialspoint.com/java/", "https://tutorialspoint.com/java/images/java-mini-logo.jpg"),
new SubjectData("Python", "https://tutorialspoint.com/python/", "https://tutorialspoint.com/python/images/python-mini.jpg"),
new SubjectData("JavaScript", "https://tutorialspoint.com/javascript/", "https://tutorialspoint.com/javascript/images/javascript-mini-logo.jpg"),
new SubjectData("C Programming", "https://tutorialspoint.com/cprogramming/", "https://tutorialspoint.com/cprogramming/images/c-mini-logo.jpg"),
new SubjectData("C++", "https://tutorialspoint.com/cplusplus/", "https://tutorialspoint.com/cplusplus/images/cpp-mini-logo.jpg"),
new SubjectData("Android", "https://tutorialspoint.com/android/", "https://tutorialspoint.com/android/images/android-mini-logo.jpg")
);
CustomAdapter adapter = new CustomAdapter(this, items);
listView.setAdapter(adapter);
listView.setOnItemClickListener((parent, view, position, id) -> {
SubjectData data = (SubjectData) parent.getItemAtPosition(position);
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(data.link));
startActivity(intent);
});
}
}
Dependencies & Permissions
// build.gradle
dependencies {
implementation "com.squareup.picasso:picasso:2.71828"
}
// AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
🔄 Modern Replacement: RecyclerView + ListAdapter (DiffUtil)
public class SubjectAdapter extends ListAdapter<SubjectData, SubjectAdapter.ViewHolder> {
public SubjectAdapter() {
super(DIFF_CALLBACK);
}
private static final DiffUtil.ItemCallback<SubjectData> DIFF_CALLBACK =
new DiffUtil.ItemCallback<SubjectData>() {
@Override public boolean areItemsTheSame(@NonNull SubjectData old, @NonNull SubjectData n) {
return old.link.equals(n.link);
}
@Override public boolean areContentsTheSame(@NonNull SubjectData old, @NonNull SubjectData n) {
return old.subjectName.equals(n.subjectName) && old.imageUrl.equals(n.imageUrl);
}
};
@NonNull @Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_row, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
SubjectData data = getItem(position);
holder.bind(data);
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView title; ImageView image;
ViewHolder(View v) { super(v); title = v.findViewById(R.id.title); image = v.findViewById(R.id.list_image); }
void bind(SubjectData data) {
title.setText(data.subjectName);
Picasso.get().load(data.imageUrl).into(image);
}
}
}
ข้อดีของ RecyclerView + ListAdapter
- ✅ DiffUtil: Animate Changes (Insert/Delete/Move) อัตโนมัติ
- ✅ ViewHolder Pattern บังคับ (Performance)
- ✅ LayoutManager แยกจาก Adapter (Linear, Grid, Staggered)
- ✅ ItemDecoration, ItemAnimator Built-in
- ✅ Paging 3 / PagingSource Integration ง่าย