เพื่อที่จะใช้งาน fragment UI อีก เราควรที่จะสร้างให้แต่ละ fragment มี container ของตัวเอง
มีองค์ประกอบ module ใน layout และชุดคำสั่งการทำงานของตัวเอง สิ่งหนึ่งที่เราต้องกำหนด
ให้กับ fragment ให้สามารถนำมาใช้ใหม่ คือ เราเชื่อม fragment กับ activity และสัมพันธ์กับ
เหตุผลของโปรแกรม ประกอบขึ้นมาเป็น UI ทั้งหมด
บ่อยครั้งที่เราต้องการที่จะสื่อสารระหว่าง fragment เช่น การเปลี่ยนแปลงเนื้อหาตามการ
กระทำของผู้ใช้ ทุกการเชื่อมต่อสื่อสารระหว่าง fragment จะทำผ่าน activity ที่เกี่ยวข้อง
fragment แต่ละอันจะไม่ติดต่อสื่อสารระหว่างกันโดยตรง
การกำหนด interface
เพื่ออนุญาตให้ fragmnt ติดต่อขึ้นยัง activity เราสามารถกำหนด interface ใน fragment class
และใช้มันใน activity การดักจับ interface ของ fragment จะอยู่ในขั้นตอนการเรียกใช้งาน onAttach()
method และสามารถเรียกใช้ method ของ interface เพื่อสื่อสารกับ activity
เรามาทดสอบ เพื่อศึกษา การสื่อสารระหว่าง fragment กับ activity
โดยจะขออ้างอิงจากเนื้อหาตอนที่แล้ว
เพิ่มความยืดหยุ่นการใช้งาน UI เรียกใช้ fragment จากใน activity ตอนที่ 2
https://www.ninenik.com/content.php?arti_id=629 via @ninenik
โดยรูปแบบการทำงานคือ เมื่อผู้ใช้ กดปุ่ม Button ที่อยู่ใน fragment ก็จะให้เรียกใช้งาน
method ที่อยู่ใน activity หลัก เพื่อทำงานเปลี่ยนข้อความใน TextView ที่อยู่ใน fragment อีกที
1. ที่ไฟล์ fragment_my.xml code เดิมจากตอนที่แล้ว
<TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="Dynamic Fragment" xmlns:android="http://schemas.android.com/apk/res/android" />
เปลี่ยนใหม่ โดย จะมี layout view , TextView และก็ Button ตามนี้
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/placeText1" android:layout_width="match_parent" android:layout_height="match_parent" android:text="Dynamic Fragment" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="SET TEXT" /> </LinearLayout>
2. มาที่ fragment class เราจะกำหนด interface เข้าไป
code เดิม
package com.example.ninenik.study003; import android.os.Bundle; import android.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; /** * A simple {@link Fragment} subclass. */ public class myFragment extends Fragment { public myFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_my, container, false); } }
ปรับใหม่ โดยเพิ่ม แต่ละส่วนดังนี้
// กำหนด mCallback เป็น instance ของ onChangeTextListener interface onChangeTextListener mCallback;
// กำหนด interface ชื่อ onChangeTextListener // Activity ต้องทำการ implement interface นี้ public interface onChangeTextListener { // กำหนด method ของ interface ใช้ชื่อเดียวกับที่จะกำหนดใน activity // โดย method นี้จะรับค่า parameter ที่เป็นข้อความ public void changeText(String myText); }
@Override public void onAttach(Activity activity) { super.onAttach(activity); // ตรวจสอบให้มั่นใจว่า activity ว่า activity ได้ทำการ implement แล้วหรือไม่ // ถ้าได้ทำการ implement แล้วจะส่ง interface กลับมา try { mCallback = (onChangeTextListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement onChangeTextListener"); } }
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // ขยายแสดง layout ของ fragment จาก code เดิมเราจะ return ค่าเลย แต่ครั้งนี้ // เราจะรับค่าไว้ใน instance ชื่อ v ก่อน แล้ว ค่อย return ค่าที่หลัง // โดย instance v จะใช้ในการระบุ อ้างอิงค่า view อื่นๆ ใน layout ด้วย findViewById() View v = inflater.inflate(R.layout.fragment_my, container, false); // กำหนด instance ให้กับปุ่ม Button myButton1 = (Button) v.findViewById(R.id.button1); // กำหนดให้ปุ่มรอรับการทำงานจากการ click myButton1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // เมื่อ view ถูก click // ใช้ instance ของ interface เรียกใช้งาน changeText() method // ใน activity ที่ได้ทำการ implement แล้ว โดยจะส่งข้อความว่า "hello world" mCallback.changeText("Hello World"); } }); return v; }
จะได้ myFragment class ทั้งหมดดังนี้
package com.example.ninenik.study003; import android.app.Activity; import android.os.Bundle; import android.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; /** * A simple {@link Fragment} subclass. */ public class myFragment extends Fragment { // กำหนด mCallback เป็น instance ของ onChangeTextListener interface onChangeTextListener mCallback; // กำหนด interface ชื่อ onChangeTextListener // Activity ต้องทำการ implement interface นี้ public interface onChangeTextListener { // กำหนด method ของ interface ใช้ชื่อเดียวกับที่จะกำหนดใน activity // โดย method นี้จะรับค่า parameter ที่เป็นข้อความ public void changeText(String myText); } @Override public void onAttach(Activity activity) { super.onAttach(activity); // ตรวจสอบให้มั่นใจว่า activity ว่า activity ได้ทำการ implement แล้วหรือไม่ // ถ้าได้ทำการ implement แล้วจะส่ง interface กลับมา try { mCallback = (onChangeTextListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement onChangeTextListener"); } } public myFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // ขยายแสดง layout ของ fragment จาก code เดิมเราจะ return ค่าเลย แต่ครั้งนี้ // เราจะรับค่าไว้ใน instance ชื่อ v ก่อน แล้ว ค่อย return ค่าที่หลัง // โดย instance v จะใช้ในการระบุ อ้างอิงค่า view อื่นๆ ใน layout ด้วย findViewById() View v = inflater.inflate(R.layout.fragment_my, container, false); // กำหนด instance ให้กับปุ่ม Button myButton1 = (Button) v.findViewById(R.id.button1); // กำหนดให้ปุ่มรอรับการทำงานจากการ click myButton1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // เมื่อ view ถูก click // ใช้ instance ของ interface เรียกใช้งาน changeText() method // ใน activity ที่ได้ทำการ implement แล้ว โดยจะส่งข้อความว่า "hello world" mCallback.changeText("Hello World"); } }); return v; } }
3. ทำการ implement interface ที่ MainActivity โดย code Mainctivity เดิมจะเป็นดังนี้
package com.example.ninenik.study003; import android.support.v4.app.FragmentActivity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.widget.TextView; //ActionBarActivity public class MainActivity extends FragmentActivity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // ตรวจสอบว่า activity มีการเรียกใช้งาน layout ที่มีการกำหนด // FrameLayout เป็น fragment container จาก id ชื่อ fragment_container if (findViewById(R.id.fragment_container) != null) { // ถ้ามีการกำหนด // กรณีถ้า, ถ้ามีการกู้คืนค่าจาก state ก่อนหน้า, // แล้วเราไม่ต้องทำอะไร และให้ return ค่า if (savedInstanceState != null) { return; } // สร้าง fragment เพื่อใส่เข้าไปใน activity layout // myFragment คือชื่อ fragment activity ที่เราสร้างในตอนที่แล้ว // เราตั้งชื่อ instance ของ fragment ใหม่ชื่อ FragmentA myFragment FragmentA = new myFragment(); // ในกรณีที่ activity นี้ มีการการรับค่าจาก intent // ให้ส่งค่าจาก intent ไปยัง fragment ด้วยการกำหนด arguments FragmentA.setArguments(getIntent().getExtras()); // แทนที่ fragment ไปใน 'fragment_container' FrameLayout getFragmentManager().beginTransaction() .add(R.id.fragment_container, FragmentA) .commit(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
ให้ปรับเพิ่มแต่ละส่วนตามนี้
// ทำการ implement โดยต่อด้วย implements myFragment.onChangeTextListener // รูปแบบ implements [ชื่อ fragment].[ชื่อ instance interface] // จะได้เป็น implements myFragment.onChangeTextListener public class MainActivity extends FragmentActivity implements myFragment.onChangeTextListener{ .............. ......
// method ที่เรียกใช้งานจาก fragment public void changeText(String myText){ // เก็บ fragment instance ในชื่อ FragmentB ถ้ามีการเรียกใช้งาน fragment แล้ว myFragment FragmentB = (myFragment) getFragmentManager().findFragmentById(R.id.fragment_container); // ตรวจสอบว่ามี fragment ชื่อ instance ว่า FragmentB หรือไม่ if (FragmentB != null) { // มีการกำหนด fragment แล้ว // กำหนดการทำงานของ method ตามต้องการ ในที่นี้เราจะกำหนดข้อความใน TextView ใน fragment // โดยที่เมื่อได้รับค่าที่ส่งเข้ามาแล้วใน method เมื่อกดที่ปุ่ม Button แล้วก็ให้แสดงค่าใน TextView // สร้าง instance ของ TextView สังเกตเราจะใช้ FragmentB.getView() ก่อนจะเรียก findViewById() // ทั้งนี้เพราะเป็นการไปใช้งานกับ fragment TextView myTextView = (TextView) FragmentB.getView().findViewById(R.id.placeText1); myTextView.setTextSize(40); myTextView.setText(myText); } else { // สร้าง fragment เพื่อใส่เข้าไปใน activity layout // myFragment คือชื่อ fragment activity ที่เราสร้างในตอนที่แล้ว // เราตั้งชื่อ instance ของ fragment ใหม่ชื่อ FragmentA myFragment FragmentA = new myFragment(); // ในกรณีที่ activity นี้ มีการการรับค่าจาก intent // ให้ส่งค่าจาก intent ไปยัง fragment ด้วยการกำหนด arguments FragmentA.setArguments(getIntent().getExtras()); // แทนที่ fragment ไปใน 'fragment_container' FrameLayout getFragmentManager().beginTransaction() .replace(R.id.fragment_container, FragmentA) .addToBackStack(null) .commit(); } }
จะได้ code ของ MainActivity ทั้งหมดดังนี้
package com.example.ninenik.study003; import android.support.v4.app.FragmentActivity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.widget.TextView; //ActionBarActivity public class MainActivity extends FragmentActivity implements myFragment.onChangeTextListener{ // method ที่เรียกใช้งานจาก fragment public void changeText(String myText){ // เก็บ fragment instance ในชื่อ FragmentB ถ้ามีการเรียกใช้งาน fragment แล้ว myFragment FragmentB = (myFragment) getFragmentManager().findFragmentById(R.id.fragment_container); // ตรวจสอบว่ามี fragment ชื่อ instance ว่า FragmentB หรือไม่ if (FragmentB != null) { // มีการกำหนด fragment แล้ว // กำหนดการทำงานของ method ตามต้องการ ในที่นี้เราจะกำหนดข้อความใน TextView ใน fragment // โดยที่เมื่อได้รับค่าที่ส่งเข้ามาแล้วใน method เมื่อกดที่ปุ่ม Button แล้วก็ให้แสดงค่าใน TextView // สร้าง instance ของ TextView สังเกตเราจะใช้ FragmentB.getView() ก่อนจะเรียก findViewById() // ทั้งนี้เพราะเป็นการไปใช้งานกับ fragment TextView myTextView = (TextView) FragmentB.getView().findViewById(R.id.placeText1); myTextView.setTextSize(40); myTextView.setText(myText); } else { // สร้าง fragment เพื่อใส่เข้าไปใน activity layout // myFragment คือชื่อ fragment activity ที่เราสร้างในตอนที่แล้ว // เราตั้งชื่อ instance ของ fragment ใหม่ชื่อ FragmentA myFragment FragmentA = new myFragment(); // ในกรณีที่ activity นี้ มีการการรับค่าจาก intent // ให้ส่งค่าจาก intent ไปยัง fragment ด้วยการกำหนด arguments FragmentA.setArguments(getIntent().getExtras()); // แทนที่ fragment ไปใน 'fragment_container' FrameLayout getFragmentManager().beginTransaction() .replace(R.id.fragment_container, FragmentA) .addToBackStack(null) .commit(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // ตรวจสอบว่า activity มีการเรียกใช้งาน layout ที่มีการกำหนด // FrameLayout เป็น fragment container จาก id ชื่อ fragment_container if (findViewById(R.id.fragment_container) != null) { // ถ้ามีการกำหนด // กรณีถ้า, ถ้ามีการกู้คืนค่าจาก state ก่อนหน้า, // แล้วเราไม่ต้องทำอะไร และให้ return ค่า if (savedInstanceState != null) { return; } // สร้าง fragment เพื่อใส่เข้าไปใน activity layout // myFragment คือชื่อ fragment activity ที่เราสร้างในตอนที่แล้ว // เราตั้งชื่อ instance ของ fragment ใหม่ชื่อ FragmentA myFragment FragmentA = new myFragment(); // ในกรณีที่ activity นี้ มีการการรับค่าจาก intent // ให้ส่งค่าจาก intent ไปยัง fragment ด้วยการกำหนด arguments FragmentA.setArguments(getIntent().getExtras()); // แทนที่ fragment ไปใน 'fragment_container' FrameLayout getFragmentManager().beginTransaction() .add(R.id.fragment_container, FragmentA) .commit(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
เป็นอันเสร็จการเชื่อมต่อสื่อสารระกว่าง fragment กับ activity
4. ทดสอบ run app จะได้ผลลัพธ์ ดังนี้