Android Code : Multiple layout listView การทำ layout หลายๆแบบใน ListView
บทความนี้ผมจะบันทึก เกี่ยวกับการทำ layout ใน listView หลายๆแบบ
ถายังไม่เคยทำเกี่ยวกับ custom view แนะนำให้อ่านบทความเรื่อง CustomViewGroup และ CustomViewGroup on ListView ก่อนนะ
Android Code : ListView และ Custom Groupview บน Android เบื้องต้น
รู้จักกับ Multiple layout ListView
ปกติแล้วใน List view จะประกอบไปด้วยข้อมูลลักษณะคล้ายๆ รูปแบบแถว ซึ่งพบเจอได้บ่อยมาก แต่ว่ารูปแบบของแถวก็ไม่จำเป็นต้องมีเพียงลักษณะเดียว อาจจะมีหลายๆแบบ ที่เจอบ่อยๆคือ แอปแชท เช่น LINE , Messager
มีแถวของข้อความ แถวของรูปสติกเกอร์ เป็นต้น
เราจะลองทำ ListView ให้รองรับ แบบ Multiple layout กัน เอาแค่พอเป็นแนวทางไปประยุกต์ใช้ต่อได้ก็พอ
โดยจะมี Layout 3 แบบ คือ ข้อความของเพื่อน ข้อความของเรา และรูปสติกเกอร์ของเพื่อน เท่านี้ก่อน สมมุติว่าเราส่งสติกเกอร์ไม่ได้ ฮ่าๆ
สร้าง Layout
เริ่มจากใส่ listView ใน layout ของ activity หรือ fragment
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent"> </ListView> </LinearLayout>
ต่อมาจะสร้าง layout ของทั้งสามแบบ
layout_row_image_friend.xml
จะมีแต่ imageView เอาไว้แสดงสติกเกอร์
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/white" android:padding="16dp"> <ImageView android:id="@+id/imgV" android:layout_width="200dp" android:layout_height="200dp" android:src="@mipmap/ic_launcher"/> </LinearLayout>
layout_row_message_friend.xml
มีข้อความ เป็น TextView
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/white" android:padding="16dp"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:padding="10dp" android:layout_gravity="left"> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="First name" android:textSize="18sp"/> <TextView android:id="@+id/tv_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="First name" android:textSize="22sp"/> </LinearLayout> </FrameLayout>
layout_row_message_me.xml
มี TextView เป็นข้อความธรรมดา แต่ให้มันชิดขวา
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/white" android:padding="16dp"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:padding="10dp" android:layout_gravity="right"> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="First name" android:layout_gravity="right" android:textSize="18sp"/> <TextView android:id="@+id/tv_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="First name" android:layout_gravity="right" android:textSize="22sp"/> </LinearLayout> </FrameLayout>
สร้าง CustomView class ใน java
ส่วนต่อมาก็เอา layout ที่ทำไว้ทั้งสามอัน มาเขียนใน java , code จะซ้ำๆกันนิดหน่อย
CustomRowImageFriend.java
public class CustomRowImageFriend extends FrameLayout { ImageView imgView; public CustomRowImageFriend(Context context) { super(context); initInflate(); initInstance(); } public CustomRowImageFriend(Context context, AttributeSet attrs) { super(context, attrs); initInflate(); initInstance(); } public CustomRowImageFriend(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initInflate(); initInstance(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public CustomRowImageFriend(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initInflate(); initInstance(); } public void initInflate(){ inflate(getContext(), R.layout.layout_row_image_friend,this); } public void initInstance(){ imgView = (ImageView) findViewById(R.id.imgV); } public void setImage(int res){ imgView.setImageResource(res); } }
CustomRowMessageFriend.java
public class CustomRowMessageFriend extends FrameLayout { TextView tvName; TextView tvMessage; public CustomRowMessageFriend(Context context) { super(context); initInflate(); initInstance(); } public CustomRowMessageFriend(Context context, AttributeSet attrs) { super(context, attrs); initInflate(); initInstance(); } public CustomRowMessageFriend(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initInflate(); initInstance(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public CustomRowMessageFriend(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initInflate(); initInstance(); } public void initInflate(){ inflate(getContext(), R.layout.layout_row_message_friend,this); } public void initInstance(){ tvName = (TextView) findViewById(R.id.tv_name); tvMessage = (TextView) findViewById(R.id.tv_message); } public void setName(String name){ tvName.setText(name); } public void setMessage(String des){ tvMessage.setText(des); } }
CustomRowMessageMe.java
public class CustomRowMessageMe extends FrameLayout { TextView tvName; TextView tvMessage; public CustomRowMessageMe(Context context) { super(context); initInflate(); initInstance(); } public CustomRowMessageMe(Context context, AttributeSet attrs) { super(context, attrs); initInflate(); initInstance(); } public CustomRowMessageMe(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initInflate(); initInstance(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public CustomRowMessageMe(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initInflate(); initInstance(); } public void initInflate(){ inflate(getContext(), R.layout.layout_row_message_me,this); } public void initInstance(){ tvName = (TextView) findViewById(R.id.tv_name); tvMessage = (TextView) findViewById(R.id.tv_message); } public void setName(String name){ tvName.setText(name); } public void setMessage(String des){ tvMessage.setText(des); } }
สร้าง class ของข้อมูล
ต่อมาสร้าง class สำหรับโครงสร้างข้อมูล ประกอบไปด้วย sticker ตัวอย่างนี้สมมุติใช้เป็นรูปใน res แล้วก็มี ชื่อคนส่ง ระบุว่าเป็นของเราหรือไม่ สุดท้ายก็ตัวเนื้อหาข้อความ
สังเกตว่าจะมี type ด้วยเพื่อระบุว่าเป็นประเภทไหน
Message.java
public class Message { public static final int MESSAGE_FRIEND_STICKER = 0; public static final int MESSAGE_FRIEND_TEXT = 1; public static final int MESSAGE_MY_TEXT = 2; private int sticker; private String message; private int type; private String senderName; public int getSticker() { return sticker; } public void setSticker(int sticker) { this.sticker = sticker; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getSenderName() { return senderName; } public void setSenderName(String senderName) { this.senderName = senderName; } public int getType() { return type; } public void setType(int type) { this.type = type; } }
สร้าง Adapter
โดยทั่วไปก็เหมือนกับ adapter ปกติ แต่จะมี method ที่สำคัญคือ getItemViewType(position) ที่เรา ต้อง return ว่า position นี้เป็น type อะไร
แล้วที่ getView เราก็ทำการ เช็ค type ว่าเป็นประเภทไหน จากนั้นก็สร้าง CustomView พร้อมกำหนดค่าให้มัน
ก่อนที่จะ return View ออกไป
CustomMultipleLayoutAdapter.java
public class CustomMultipleLayoutAdapter extends BaseAdapter { ArrayList<Message> data; public void setData(ArrayList<Message> data){ this.data = data; } @Override public int getCount() { return data.size(); } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } @Override public int getViewTypeCount() { return Message.COUNT_TYPE; } @Override public int getItemViewType(int position) { return data.get(position).getType(); } @Override public View getView(int position, View convertView, ViewGroup parent) { int type = getItemViewType(position); Message message = data.get(position); switch (type){ case Message.MESSAGE_FRIEND_STICKER : CustomRowImageFriend rowImageFriend = new CustomRowImageFriend(parent.getContext()); rowImageFriend.setImage(message.getSticker()); return rowImageFriend; case Message.MESSAGE_FRIEND_TEXT : CustomRowMessageFriend rowMessageFriend = new CustomRowMessageFriend(parent.getContext()); rowMessageFriend.setName(message.getSenderName()); rowMessageFriend.setMessage(message.getMessage()); return rowMessageFriend; case Message.MESSAGE_MY_TEXT : CustomRowMessageMe rowMessageMe = new CustomRowMessageMe(parent.getContext()); rowMessageMe.setName(message.getSenderName()); rowMessageMe.setMessage(message.getMessage()); return rowMessageMe; } return null; } }
ลองสมมุติข้อมูล
ต่อมาก็เอาข้อมูลมาใส่ใน adapter ผมจะใช้วิธีสุ่ม สมมุติขึ้นมานะ
MainActivity.java
public class MainActivity extends AppCompatActivity { ListView listView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initInstance(); } private void initInstance(){ listView = (ListView) findViewById(R.id.listView); CustomMultipleLayoutAdapter adapter = new CustomMultipleLayoutAdapter(); adapter.setData(getSampleMessage()); listView.setAdapter(adapter); } private ArrayList<Message> getSampleMessage(){ ArrayList<Message> data = new ArrayList<>(); for (int i = 0; i < 100; i++) { Message message = new Message(); int random = (int )(Math.random() * 3 + 1); if(random == 1){ // set sticker friend message. message.setType(Message.MESSAGE_FRIEND_STICKER); message.setSticker(R.mipmap.ic_launcher); }else if(random == 2){ // set message friend message. message.setType(Message.MESSAGE_FRIEND_TEXT); message.setSenderName("Monkey D. luffy"); message.setMessage("Hello guy!!"); }else{ message.setType(Message.MESSAGE_MY_TEXT); message.setSenderName("You"); message.setMessage("I'am Iron Man!!"); } data.add(message); } return data; } }
ดูผลการทำงาน
จบแล้ว
จากตัวอย่างสามารถทำได้หลายแบบจะไม่ทำ custom view class เลยก็ได้ ไป inflate ใน method ของ adapter ก็สามารถทำได้ครับ หรือถ้า layout คล้ายๆกัน จะทำแค่ layout เดียวแล้วเปิดปิด visibility ก็ได้เหมือนกัน แต่ผมคิดว่าทำแบบครอบคลุมไปเลยดีกว่า เพราะงานจริงคงซับซ้อน และจะได้เข้าใจการทำงาน ดังนั้นเลยพาทำ custom viewgroup สร้าง class ไปเลย อันนี้เป็นตัวอย่างคร่าวๆ เอาไปประยุกต์ใช้อีกทีครับ
หากมีอะไรสอบถาม หรือแนะนำคอมเม้นไว้ได้เลยนะ
(: