ฉันจะเพิ่ม Fragment ให้กับกิจกรรมด้วยมุมมองเนื้อหาที่สร้างโดยทางโปรแกรมได้อย่างไร


240

ฉันต้องการเพิ่ม Fragment ให้กับกิจกรรมที่ใช้เลย์เอาต์ของมันแบบเป็นโปรแกรม ฉันดูเอกสารชิ้นส่วน แต่ไม่มีตัวอย่างมากมายที่อธิบายถึงสิ่งที่ฉันต้องการ นี่คือประเภทของรหัสที่ฉันพยายามเขียน:

public class DebugExampleTwo extends Activity {

    private ExampleTwoFragment mFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FrameLayout frame = new FrameLayout(this);
        if (savedInstanceState == null) {
            mFragment = new ExampleTwoFragment();
            FragmentTransaction ft = getFragmentManager().beginTransaction();
            ft.add(frame.getId(), mFragment).commit();
        }

        setContentView(frame);
    }
}

...

public class ExampleTwoFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, 
                             ViewGroup container, 
                             Bundle savedInstanceState) {
        Button button = new Button(getActivity());
        button.setText("Hello There");
        return button;
    }
}

รหัสนี้รวบรวม แต่ล้มเหลวในการเริ่มต้นอาจเป็นเพราะฉันFragmentTransaction.add()ไม่ถูกต้อง วิธีที่ถูกต้องในการทำเช่นนี้คืออะไร?

คำตอบ:


198

ปรากฎว่ามีปัญหามากกว่าหนึ่งรหัส ไม่สามารถประกาศแฟรกเมนต์ด้วยวิธีนั้นภายในไฟล์ java เดียวกันกับกิจกรรม แต่ไม่เหมือนกับคลาสภายในสาธารณะ เฟรมเวิร์กคาดว่าตัวสร้างของแฟรกเมนต์ (ไม่มีพารามิเตอร์) ให้เป็นแบบสาธารณะและสามารถมองเห็นได้ การย้ายแฟรกเมนต์ไปยังกิจกรรมเป็นคลาสภายในหรือสร้างไฟล์ java ใหม่สำหรับการแก้ไขแฟรกเมนต์ที่

ปัญหาที่สองคือเมื่อคุณเพิ่มส่วนด้วยวิธีนี้คุณต้องผ่านการอ้างอิงไปยังมุมมองที่มีชิ้นส่วนและมุมมองนั้นต้องมีรหัสที่กำหนดเอง การใช้รหัสเริ่มต้นจะทำให้แอปเสียหาย นี่คือรหัสปรับปรุง:

public class DebugExampleTwo extends Activity {

    private static final int CONTENT_VIEW_ID = 10101010;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FrameLayout frame = new FrameLayout(this);
        frame.setId(CONTENT_VIEW_ID);
        setContentView(frame, new LayoutParams(
            LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

        if (savedInstanceState == null) {
            Fragment newFragment = new DebugExampleTwoFragment();
            FragmentTransaction ft = getFragmentManager().beginTransaction();
            ft.add(CONTENT_VIEW_ID, newFragment).commit();
        }
    }

    public static class DebugExampleTwoFragment extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            EditText v = new EditText(getActivity());
            v.setText("Hello Fragment!");
            return v;
        }
    }
}

115
ft.add(android.R.id.content, newFragment)ถ้าคุณเพียงต้องการที่จะใช้ชิ้นส่วนในขณะที่มุมมองเนื้อหาระดับบนสุดของกิจกรรมแล้วคุณสามารถใช้ จำเป็นต้องสร้างโครงร่างแบบกำหนดเองและตั้งค่า id หากคอนเทนเนอร์ของแฟรกเมนต์ไม่ใช่มุมมองเนื้อหาของกิจกรรม
Tony Wong

25
แทนที่จะเขียนรหัสยากคุณสามารถกำหนดมันใน XMLและอ้างอิงตามปกติ (R.id.myid)
Jason Hanley

1
ฉันไม่ทราบวิธีการทำเช่นนั้น แต่จำไว้ว่ารหัสจะต้องไม่ซ้ำกันในขอบเขตที่คุณต้องใช้
Jason Hanley

2
id จะต้องไม่ซ้ำกันในระดับภายใน heirarchy ปัจจุบันของเค้าโครงที่มี ดังนั้นถ้ามันถูกห่อในเลย์เอาต์แบบเชิงเส้นมันจะต้องไม่เป็นเท็จในมุมมองอื่น ๆ ภายในเลย์เอาต์เชิงเส้นนั้น
Shaun

1
คุณสามารถสร้าง ID แบบไดนามิกโดยใช้ setId (View.NO_ID) จากนั้น getId () เพื่อดูว่ามันคืออะไร
enl8enmentnow

71

นี่คือสิ่งที่ฉันคิดขึ้นหลังจากได้อ่านความคิดเห็นของ Tony Wong :

public class DebugExampleTwo extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addFragment(android.R.id.content,
                    new DebugExampleTwoFragment(),
                    DebugExampleTwoFragment.FRAGMENT_TAG);
    }

}

...

public abstract class BaseActivity extends Activity {

    protected void addFragment(@IdRes int containerViewId,
                               @NonNull Fragment fragment,
                               @NonNull String fragmentTag) {
        getSupportFragmentManager()
                .beginTransaction()
                .add(containerViewId, fragment, fragmentTag)
                .disallowAddToBackStack()
                .commit();
    }

    protected void replaceFragment(@IdRes int containerViewId,
                                   @NonNull Fragment fragment,
                                   @NonNull String fragmentTag,
                                   @Nullable String backStackStateName) {
        getSupportFragmentManager()
                .beginTransaction()
                .replace(containerViewId, fragment, fragmentTag)
                .addToBackStack(backStackStateName)
                .commit();
    }

}

...

public class DebugExampleTwoFragment extends Fragment {

    public static final String FRAGMENT_TAG = 
        BuildConfig.APPLICATION_ID + ".DEBUG_EXAMPLE_TWO_FRAGMENT_TAG";

    // ...

}

Kotlin

หากคุณกำลังใช้ Kotlin ตรวจสอบให้แน่ใจว่าGoogle ให้ส่วนขยาย Kotlin เป็นอย่างไรหรือเขียนเอง


อย่าทำอย่างนั้น! ตรวจสอบif (savedInstanceState == null)ก่อนที่จะสร้างชิ้นส่วนหรือหลังจากหมุนหน้าจอคุณจะมีสองส่วนหรือเรียงลำดับชิ้นส่วนใหม่ อย่าใช้addวิธีเลย! replaceเท่านั้น หรือคุณจะมีพฤติกรรมแปลก ๆ
CoolMind

คุณจะได้รับค่าสำหรับ "backStackStateName" ที่ไหน? (เมื่อใช้ฟังก์ชันแทนที่)
vikzilla

@vikzilla คุณสามารถหาคำตอบที่ดีงามที่นี่และในเอกสาร กล่าวโดยย่อ: backStackStateNameสตริงคือสิ่งที่คุณกำหนด
JJD

34
    public class Example1 extends FragmentActivity {

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
          DemoFragment fragmentDemo = (DemoFragment) 
          getSupportFragmentManager().findFragmentById(R.id.frame_container);
          //above part is to determine which fragment is in your frame_container
          setFragment(fragmentDemo);
                       (OR)
          setFragment(new TestFragment1());
        }

        // This could be moved into an abstract BaseActivity 
        // class for being re-used by several instances
        protected void setFragment(Fragment fragment) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = 
                fragmentManager.beginTransaction();
            fragmentTransaction.replace(android.R.id.content, fragment);
            fragmentTransaction.commit();
        }
    }

ในการเพิ่มแฟรกเมนต์ลงในกิจกรรมหรือ FramentActivity จะต้องมีคอนเทนเนอร์ คอนเทนเนอร์นั้นควรเป็น " Framelayout" ซึ่งสามารถรวมอยู่ใน xml หรือคุณสามารถใช้คอนเทนเนอร์เริ่มต้นสำหรับสิ่งนั้นเช่น " android.R.id.content" เพื่อลบหรือแทนที่ส่วนในกิจกรรม

main.xml

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 <!-- Framelayout to display Fragments -->
   <FrameLayout
        android:id="@+id/frame_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageView
        android:id="@+id/imagenext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_margin="16dp"
        android:src="@drawable/next" />
</RelativeLayout>

29

หลังจากอ่านคำตอบทั้งหมดฉันได้วิธีที่สง่างาม:

public class MyActivity extends ActionBarActivity {

 Fragment fragment ;

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

    FragmentManager fm = getSupportFragmentManager();
    fragment = fm.findFragmentByTag("myFragmentTag");
    if (fragment == null) {
        FragmentTransaction ft = fm.beginTransaction();
        fragment =new MyFragment();
        ft.add(android.R.id.content,fragment,"myFragmentTag");
        ft.commit();
    }

}

โดยพื้นฐานแล้วคุณไม่จำเป็นต้องเพิ่ม frameLayout เป็นคอนเทนเนอร์ของแฟรกเมนต์ของคุณแทนคุณสามารถเพิ่มแฟรกเมนต์ลงในคอนเทนเนอร์รากของ Android โดยตรง

สิ่งสำคัญ: ไม่ได้ใช้แทนที่ส่วนเป็นส่วนใหญ่ของวิธีการแสดงที่นี่ถ้าคุณไม่คิดที่จะสูญเสียส่วนรัฐเช่นตัวแปรในช่วงonrecreationกระบวนการ


ขอบคุณสำหรับคำตอบนี้จะเพิ่มแท็บส่วนย่อยให้เต็มหน้าจอหรือไม่ แต่คุณจะเพิ่มโครงร่างหรือดูเพจเจอร์ได้อย่างไร?
flankechen

6
public abstract class SingleFragmentActivity extends Activity {

    public static final String FRAGMENT_TAG = "single";
    private Fragment fragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
        if (savedInstanceState == null) {
            fragment = onCreateFragment();
           getFragmentManager().beginTransaction()
                   .add(android.R.id.content, fragment, FRAGMENT_TAG)
                   .commit();
       } else {
           fragment = getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
       }
   }

   public abstract Fragment onCreateFragment();

   public Fragment getFragment() {
       return fragment;
   }

}

ใช้

public class ViewCatalogItemActivity extends SingleFragmentActivity {
    @Override
    public Fragment onCreateFragment() {
        return new FragmentWorkShops();
    }

}

6

สำหรับAPI ระดับ 17หรือสูงกว่าView.generateViewId()จะแก้ปัญหานี้ได้ เมธอดยูทิลิตี้จัดเตรียม id เฉพาะที่ไม่ได้ใช้ในเวลา build


3
ยินดีต้อนรับสู่ Stack Overflow! ในขณะที่สิ่งนี้อาจตอบคำถามในทางทฤษฎีมันก็ควรที่จะรวมส่วนสำคัญของคำตอบที่นี่และให้ลิงค์สำหรับการอ้างอิง
SuperBiasedMan

3

สำหรับการแนบแฟรกเมนต์เข้ากับกิจกรรมโดยทางโปรแกรมใน Kotlin คุณสามารถดูโค้ดต่อไปนี้:

MainActivity.kt

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

            // create fragment instance
            val fragment : FragmentName = FragmentName.newInstance()

            // for passing data to fragment
            val bundle = Bundle()
            bundle.putString("data_to_be_passed", DATA)
            fragment.arguments = bundle

            // check is important to prevent activity from attaching the fragment if already its attached
            if (savedInstanceState == null) {
                supportFragmentManager
                    .beginTransaction()
                    .add(R.id.fragment_container, fragment, "fragment_name")
                    .commit()
            }
        }

    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.MainActivity">

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

FragmentName.kt

class FragmentName : Fragment() {

    companion object {
        fun newInstance() = FragmentName()
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        // receiving the data passed from activity here
        val data = arguments!!.getString("data_to_be_passed")
        return view
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
    }

}

หากคุณคุ้นเคยกับExtensionsใน Kotlin คุณสามารถปรับปรุงโค้ดนี้ให้ดียิ่งขึ้นโดยทำตามสิ่งนี้บทความ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.