Day 13 - View Pager
สวัสดีครับ บทความนี้เป็นบทความที่ 13 แล้วนะครับ ที่ผมจะมาเขียน ในซีรีย์ Learn 30 Android Libraries in 30 days
สำหรับบทความทั้งหมด อ่านได้จากด้านล่างครับ
- Day 1 : AndroidStaggered Grid
- Day 2 : Paralloid
- Day 3 : Retrofit
- Day 4 : SwipeRefreshLayout
- Day 5 : Android GraphView
- Day 6 : Holo Color Picker
- Day 7 : Android Async Http
- Day 8 : Crashlytics
- Day 9 : Butter Knife
- Day 10 : Android Annotations
- Day 11 : DateTimePicker
- Day 12 : Circular Progress Button
- Day 13 : ViewPager
- Day 14 : ViewPagerIndicator
- Day 15 : FadingActionBar
- Day 16 : AutofitTextView
- Day 17 : SwipeListView
- Day 18 : ShowcaseView
- Day 19 : GreenDAO
- Day 20 : AndroidViewAnimation
- Day 21 : ActiveAndroid
- Day 22 : Twitter4J
- Day 23 : ListViewAnimations
- Day 24 : AndEngine
- Day 25 : EazeGraph
- Day 26 : Cardslib
- Day 27 : AdapterKit
- Day 28 : WeatherLib
- Day 29 : FlatUI
- Day 30 : Android Firebase
สำหรับวันนี้ขอนำเสนอเรื่อง 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
ให้มันซะ
ทดสอบรันโปรแกรมครับ จะได้ผลลัพธ์แบบภาพข้างล่างนี้
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>
ทดสอบรับโปรแกรม
อ้าวเห้ย มีแต่ 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);
}
}
แล้วรองรันโปรแกรมใหม่อีกครั้ง
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 ถูกเลือก
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
สรุป
บทความนี้ขอใช้เป็นบทความสอน การใช้ ViewPager นะครับ เพื่อปูพื้นฐานนำไปสู่การใช้ Library ที่ชื่อ ViewPagerIndicator ด้วยครับ เนื่องจากว่ามันต้องพอรู้เรื่อง ViewPager และ Fragment บ้าง ถ้าเขียน ViewPagerIndicator ไปเลย เกรงว่าจะมีบางคนงงอีกครับ ซึ่งบทความที่ผ่านมา ก็มีคนมาถามอยู่เรื่องๆ (ผมอุตส่าห์แนบลิงค์ศึกษาเพิ่มเติมไปแล้วนะ หาก search หาซักนิด จากคีย์เวิร์ดที่ให้ไป) สำหรับบทความนี้ ก็ถือเป็น Library เนอะ เพราะเป็น Support Library Version 4 นะครับ :D
Source Code
References
- Authors
- Name
- Chai Phonbopit
- Website
- @Phonbopit