Day 13 : View Pager

Day 13 : View Pager Cover Image

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

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

สำหรับวันนี้ขอนำเสนอเรื่อง ViewPager ครับ จริงๆแล้ว วันนี้ผมกะเขียนเรื่อง ViewPagerIndicator แต่ว่าเห็นว่าเนื้อหาส่วนใหญ่ จำเป็นต้องรู้ เช่น ViewPager และ Fragment ซะก่อน ทำให้ผมตัดสินใจเลือกทำเป็นบทความสอน ViewPager ขึ้นมาซะเลย ทำให้บทความนี้เป็นการทบทวนการใช้ ViewPager (ซึ่งไม่ได้ใช้นาน) ของผมเองแล้วก็นำมาเรียบเรียงเป็นบทสอนด้วยครับ

Getting Started

การสร้าง ViewPager จำเป็นต้องมี 3 ส่วนหลักๆคือ

  • ViewPager : เป็นคลาส Layout Manager ที่เอาไว้ swipe เพื่อเปลี่ยน Fragment
  • Fragment : สำหรับแสดงหน้า Layout แต่ละหน้า
  • FragmentStatePageAdapter : เป็น Adapter ที่เอาไว้จัดการ Fragment แต่ละหน้า (นึกถึง ListView หรือ GridView ก็จะมี Adapter คอยจัดการ Layout และข้อมูล)

Step 1 : Create Framgent

ขั้นแรก เราจะสร้างหน้า Layout ขึ้นมาก่อน เริ่มต้นด้วยการสร้าง day13_fragment.xml ขึ้นมา ซึ่งมีแค่ TextView ในส่วนนี้จะเอาไว้แสดง ข้อความไม่ให้ซ้ำกันในแต่ละหน้า

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/linear_layout"
              android:orientation="vertical"
              android:gravity="center"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/greeting"
        android:textColor="@android:color/white"
        android:textSize="32sp" />

</LinearLayout>

ต่อมาสร้างคลาส Fragment ขึ้นมา ผมตั้งชื่อว่า SimpleFragment.java

package com.devahoy.learn30androidlibraries.day13;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.devahoy.learn30androidlibraries.R;

public class SimpleFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        ViewGroup rootView = (ViewGroup) inflater.inflate(
                R.layout.day13_fragment, container, false);

        return rootView;
    }
}

จากด้านบน สร้าง Fragment จากนั้นเพิ่มเธอด onCreateView() เพื่อทำการสร้าง ViewGroup จาก เลเอาท์ด้านบนที่เราเพิ่งสร้างไป

ต่อมาผมทำการ binding View 2 ตัวคือ TextView และ LinearLayout เพื่อที่จะให้แต่ละหน้าของ Fragment หน้าตาแตกต่างกัน โดยเพิ่มข้อมูลจำลองไปเป็นแบบนี้

package com.devahoy.learn30androidlibraries.day13;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.devahoy.learn30androidlibraries.R;

public class SimpleFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        int position = 0;

        int[] colors = {
            Color.rgb(0xF6, 0x47, 0x47), // #F64747
            Color.rgb(0x9A, 0x12, 0xB3), // #9A12B3
            Color.rgb(0x22, 0xA7, 0xF0), // #22A7F0
            Color.rgb(0x4B, 0x77, 0xBE), // #4B77BE
            Color.rgb(0x1B, 0xBC, 0x9B), // #1BBC9B
            Color.rgb(0xF2, 0x79, 0x35)  // #F27935
        };

        final String[] names = {
                "John Doe", "Jane Doe", "Chuck Norris", "Janie Roe", "James Roe"
        };

        Bundle bundle = getArguments();

        position = bundle.getInt(SimplePagerAdapter.ARGS_POSITION);

        ViewGroup rootView = (ViewGroup) inflater.inflate(
                R.layout.day13_fragment, container, false);

        TextView greeting = (TextView) rootView.findViewById(R.id.greeting);
        LinearLayout linearLayout = (LinearLayout)
                rootView.findViewById(R.id.linear_layout);

        linearLayout.setBackgroundColor(colors[position % colors.length]);
        greeting.setText("Hello " + names[position % names.length]);

        return rootView;
    }
}

จากโค๊ดด้านบน เรายังไม่มีในส่วนของ getArguments() นะครับ เนื่องจากส่วนนี้จะถูกส่งมาจาก Activity ซึ่งเรายังไม่ได้สร้าง โดยเราจะส่งเป็น หน้า Page มาว่าหน้าที่เท่าไหร่ จากนั้นก็แสดง colors และ names ตามตำแหน่งที่ส่งมา

โค๊ดข้างบนยังมี error นะครับ เนื่องจากยังไม่ได้สร้างทั้ง Activity และ PagerAdapter ที่กำลังจะสร้างในขั้นตอนถัดไป

Step 2 : FragmentStatePagerAdapter

ต่อมาหลังจากสร้าง Fragment แล้ว ต่อไปก็เป็นการสร้าง Adapter โดยทำการ extends เ้้จ้า FragmentStatePagerAdapter ให้สร้างคลาสขึ้นมาใหม่ 1 คลาส สมมติผมตั้งชื่อว่า SimplePagerAdapter

package com.devahoy.learn30androidlibraries.day13;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;

class SimplePagerAdapter extends FragmentStatePagerAdapter {

    public static final String ARGS_POSITION = "name";
    public static final int NUM_PAGES = 5;

    public SimplePagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {

        Fragment fragment = new SimpleFragment();
        Bundle args = new Bundle();
        args.putInt(ARGS_POSITION, position);
        fragment.setArguments(args);

        return fragment;
    }

    @Override
    public int getCount() {
        return NUM_PAGES;
    }
}

จากโค๊ดด้านบน เมื่อเราทำการ extends FragmentStatePagerAdapter แล้ว เราก็ต้อง override เมธอด 2 ตัวครับ คือ getItem() สำหรับแสดงหน้า Fragment และ getCount() สำหรับแสดงจำนวน page ทั้งหมดของ ViewPager

ในส่วน getItem() จะเห็นว่าเราทำการ new SimpleFragment() ขึ้นมา แล้วส่งค่า argument ให้กับ Fragment หากลองขึ้นไปดูคลาส SimpleFragment จะเห็นว่าเรา เรียก getArgument() เพื่อรับค่าตัวนี้ไว้นั่นเอง

ต้องทำการสร้าง Constructor เพื่อสั่ง call ตัว Constructor ของ FragmentStatePagerAdapter ด้วยนะครับ!

Step 3 : ViewPager

มาถึงขั้นตอนสุดท้ายสำหรับการสร้าง ViewPager กันแล้ว การจะใช้ ViewPager เราต้องทำการสร้าง Layout ขึ้นมา่ก่อน ผมตั้งชื่อว่า activity_viewpager.xml ซึ่งภายใน Layout มี ViewPager อยู่อันเดียวครับ

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.view.ViewPager
    android:id="@+id/pager"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

ต่อมา สร้างคลาสใหม่ขึ้นมา ตั้งชื่อว่า ViewPagerActivity.java

package com.devahoy.learn30androidlibraries.day13;

import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBarActivity;

import com.devahoy.learn30androidlibraries.R;

public class ViewPagerActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.day13_activity_viewpager);

        ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
        PagerAdapter adapter = new SimplePagerAdapter(getSupportFragmentManager());

        viewPager.setAdapter(adapter);
    }
}

จากโค๊ดด้านบน เราทำการเซท Layout โดยใช้เลเอาท์ด้านบน ที่มีแค่ ViewPager จากนั้นก็ setAdapter ให้มันซะ

ทดสอบรันโปรแกรมครับ จะได้ผลลัพธ์แบบภาพข้างล่างนี้

Normal

Step 4 : Title Strip

หากเราต้องการเพิ่มในส่วน Title เข้าไปแต่ละหน้าด้วยละ ทำยังไง? ทำได้โดยการใช้ PagerTitleStrip ครับ โดยการเปิดไฟล์ activity_viewpager.xml จากนั้นเพิ่มโค๊ดในส่วน PagerTitleStrip เข้าไปดังนี้ (สังเกตว่าต้องเพิ่มลงไปอยู่ภายใน ViewPager นะครับ ไม่ใช่ต่อกัน)

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.view.ViewPager
    android:id="@+id/pager"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.PagerTitleStrip
        android:id="@+id/pager_title_strip"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="top"
        android:background="#3EAFF9"
        android:paddingBottom="4dp"
        android:paddingTop="8dp"
        android:textColor="#fff"/>

</android.support.v4.view.ViewPager>

ทดสอบรับโปรแกรม

No Title

อ้าวเห้ย มีแต่ Strip แต่ไม่มี Title ขึ้นนี่นา? ถูกแล้วครับ เนื่องจากว่าเรายังไมไ่ด้เพิ่ม Title ให้มันครับ กลับไปแก้ไขโดยไปที่ไฟล์ SimplePagerAdapter จากนั้นเพิ่่มเมธอด getPageTitle() ลงไปดังนี้

package com.devahoy.learn30androidlibraries.day13;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;

class SimplePagerAdapter extends FragmentStatePagerAdapter {
    ...

    @Override
    public CharSequence getPageTitle(int position) {
        return "PAGE#" + (position + 1);
    }
}

แล้วรองรันโปรแกรมใหม่อีกครั้ง

Strip

OK! ได้ผลเป็นที่น่าพอใจแล้ว :D

Step 5 : Tab Title

สุดท้ายแล้ว หากเรายังเปลี่ยน Title จาก Strip ไปใช้ Tab แทน จะทำยังไง?

ต้องใช้ ActionBar เข้ามาช่วยครับ โดยการเซท ActionBar.NAVIGATION_MODE_TABS หากจะใช้ Tab แทน Strip ก็ให้ลบ PagerTitleStrip ออกจาก Layout ซะก่อนครับ เหลือไว้แค่ ViewPager เหมือนเดิม

จากนั้นเปิดไฟล์ ViewPagerActivity เพิ่มในส่วนของ ActionBar ดังนี้

package com.devahoy.learn30androidlibraries.day13;

import android.app.ActionBar;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBarActivity;

import com.devahoy.learn30androidlibraries.R;

public class ViewPagerActivity extends ActionBarActivity {

    private ViewPager mViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final ActionBar actionBar = getActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        setContentView(R.layout.day13_activity_viewpager);

        mViewPager = (ViewPager) findViewById(R.id.pager);
        PagerAdapter adapter = new SimplePagerAdapter(getSupportFragmentManager());
        mViewPager.setAdapter(adapter);

        for (int i = 0; i < SimplePagerAdapter.NUM_PAGES; i++) {
            actionBar.addTab(actionBar.newTab()
                            .setText("Tab #" + i)
                            .setTabListener(tabListener));
        }
    }

    private ActionBar.TabListener tabListener = new ActionBar.TabListener() {
        @Override
        public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
            mViewPager.setCurrentItem(tab.getPosition());
        }

        @Override
        public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {

        }

        @Override
        public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {

        }
    };
}

จากโค๊ดด้านบน เราทำการ getActionBar() จากนั้นก็ทำการ set ให้อยู่ในโหมด Tab จากนั้นก็วนลูปตามจำนวนของ Page แล้ว addTab() โดยตั้งชื่อTab ตามตำแหน่งของ Page และทำการ setTabListener() ให้แท็ป เมื่อมีการคลิกที่แท็ป ตรงส่วน onTabSelected เราจะเซตค่า ViewPager.setCurrentItem() เป็นหน้าเดียวกับที่ Tab ถูกเลือก

Tab

Cool เลือกแท้ป แล้ว Page เปลี่ยน แต่ว่าตอน swipe Page เจ้า Tab กลับไม่ยอมเปลี่ยนตาม ทำไม? เพราะว่าเรายังไม่ได้ setListener() ให้กับ ViewPager เมื่อตอนที่มีการเปลี่ยน Page ครับ ทำได้โดยเพิ่มนี้ลงไป

mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        getActionBar().setSelectedNavigationItem(position);
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
});

สุดท้ายทดสอบรันโปรแกรมใหม่อีกครั้ง (ผมเพิ่ม PAGE_NUM เป็น 50) คราวนี้สมบูรณ์เรียบร้อยแล้ว จบครับ :D

Completed

สรุป

บทความนี้ขอใช้เป็นบทความสอน การใช้ ViewPager นะครับ เพื่อปูพื้นฐานนำไปสู่การใช้ Library ที่ชื่อ ViewPagerIndicator ด้วยครับ เนื่องจากว่ามันต้องพอรู้เรื่อง ViewPager และ Fragment บ้าง ถ้าเขียน ViewPagerIndicator ไปเลย เกรงว่าจะมีบางคนงงอีกครับ ซึ่งบทความที่ผ่านมา ก็มีคนมาถามอยู่เรื่องๆ (ผมอุตส่าห์แนบลิงค์ศึกษาเพิ่มเติมไปแล้วนะ หาก search หาซักนิด จากคีย์เวิร์ดที่ให้ไป) สำหรับบทความนี้ ก็ถือเป็น Library เนอะ เพราะเป็น Support Library Version 4 นะครับ :D

Source Code

References

Chai

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

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