Day 17 : Swipe ListView

Day 17 : Swipe ListView Cover Image

สวัสดีครับ บทความนี้เป็นบทความที่ 17 แล้วนะครับ ที่ผมจะมาเขียน ในซีรีย์ Learn 30 Android Libraries in 30 days

สำหรับบทความทั้งหมด อ่านได้จากด้านล่างครับ

สำหรับวันนี้ขอนำเสนอเรื่อง SwipeListView เป็น Library ที่เอาไว้ทำให้ ListView สามารถที่จะ swipe เลื่อนซ้าย ขวาได้ ดังตัวอย่าง วิดีโอและรูปข้างล่างเลยครับ

SwipeListView

Installation

ขั้นตอนการติดตั้ง ง่ายๆครับ เพิ่ม dependencies ลงไปที่ไฟล์ build.gradle (ไฟล์ภายใน Module)

dependencies {
    compile ('com.fortysevendeg.swipelistview:swipelistview:[email protected]') {
        transitive = true
    }
}

ต่อจากนั้นก็เพิ่ม Maven Central ที่ไฟล์ build.gradle ใน Root Project ดังนี้

repositories {
    maven { url 'http://clinker.47deg.com/nexus/content/groups/public' }
}

ถ้ามี maven url หลายอัน ก็จะได้เป็นลักษณะนี้

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    ....
}
allprojects {
    repositories {
        maven {
            url {
                'http://repo1.maven.org/maven2/'
                "http://dl.bintray.com/populov/maven"
                "http://clinker.47deg.com/nexus/content/groups/public"
            }
        }
        jcenter()
    }
}

กด Sync Project with Gradle File เป็นอันเรียบร้อย

Getting Started

การใช้งาน SwipeListView มีตัวอย่าง Demo ให้ดูที่ Google Play ด้วยนะครับ ลองโหลดมาลองดูได้ ส่วน Sample Code ก็ตามนี้

สำหรับวิธีการใช้งาน ก็เหมือนการใช้ ListView ธรรมดาเลยครับ โดย xml จะมีลักษณะประมาณนี้

<com.fortysevendeg.swipelistview.SwipeListView
    xmlns:swipe="http://schemas.android.com/apk/res-auto"
    android:id="@+id/example_lv_list"
    android:listSelector="#00000000"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    swipe:swipeFrontView="@+id/front"
    swipe:swipeBackView="@+id/back"
    swipe:swipeActionLeft="[reveal | dismiss]"
    swipe:swipeActionRight="[reveal | dismiss]"
    swipe:swipeMode="[none | both | right | left]"
    swipe:swipeCloseAllItemsWhenMoveList="[true | false]"
    swipe:swipeOpenOnLongPress="[true | false]"
    swipe:swipeAnimationTime="[miliseconds]"
    swipe:swipeOffsetLeft="[dimension]"
    swipe:swipeOffsetRight="[dimension]"
    />

ส่วนข้างล่างเป็นคำอธิบายแต่ละ attributes ครับ

  • swipeFrontView : เลเอาท์สำหรับ Front View (ต้องมี)
  • swipeBackView : เลเอาท์สำหรับ Back View (ต้องมี)
  • swipeActionLeft : เอฟเฟคเวลา swipe ไปทางซ้าย มีให้เลือก reveal (default) กับ dismiss
  • swipeActionRight : เอฟเฟคเวลา swipe ไปทางขวา มีให้เลือก reveal (default) กับ dismiss
  • swipeMode : เลือก Gestures หรือไม่ มี ให้เลือก none, both, right, left
  • swipeCloseAllItemsWhenMoveList : ให้ปิดที่ swipe ไว้ทั้งหมด เวลาเลือก listView. Default เป็น true
  • swipeOpenOnLongPress เปิดเมื่อกด Long Press. Default เป็น true
  • swipeAnimationTime : เวลาที่ใ้ห้แสดง animation หน่วยเป็น ms
  • swipeOffsetLeft และ swipeOffsetRight : ระยะ offset เวลา swipe ไปซ้ายขวา จะให้มันเว้นระยะแค่ไหน

OK เมื่อรู้วิธีใช้และ attribute เบื้องต้นแล้ว ต่อไปก็มาสร้างโปรเจ็คกันนะครับ มันคล้ายๆกับ ListView ทั่วๆไป

Create Project

เป้าหมายโปรเจ็คคือ แสดง ListView เป็นชื่อเว็บไซต์ที่มีบทความสอนแอนดรอยส์ จากนั้นเมื่อ Swipe ก็จะมีปุ่มให้กดไปหน้าเว็บไซต์นั้นๆครับ สำหรับไฟล์และเลเอาท์ที่จะใช้ก็จะมีดังนี้

  • SwipeListViewActivity.java : สำหรับ Activity หลักครับ
  • CustomAdapter : มี Custom Adapter คลาสนึง เอาไว้แสดง หน้า front และ back ของ ListView แต่ละแถว
  • activity_swipe_listview.xml : หน้าเลเอาท์หลัก มี SwipeListView อยู่ภายใน
  • list_item.xml : สำหรับ Layout ในแต่ละ row ของ SwipeListView

บทความที่เกี่ยวข้อง

เริ่มที่ activity_swipe_listview.xml มีแค่ SwipeListView ภายใน LinearLayout อันเดียวเท่านั้น

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:swipe="http://schemas.android.com/apk/res-auto"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <com.fortysevendeg.swipelistview.SwipeListView
        android:id="@+id/list_view"
        android:listSelector="#f30085ff"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        swipe:swipeFrontView="@+id/front"
        swipe:swipeBackView="@+id/back"
        swipe:swipeAnimationTime="300"
        swipe:swipeActionLeft="dismiss"
        swipe:swipeMode="both"
        swipe:swipeCloseAllItemsWhenMoveList="true" />

</LinearLayout>

ต่อมาก็สร้างเลเอาท์ แต่ละแถวของ ListView ก็คือไฟล์ list_item.xml โดยให้ FrontView อยู่ด้านบนของ BackView

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="match_parent" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp"
        android:gravity="center"
        android:background="#ffcdcbcb"
        android:id="@+id/back"
        android:tag="back" >

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/button_visit"
            android:text="@string/visit_website"/>

    </LinearLayout>
    <RelativeLayout
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:padding="16dp"
        android:background="#ff31415a"
        android:id="@+id/front"
        android:tag="front">

        <ImageView
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:layout_centerVertical="true"
            android:layout_marginRight="16dp"
            android:id="@+id/website_logo"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/website_logo"
            android:layout_centerVertical="true"
            android:textColor="#ffffff"
            android:id="@+id/website_title"/>

    </RelativeLayout>

</FrameLayout>

เลเอาท์ LinearLayout และ RelativeLayout จะซ้อนกันแบบนี้

Layout

จะเห็นว่า มี id ชื่อ @+id/back และ @+id/front ซึ่งเป็นเลเอาท์ สำหรับแสดงข้างหน้าก่อนการ swipe และ เลเอาท์ข้างหลัก เมื่อมีการ swipe ดังที่ประกาศ attribute

swipe:swipeFrontView="@+id/front"
swipe:swipeBackView="@+id/back"

ไว้ในไฟล์ activity_swipe_listview.xml

Custom Adapter

ต่อมาผมทำ Custom Adapter เพื่อจะจัดหน้าตาเลเอาท์แต่ละ row เอง ส่วนนี้ไม่ขอลงรายละเอียดมากนะครับ ไปอ่านเพิ่มเติมในบทความเกี่ยวกับ Custom ListView ดูครับ

ทำการตั้งชื่อว่า WebsiteAdapter.java มีโค๊ดดังนี้

package com.devahoy.learn30androidlibraries.day17;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import com.devahoy.learn30androidlibraries.R;

import java.util.ArrayList;

public class WebsiteAdapter extends BaseAdapter {

    private Context mContext;
    private LayoutInflater mInflater;
    private ArrayList<Website> mWebsites;

    public WebsiteAdapter(Context context, ArrayList<Website> sites) {
        mContext = context;
        mInflater = LayoutInflater.from(context);
        mWebsites = sites;
    }

    public int getCount() {
        return mWebsites.size();
    }

    public Object getItem(int position) {
        return mWebsites.get(position);
    }

    public long getItemId(int position) {
        return position;
    }

    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder holder;

        if (convertView == null) {
            holder = new ViewHolder();
            convertView = mInflater.inflate(R.layout.day17_list_item, parent, false);

            holder.title = (TextView) convertView.findViewById(R.id.website_title);
            holder.buttonVisit = (Button) convertView.findViewById(R.id.button_visit);
            holder.logo = (ImageView) convertView.findViewById(R.id.website_logo);

            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }


        final Website website = mWebsites.get(position);

        holder.title.setText(website.getTitle());
        holder.logo.setImageResource(website.getImageResourceId());

        holder.buttonVisit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setData(Uri.parse(website.getUrl()));
                mContext.startActivity(intent);
            }
        });

        return convertView;
    }

    static class ViewHolder {
        TextView title;
        Button buttonVisit;
        ImageView logo;
    }
}

ตัวเว็บไซต์มี ชื่อ มี url และมีโลโ้ก้ ฉะนั้นผมเลยสร้างคลาส Website ขึ้นมาครับ ดังนี้

package com.devahoy.learn30androidlibraries.day17;

public class Website {
    String title;
    String url;
    int imageResourceId;

    public Website(String title, String url, int imgId) {
        this.title = title;
        this.url = url;
        this.imageResourceId = imgId;
    }

    public String getTitle() {
        return title;
    }
    public String getUrl() {
        return url;
    }
    public int getImageResourceId() {
        return imageResourceId;
    }
}

สุดท้ายที่ไฟล์ Activity หลัก ผมตั้งชื่อว่า SwipeListViewActivity

package com.devahoy.learn30androidlibraries.day17;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;

import com.devahoy.learn30androidlibraries.R;
import com.fortysevendeg.swipelistview.BaseSwipeListViewListener;
import com.fortysevendeg.swipelistview.SwipeListView;

import java.util.ArrayList;

public class SwipeListViewActivity extends ActionBarActivity {

    SwipeListView mSwipeListView;
    private ArrayList<Website> mWebsites;
    private WebsiteAdapter mAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.day17_activity_swipe_listview);
        mSwipeListView = (SwipeListView) findViewById(R.id.list_view);

        initSampleData();


        mSwipeListView.setSwipeListViewListener(new BaseSwipeListViewListener() {
            @Override
            public void onDismiss(int[] reverseSortedPositions) {
                for (int position : reverseSortedPositions) {
                    Website website = mWebsites.get(position);

                    mWebsites.remove(position);
                    mWebsites.add(website);
                }

                mAdapter.notifyDataSetChanged();
            }
        });

    }

    private void initSampleData() {
        Website akexorcist = new Website("Sleeping For Less",
                "http://www.akexorcist.com/",
                R.drawable.akexorcist);

        Website androidthai = new Website("ทุกๆเรื่อง ที่เกี่ยวกับ android โดย มาสเตอร์ อึ่ง",
                "http://www.androidthai.in.th/",
                R.drawable.androidthai);

        Website android4health = new Website("android4health",
                "http://android4health.wordpress.com/",
                R.drawable.android4health);

        Website martoutine = new Website("Mart Tanathip | Simple routine of me",
                "http://www.martroutine.com",
                R.drawable.martroutine);

        Website nuuneoi = new Website("NuuNeoI : Personal Blog of a little full stack developer guy",
                "http://nuuneoi.com",
                R.drawable.nuuneoi);

        Website devahoy = new Website("Devahoy",
                "http://devahoy.com",
                R.drawable.devahoy);

        mWebsites = new ArrayList<Website>();
        mWebsites.add(akexorcist);
        mWebsites.add(androidthai);
        mWebsites.add(android4health);
        mWebsites.add(martoutine);
        mWebsites.add(nuuneoi);
        mWebsites.add(devahoy);

        mAdapter = new WebsiteAdapter(this, mWebsites);
        mSwipeListView.setAdapter(mAdapter);
    }
}

โค๊ดด้านบน เป็นการสร้าง WebsiteAdapter จาก ArrayList<Website> ที่ผมจำลองขึ้นมา จากนั้นก็ setAdapter() ให้กับ SwipeListView ส่วน SwipeListView ทำการ setSwipeListViewListener ให้มันเพื่อส่ง callback กลับมา ในกรณีที่มีการ swipe จากนั้นผม override เมธอด แค่ตัวเดียวครับ onDismiss เวลาที่ทำการ swipe ไปทางซ้าย ก็จะเป็นการเรียก dismiss (ที่คอนฟิคไว้ใน xml) ก็จะให้แถวๆนั้น ไปอยู่ท้ายสุดของ List ครับ ส่วนเมธอดใน Listenter มีอะไรบ้าง ก็ตามรูปครับ ลองนำไปประยุกต์ใช้ดูครับ

Method

ส่วนใน initSampleData() ผมทำการสร้าง ข้อมูลขึ้นมา โดยใส่ title, url และ ไฟล์รูปภาพนะครับ สำหรับใครอยากได้รูปภาพ ก็โหลดจาก Github เลยครับ ภาพแอบขโมยมาจากแถวๆนี้แหละครับ :)

เมื่อรัน ผลลัพธ์ก็ได้ดังรูป

List

List2

Happy Coding :D

Source Code

References

Chai Chai Phonbopit : Software Engineer @Nextzy • ผู้ชายธรรมดาๆ ที่ชื่นชอบ Android, JavaScript (Node.js) และ Open Source มีงานอดิเรกเป็น Acoustic Guitar และ Football

บทความล่าสุด