31 October 2014

Let's Fragment - เริ่มต้นง่ายๆกับ Fragment แบบพื้นฐาน

Updated on


        หลังจากที่เกริ่นกันไปคร่าวๆแล้วกับ Fragment ในบทความ [Android Code] Fragment Principle - มารู้จักกับ Fragment กันเถอะ~ คราวนี้ก็ลองมาดูวิธีการใช้งานเบื้องต้นกันต่อดีกว่า

        แต่ก่อนจะเริ่มเรียกใช้งาน Fragment ก็ต้องรู้เรื่องพื้นฐานต่างๆสำหรับ Fragment ก่อนนะ

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

        • Fragment Principle - มารู้จักกับ Fragment กันเถอะ~
        • Let's Fragment - เริ่มต้นง่ายๆกับ Fragment แบบพื้นฐาน
        • Let's Fragment - ว่าด้วยเรื่องการสร้าง Fragment จาก Constructor ที่ถูกต้อง
        • Let's Fragment - รู้จักกับ FragmentTransaction สำหรับการแสดง Fragment [ตอนที่ 1]
        • Let's Fragment - รู้จักกับ FragmentTransaction สำหรับการแสดง Fragment [ตอนที่ 2]
        • Let's Fragment - วงจรชัวิตของ Fragment (Fragment Lifecycle)
        • Let's Fragment - มาทำ View Pager กันเถิดพี่น้อง~ [ตอนที่ 1]
        • Let's Fragment - มาทำ View Pager กันเถิดพี่น้อง~ [ตอนที่ 2]

คลาส Fragment ในทุกวันนี้มี 2 คลาสด้วยกัน

Fragment (android.app.Fragment)

        เป็น Fragment ที่ถือกำเนิดขึ้นบน Android 3.0 Honeycomb ซึ่งในตอนนั้น Fragment ถูกสร้างขึ้นมาและถูกผลักดันให้ใช้งานเพื่อให้การทำงานของแอปพลิเคชันยืดหยุ่นขึ้น สามารถทำแอปพลิเคชันในรูปแบบต่างๆได้เยอะขึ้น แต่ทว่าปัญหาหลักในตอนนั้นก็คือแอนดรอยด์เวอร์ชันที่ต่ำกว่านั้นใช้งานไม่ได้ ซึ่งเป็นช่วงที่ Android 2.3 ยังเป็นที่แพร่หลาย (ในขณะที่ Android 3.0 - 3.2 ไม่เป็นที่นิยมซักเท่าไร) จึงทำให้ Fragment มีข้อจำกัดที่ทำให้ไม่ค่อยน่าใช้งานซักเท่าไร

Fragment (android.support.v4.Fragment)

        เป็น Fragment ที่ทีมแอนดรอยด์ได้เปิดตัวใน Android 4.0 Icecream Sandwich ซึ่งมาพร้อมกับ Android Support v4 ซึ่งจุดเด่นหลักคือ "ความสามารถใหม่ๆ ที่สามารถใช้กับเวอร์ชันเก่าๆได้" ซึ่ง Fragment ของคลาสนี้จะถูกเพิ่มฟีเจอร์ใหม่ๆเข้ามาอยู่ตลอดเวลา ไม่ได้ขึ้นอยู่กับเวอร์ชันแอนดรอยด์ของแต่ละเครื่อง

        ในการใช้งานจริง เจ้าของบล็อกแนะนำให้ใช้ Fragment ของ Support v4 นะ เพราะว่า Support Library นั้นจะมีการอัปเดตอยู่เสมอ ไม่ว่าจะเป็นฟีเจอร์หรือว่าการแก้ไขบั๊ก ซึ่งต่างจาก Fragment อีกแบบที่ผูกอยู่กับเวอร์ชันแอนดรอยด์ของเครื่องนั้นๆ จึงทำให้อัปเดตฟีเจอร์เพิ่มหรือแก้ไขบั๊กไม่ได้

ก่อนจะเริ่มสร้าง Fragment

        การสร้าง Fragment ก็จะคล้ายๆกับ Activity แต่ไม่จำเป็นต้องประกาศใน Android Manifest จึงสามารถสร้างคลาส Fragment แล้วนำไปใช้งานได้ทันที

        อย่างที่บอกไปว่าเจ้าของบล็อกแนะนำให้ใช้ Fragment ของ Support v4 ดังนั้นจึงอย่าลืมใส่ Dependency ของ Android Support v4 ไว้ใน build.gradle ด้วยนะ

ependencies {
    
    ...

    compile 'com.android.support:support-v4:<LATEST_VERSION>'
}

        หรือถ้าใช้พวก AppCompat v7 อยู่แล้วก็ไม่ต้องใส่เพิ่ม เพราะว่า AppCompat v7 นั้นมี Support v4 อยู่แล้ว

การสร้าง Fragment

        ผู้ที่หลงเข้ามาอ่านสามารถสร้าง Fragment ผ่านเมนูของ Android Studio ได้เลย โดยเลือก New > Fragment > Fragment (Blank)



        ในขั้นตอนการกำหนด Fragment เจ้าของบล็อกอยากจะให้เอาช่อง Include fragment factory methods? กับ Include interface callback? ออกก่อน เพราะเป็นส่วนที่ Android Studio จะใส่ชุดคำสั่งเตรียมมาให้ในตอนสร้าง ซึ่งไม่ค่อยเป็นมิตรซักเท่าไรสำหรับผู้เริ่มต้น



        เมื่อสร้างขึ้นมาแล้ว ก็จะเห็นว่ามีโค้ดที่ถูกเตรียมไว้ให้ประมาณนี้



        โดย Layout สำหรับ Fragment ก็จะถูกสร้างขึ้นมาพร้อมกันเลย ซึ่งยังไม่ต้องไปยุ่งอะไรกับมัน ขอให้ Layout ตัวนั้นมี Text View พร้อมข้อความก็พอแล้ว


        เท่านี้ก็เป็นอันเสร็จสิ้นการสร้าง Fragment (แค่เบื้องต้น จริงๆมีโค๊ดมากว่านี้)

        ทั้งนี้ทั้งนั้น ผู้ที่หลงเข้ามาอ่านสามารถเลือกได้ว่าจะสร้างไฟล์แล้วพิมพ์โค้ดลงไปเองหรือสร้างจากเมนูที่อยู่ใน Android Studio

วิธีการทำ Fragment ไปใช้งาน

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

วิธีแรก : ประกาศ <fragment> ใน Layout XML

        ถ้า Fragment ที่สร้างขึ้นมาต้องการแปะไว้ที่ Activity ที่ต้องการแบบถาวร ไม่มีการสลับสับเปลี่ยนระหว่าง Fragment ตัวอื่นๆ ก็แนะนำให้ใช้วิธีนี้ครับ

        ใน Layout XML จะมี <fragment /> ให้ใช้งาน ซึ่งเป็น XML Tag สำหรับ Fragment โดยเฉพาะ สามารถกำหนดชื่อ Fragment ที่ต้องการแปะไว้ใน Layout ได้ทันที

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    
    <fragment
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:name="com.akexorcist.fragment.OrderListFragment"
        android:id="@+id/fragment_order_list"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        tools:layout="@layout/fragment_my" />
    
</RelativeLayout>

        พอ Activity เรียก Layout XML ตัวนี้มาใช้ ก็จะนำ Fragment มาแปะไว้ตามที่กำหนดไว้ใน XML Tag ทันที และอย่าลืมกำหนด ID ด้วยล่ะ เพื่อเรียกใช้งานผ่านโค้ด Java ในภายหลัง

        เวลาจะเรียกใช้งานผ่านโค้ด Java ก็จะเรียกจากชื่อ ID นั่นเอง

public class MainActivity extends AppCompatActivity {

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

        Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_order_list);
        OrderListFragment orderListFragment = (OrderListFragment) fragment;
    }
}

        จะเห็นว่าการเรียก Fragment ที่สร้างขึ้นด้วยวิธีนี้มาใช้งานจะต้องใช้คำสั่ง findFragmentById(...) ซึ่งคล้ายๆกับ findViewById(...) นั่นเอง

        ไม่ยากเนอะ?

        เพิ่มเติม การเพิ่ม <fragment> ลงใน Layout XML ไม่จำเป็นต้องพิมพ์โค๊ดเองนะ เพราะในหน้า Design มีให้เลือกอยู่แล้ว


        เมื่อลากใส่ Layout ที่ต้องการแล้ว ก็จะมีหน้าต่างแสดงขึ้นมาถามว่าจะกำหนดเป็น Fragment ตัวไหน (นั่นคือที่มาว่าทำไมต้องไปสร้าง Fragment ขึ้นมาก่อน)



        แต่บางครั้งที่หน้า Design ก็จะมีแจ้งเตือนแบบนี้


        ไม่ต้องตกใจไป ที่แจ้งเตือนแบบนี้ก็เพราะว่าเพิ่ม <fragment> ลงไปใน Layout แต่ Android Studio ไม่รู้ว่าจะต้องเอา Layout ตัวไหนของ Fragment มา Preview ให้ดู ซึ่งสามารถแก้ไขได้ด้วยการกดเลือก Use @layout/<fragment name> เพื่อให้ Preview Layout ของ Fragment ตัวนั้นๆ ก็หมดปัญหาแล้ว

        และจริงๆแล้วคือการใส่ tools:layout ใน <fragment> เพื่อให้แสดง Layout ตัวนั้นๆในหน้าต่าง Preview เท่านั้น ไม่มีผลต่อการทำงานจริง


วิธีที่สอง : สร้าง Fragment แล้วแปะลงบน Layout ผ่านโค้ด

        นอกจากการใช้ <fragment /> แล้ว ยังสามารถสร้าง Fragment ผ่านโค้ด Java แล้วเอาไปแปะลงบน ViewGroup ที่ต้องการได้ ซึ่งเจ้าของบล็อกจะนิยมใช้ FrameLayout

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    ...

    <FrameLayout
        android:id="@+id/layout_fragment_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true" />

</RelativeLayout>

        การแปะ Fragment ลงบน ViewGroup ใดๆก็ตามจะต้องทำผ่าน FragmentTransaction เท่านั้น ซึ่งอ่านเพิ่มเติมได้ที่ [Android Code] Let's Fragment - รู้จักกับ FragmentTransaction สำหรับการแสดง Fragment [ตอนที่ 1]

public class MainActivity extends AppCompatActivity {

    ...

    private void addOrderFragment() {
        OrderFragment fragment = new OrderFragment();
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();
        transaction.replace(R.id.layout_fragment_container, fragment);
        transaction.commit();
    } 
}

        เพิ่มเติม - ViewGroup ที่ว่านี้ รวมไปถึง <fragment /> ด้วยนะเออ จึงสามารถเตรียม <fragment /> เปล่าๆไว้ใน Layout แล้วใช้ FragmentTrasaction เพื่อแปะ Fragment ลงบนนั้นก็ได้เช่นกัน

        เพิ่มเติม 2 - การสร้าง Fragment ในโค้ดตัวอย่างข้างบนนี้จะใช้วิธี New Object เหมือนกันสร้าง Object ทั่วๆไป ซึ่งเจ้าของบล็อกแนะนำให้อ่านบทความ [Android Code] Let's Fragment - ว่าด้วยเรื่องการสร้าง Fragment จาก Constructor ที่ถูกต้อง เพื่อสร้าง Fragment ด้วยวิธีที่ดีกว่านี้

สร้าง Fragment ใน XML กับสร้างด้วยโค้ด Java แบบไหนดีกว่ากัน?

        ไม่มีวิธีไหนที่ดีที่สุดครับ เพราะขึ้นอยู่กับว่าผู้ที่หลงเข้ามาอ่านต้องการใช้งาน Fragment แบบไหนซะมากกว่า ซึ่งวิธีแรกนั้นจะเหมาะกับ Fragment ที่ตายตัว ไม่มีการเปลี่ยนแปลง อย่างเช่น MapFragment ของ Google Maps API ที่แปะลงบน Layout เตรียมไว้ตั้งแต่แรก ส่วนวิธีที่สองจะเหมาะสำหรับการเปลี่ยน Fragment บนนั้นอยู่ตลอดเวลา เพราะงานส่วนใหญ่จะใช้ Fragment แทนแต่ละหน้าของแอปฯ ดังนั้นการเปลี่ยนหน้าไปมาก็คือการเปลี่ยน Fragment นั่นเอง

        ซึ่งทั้งสองวิธีนี้สามารถใช้ร่วมกันได้ อย่างเช่น ต้องการแปะ Fragment ไว้ตั้งแต่ตอนแรก แต่สั่งให้เปลี่ยนเป็น Fragment ตัวอื่นได้ เป็นต้น

ข้อสังเกตเกี่ยวกับคำสั่ง getFragmentManager กับ getSupportFragmentManager

        จะเห็นว่าการเรียกใช้งาน FragmentManager จะไม่ได้เป็นการสร้าง Instance ขึ้นมาใหม่ แต่ว่าจะเป็นการเรียกใช้งาน FragmentManager ที่มีอยู่แล้วใน Activity

        แต่ทว่าคำสั่งจะมีด้วยกันอยู่สองแบบนั่นก็คือ getFragmentManager() และ getSupportFragmentManager()


        ทั้งสองคำสั่งนี้จะได้ FragmentManager มาใช้งานเหมือนกันทั้งคู่ แต่ทว่าจะเป็น Fragment Manager สำหรับ Fragment คนละแบบกัน

        • getFragmentManager : เป็น Fragment Manager ของ Fragment API

        • getSupportFragmentManager : เป็น Fragment Manager ของ Support v4

        เนื่องจากเจ้าของบล็อกแนะนำให้ใช้ Fragment ของ Support v4 ดังนั้นจึงต้องใช้ getSupportFragmentManager() นั่นเอง ซึ่งคำสั่งนี้เดิมทีไม่ได้มีอยู่ในคลาส Activity นะ แต่ถูกเพิ่มเข้ามาในคลาส FragmentActivity ซึ่งคลาสที่สืบทอดจาก FragmentActivity ก็จะสามารถเรียกใช้งานได้ (รวมไปถึง AppCompatActivity)

        นั่นหมายความว่าคลาส Activity จะไม่สามารถใช้งาน Fragment ของ Support v4 ได้ จะต้องใช้ Activity ที่สืบทอดมาจาก FragmentActivity เท่านั้น

สรุปการใช้งาน Fragment ของ Support v4

        • คลาส Fragment จะเป็นของ android.support.v4.app.Fragment
        • Activity ที่เรียกใช้งาน Fragment จะต้องสืบทอดมาจาก FragmentActivity เท่านั้น
        • FragmentManager ต้องเรียกมาจากคำสั่ง getSupportFragmentManager() ที่อยู่ใน FragmentActivity
        • ในกรณีที่เรียก FragmentManager บนคลาส Fragment ของ Support v4 สามารถใช้คำสั่ง getFragmentManager() ได้เลย เพราะว่าบน Fragment ของ Support v4 จะได้ FragmentManager เป็นของ Support v4 อยู่แล้ว

มาดูที่คลาส Fragment กันต่อ

        ถึงแม้ว่า Fragment จะมีความคล้ายกับ Activity อยู่บ้าง แต่ก็มีหลายๆอย่างที่แตกต่างกัน (และควรรู้ไว้) โดยเฉพาะ Life Cycle ซึ่งสามารถอ่านเพิ่มเติมได้จาก [Android Code] Let's Fragment - วงจรชีวิตของ Fragment (Fragment Lifecycle) และควรเข้าใจการทำงาน Fragment เพื่อให้สามารถใช้งานได้ถูกต้องและยืดหยุ่นกับการนำไปใช้งานในรูปแบบต่างๆ

การกำหนด Layout และ View Binding ใน Fragment 

        โดยปกติการกำหนด Layout ให้กับ Activity นั้นจะใช้คำสั่ง setContentView(...) แต่พอเป็น Fragment นั้นจะใช้วิธีที่แตกต่างกันออกไป

        Fragment จะมี Override Method ที่ชื่อว่า onCreateView ให้ใช้งานอยู่แล้ว ซึ่งผู้ที่หลงเข้ามาอ่านจะต้องทำการกำหนด Layout ที่ต้องการ โดยใช้ LayoutInflater แบบนี้

AwesomeFragment.java
public class AwesomeFragment extends Fragment {
    
    ...

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_awesome, container, false);
    }
}

        และนักพัฒนาหลายๆคนกำหนด View ต่างๆที่ต้องการ ในนี้ไปด้วยเลย

AwesomeFragment.java
public class AwesomeFragment extends Fragment {
    private TextView tvTitle;
    private TextView tvWelcomeMessage;
    private Button btnConfirm;
    private Button btnCancel;
    private RecyclerView rvAwesomeInfo;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_awesome, container, false);
        tvTitle = (TextView) view.findViewById(R.id.tv_title);
        tvWelcomeMessage = (TextView) view.findViewById(R.id.tv_welcome_message);
        btnConfirm = (Button) view.findViewById(R.id.btn_confirm);
        btnCancel = (Button) view.findViewById(R.id.btn_cancel);
        rvAwesomeInfo = (RecyclerView) view.findViewById(R.id.rv_awesome_info);
        return view;
    }

    ...
    
}

        แต่สำหรับเจ้าของบล็อกจะชอบกำหนด View ใน onViewCreated(...) ซะมากกว่า เพราะเป็น Override Method ที่สื่อถึงความหมายได้ดีกว่า และโยน View ของ Layout ที่กำหนดไว้ใน onCreateView(...) มาให้ด้วยล่ะ

AwesomeFragment.java
public class AwesomeFragment extends Fragment {
    private TextView tvTitle;
    private TextView tvWelcomeMessage;
    private Button btnConfirm;
    private Button btnCancel;
    private RecyclerView rvAwesomeInfo;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_awesome, container, false);
        return inflater.inflate(R.layout.fragment_awesome, container, false);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        tvTitle = (TextView) view.findViewById(R.id.tv_title);
        tvWelcomeMessage = (TextView) view.findViewById(R.id.tv_welcome_message);
        btnConfirm = (Button) view.findViewById(R.id.btn_confirm);
        btnCancel = (Button) view.findViewById(R.id.btn_cancel);
        rvAwesomeInfo = (RecyclerView) view.findViewById(R.id.rv_awesome_info);
    }

    ...

}

        ดังนั้นเจ้าของบล็อกจะใช้ onCreateView(...) เพื่อกำหนด Layout ที่ต้องการ แล้วค่อยไปกำหนด View ที่จะเรียกใช้ใน onViewCreated(...) แทน

        แต่จะใช้วิธีไหนในการ Bind View ก็แล้วแต่จะชอบนะ เพราะแบบไหนก็ใช้ได้เหมือนกัน

สรุป

        ในการใช้งาน Fragment นั้น แนะนำให้ใช้ Fragment ของ Support v4 เนื่องจากเป็น Support Library ที่ทีมแอนดรอยด์ทำขึ้นมาเพื่อรองรับเวอร์ชันเก่าๆ ซึ่งในที่นี้ไม่ได้หมายถึงการรองรับเวอร์ชันที่ต่ำกว่า API 11 เท่านั้น แต่รวมไปถึงฟีเจอร์ใหม่และการแก้ไขบั๊กๆในเวอร์ชันที่เพิ่มเข้ามาในทีหลัง

        และเมื่อใช้ Fragment ของ Support v4 ก็หมายความว่า Activity ที่ใช้ก็จะต้องเป็น FragmentActivity ด้วย เพื่อให้สามารถใช้งาน SupportFragmentManager ได้

        ส่วนวิธีการเรียกใช้งาน Fragment นั้นขึ้นอยู่กับความเหมาะสมในการนำไปใช้งาน แต่ส่วนใหญ่จะใช้วิธีเรียกผ่านโค้ด Java มากกว่า เพื่อให้สามารถสั่งเปลี่ยน Fragment หลายๆตัวสลับไปมาได้ตามต้องการ

        ซึ่งการเรียกผ่านโค้ด Java นั้นจะต้องทำผ่าน FragmentTransaction เท่านั้น ซึ่งในบทความตอนถัดไปก็จะมาเจาะลึกเรื่องนี้กันครับ