ฉันจะเปลี่ยนมุมมองเนื้อหาเมื่อเลือกแท็บอื่นได้อย่างไร (ตัวอย่าง Android HoneyCombGallery ที่เกี่ยวข้อง)

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

นี่คือโค้ดที่ฉันแก้ไขในตัวอย่าง HoneyCombGallery: (MainActivity.java)

public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
    int nTabSelected = tab.getPosition();
    switch (nTabSelected) {
    case 0:
        setContentView(R.layout.settings_fragment);
        break;
    case 1:
        setContentView(R.layout.main);
        break;
    }

อย่างอื่นก็ค่อนข้างเหมือนกันหมด (โปรดทราบว่าเช่นก่อนหน้านี้ MainActivity.onCreate เรียก setContentView(R.layout.main) - ฉันไม่ได้ยินมาว่าไม่อนุญาตให้เรียก setContentView หลายครั้ง ฉันตั้งใจที่จะทำเช่นนั้นในแต่ละแท็บคลิกต่อไป)

และนี่คือเลย์เอาต์ที่ถูกขยายในโค้ด (settings_fragment.xml)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/frags2">
    <fragment class="com.example.android.hcgallery.SettingsFragment"
            android:id="@+id/settings_fragment2"
            android:visibility="gone"
            android:layout_marginTop="?android:attr/actionBarSize"
            android:layout_width="@dimen/titles_size"
            android:layout_height="match_parent" />
</LinearLayout>

"ส่วนการตั้งค่า" คือคลาสของฉันที่ขยาย Fragment: (SettingsFragment.java)

public class SettingsFragment extends Fragment {
    private View v;
    @Override
    public View onCreateView(final LayoutInflater inflater,
            final ViewGroup container, final Bundle savedInstanceState) {

        if (v != null)
            return v;
        v = inflater.inflate(R.layout.settings_layout, container, false);
        return v;
    }
}

xml นั้นตรงไปตรงมาเช่นกัน (settings_layout.xml):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/settings_layout" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingTop="10dip">

    <TextView android:text="1.0.0" android:id="@+id/tv_version2"
        android:layout_marginTop="?android:attr/actionBarSize"
        android:textSize="15dp" android:textStyle="bold" android:typeface="sans"
        android:layout_height="wrap_content" android:layout_width="200dp"
        android:paddingTop="10dip" android:gravity="right"  >
    </TextView>
</RelativeLayout>

ดูเหมือนตรงไปตรงมาและเมื่อแอปเริ่มต้น settings_fragment จะปรากฏให้เห็นเมื่อเลือกแท็บที่ 0 ตามค่าเริ่มต้น เมื่อเราเลือกแท็บถัดไป ฉันคาดหวังว่า setContentView(R.layout.main) จะถูกดำเนินการและฉันจะเห็นเลย์เอาต์หลัก (นี่เหมือนกับในตัวอย่างของ Google: ไม่มีการแก้ไข) อย่างไรก็ตาม สิ่งที่ฉันได้รับคือข้อยกเว้นนี้:

12-15 16:49:28.501: ERROR/AndroidRuntime(31485): FATAL EXCEPTION: main
12-15 16:49:28.501: ERROR/AndroidRuntime(31485): android.view.InflateException: Binary XML file line #23: Error inflating class fragment
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:688)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.LayoutInflater.rInflate(LayoutInflater.java:724)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.LayoutInflater.inflate(LayoutInflater.java:479)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.LayoutInflater.inflate(LayoutInflater.java:391)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.LayoutInflater.inflate(LayoutInflater.java:347)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:224)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.app.Activity.setContentView(Activity.java:1777)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at com.example.android.hcgallery.MainActivity.onTabSelected(MainActivity.java:109)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at com.android.internal.app.ActionBarImpl.selectTab(ActionBarImpl.java:462)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at com.android.internal.app.ActionBarImpl$TabImpl.select(ActionBarImpl.java:787)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at com.android.internal.widget.ActionBarView$TabClickListener.onClick(ActionBarView.java:950)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.View.performClick(View.java:3100)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.View$PerformClick.run(View.java:11644)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.os.Handler.handleCallback(Handler.java:587)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.os.Handler.dispatchMessage(Handler.java:92)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.os.Looper.loop(Looper.java:126)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.app.ActivityThread.main(ActivityThread.java:3997)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at java.lang.reflect.Method.invokeNative(Native Method)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at java.lang.reflect.Method.invoke(Method.java:491)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at dalvik.system.NativeStart.main(Native Method)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485): Caused by: java.lang.IllegalArgumentException: Binary XML file line #23: Duplicate id 0x7f09000a, tag null, or parent id 0x7f090009 with another fragment for com.example.android.hcgallery.TitlesFragment
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.app.Activity.onCreateView(Activity.java:4095)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:664)
12-15 16:49:28.501: ERROR/AndroidRuntime(31485):     ... 21 more

ดูเหมือนว่าข้อผิดพลาดจะเป็น "android.view.InflateException: บรรทัดไฟล์ XML ไบนารี #23: เกิดข้อผิดพลาดในการขยายส่วนของคลาส"

สิ่งนี้หมายความว่า? ฉันไม่สามารถ setContentView เพื่อเปลี่ยนแฟรกเมนต์และเลย์เอาต์ตามที่ฉันต้องการเมื่อเลือกแท็บอื่นได้หรือไม่ หากนี่ไม่ใช่วิธีที่ถูกต้องในการจัดเลย์เอาต์ที่แตกต่างกันสำหรับแท็บต่างๆ จะต้องทำอย่างไร?

ฉันมีซอร์สโค้ดทั้งหมดที่อัปโหลดไปยัง HoneycombGallery_Modified.zip - https://docs.google.com/open?id=0B0FHtQOpOYx6YWFiMDcxYmEtOGFkMy00NWU2LTk2MzMtZWY0YzdmZWUzMDU0 (คลิกที่ "ไฟล์->ดาวน์โหลดต้นฉบับ" หากคุณต้องการดาวน์โหลดไฟล์ zip ทั้งหมด)


person Community    schedule 15.12.2011    source แหล่งที่มา


คำตอบ (3)


คุณควรอ่าน เอกสารสำหรับนักพัฒนาซอฟต์แวร์เกี่ยวกับ Fragments และพิจารณา การทำธุรกรรมการแยกส่วน

person kaspermoerch    schedule 15.12.2011
comment
ฉันได้อ่านคำแนะนำแฟรกเมนต์แล้ว แต่ทำไม setContentView() ถึงไม่ทำงาน มันใช้งานได้ครั้งแรกเมื่อเลือกแท็บที่ 0 เหตุใดจึงล้มเหลวเป็นครั้งที่สองเมื่อเลือกแท็บถัดไป เราไม่สามารถทำ setContentView บนไฟล์ XML เดียวกันเป็นครั้งที่สองได้หรือไม่? - person ; 15.12.2011
comment
ดูเหมือนว่าเอกสาร Android จะไม่บอกอย่างชัดเจนว่าคุณไม่สามารถโทร setContentView สองครั้งภายใน Activity อย่างไรก็ตาม ฉันจะงดเว้นจากการทำเช่นนี้อาจทำให้คลาส Activity ของคุณหยดลงด้วย ถ้าอย่างนั้น Fragments ก็เป็นหนทางที่ดีกว่า - person kaspermoerch; 15.12.2011
comment
โอเคขอบคุณ. หากฉันใช้ Fragment ฉันจะพบปัญหานี้: เมื่อหมุนแท็บเล็ต สิ่งเหล่านี้จะเกิดขึ้น: MainActivity ถูกสร้างขึ้น แท็บจะถูกเลือกอีกครั้ง และ Android ก็สร้าง Fragments ของฉันขึ้นมาใหม่ด้วย อย่างไรก็ตาม MainActivity และตรรกะ tabSelected ยังพยายามสร้างแฟรกเมนต์เดียวกันกับที่เคยทำเมื่อเรียกใช้ ap เป็นครั้งแรก ฉันสามารถมั่นใจได้ว่าฉันไม่ได้สร้างใน MainActivity onCreate โดยดูที่ SavedInstanceState แต่สุดท้ายเราจะสร้างมันขึ้นมาใน onTabSelected ดังนั้นเมื่อหมุน เราจะจบลงด้วยการสร้างชิ้นส่วนสองครั้ง มีวิธีหลีกเลี่ยงสิ่งนี้หรือไม่? - person ; 15.12.2011
comment
ในที่สุดฉันก็ใช้ Fragments และแก้ไขปัญหานี้แล้ว ดังนั้นถือว่าคนแรกที่ขอให้ฉันใช้ Fragment เป็นคำตอบที่ถูกต้อง - person ; 08.01.2012

ฉันประสบความสำเร็จในการบรรลุสิ่งที่คุณต้องการ

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

ฉันมีแท็บ 2 แบบ:

  • แท็บเพื่อแสดงรายการที่มีส่วนเพื่อแสดงเนื้อหาของรายการที่เลือกในรายการ (นั่นคือตัวอย่างทั่วไปของส่วน)
  • แท็บเพื่อแสดง webView แบบธรรมดา

ฉันทำดังนี้ ฉันมีคลาสชื่อ ActionBarActivity ที่เริ่มต้นแท็บและมี TabListener

นี่คือ ActionBarActivity ของฉัน:

public class ActionBarActivity
        extends SherlockFragmentActivity
{
    /**
    * The current layoutId on the screen
    */
    private int layoutId;
    protected final int LAYOUT_LIST = R.layout.list_container;
    protected final int LAYOUT_WEBVIEW = R.layout.webview_container;

    @Override
    public void onCreate( Bundle savedInstanceState )
    {
        super.onCreate( savedInstanceState );
        ActionBar actionBar = getSupportActionBar();
        actionBar.setNavigationMode( ActionBar.NAVIGATION_MODE_TABS );
        getSupportActionBar().removeAllTabs();
        // we pass the layout to use for each tab
        addTab("Tab 1", LAYOUT_LIST, ListFragment.class);
        addTab("Tab 2", LAYOUT_WEBVIEW, WebViewFragment.class);

        // in case of screen orientation
        if ( savedInstanceState != null ) {
            actionBar.setSelectedNavigationItem( savedInstanceState.getInt( "tab", 0 ) );
        }
        // initial setting of the content 
        updateContentView( actionBar.getSelectedTab() );

    }

    private void addTab( String title, int contentLayoutId, Class<? extends Fragment> tabClass )
    {
        Tab t = getSupportActionBar().newTab();
        t.setText( tabTitle );
        t.setTag( contentLayoutId );
        t.setTabListener( new TabListener( this, tabTitle, activity, null ) );
        getSupportActionBar().addTab( t );
    }

    // that's how I deal to replace the layout used to display the tab's content 
    protected void updateContentView( Tab t )
    {
        FragmentManager fgtManager = getSupportFragmentManager();
        int tabLayoutId = ( Integer ) t.getTag();

        if ( tabLayoutId == LAYOUT_WEBVIEW ) {

            Fragment details = fgtManager.findFragmentById( R.id.detail_fragment);

            if(details != null && details.isInLayout()){
                FragmentTransaction transact = fgtManager.beginTransaction();
                transact.remove( details );
                transact.commit();
                details = null;
            }
        }

        if ( this.layoutId != tabLayoutId ) {
            layoutId = tabLayoutId;
            setContentView( layoutId );
        }
    }

    @Override
    protected void onSaveInstanceState( Bundle outState )
    {
        super.onSaveInstanceState( outState );
        outState.putInt( "tab", getSupportActionBar().getSelectedNavigationIndex() );
    }

    // see APIDemos.app.FragmentTabs for more details 
    public static class TabListener
            implements ActionBar.TabListener
    {
        private SherlockFragmentActivity mActivity;

        public void onTabSelected( Tab tab, FragmentTransaction ft )
        {
            // When we select a tab we udpate the content of the ActionBarActivity 
            ((ActionBarActivity)mActivity).updateContentView( tab );

            mFragment = mActivity.getSupportFragmentManager().findFragmentByTag( mTag );
            if ( mFragment == null ) {
                mFragment = Fragment.instantiate( mActivity, mClass.getName(), mArgs );
                ft.add( R.id.content_fragment, mFragment, mTag );
            } else {
                ft.attach( mFragment );
            }
        }
    }
}

และสองเลย์เอาต์ของฉัน:

โครงร่าง/list_container.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="horizontal">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="0.6"
        android:id="@+id/content_fragment"/>
    <fragment
        android:id="@+id/detail_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="0.4"
        class="my.webview.WebViewFragment">
    </fragment>
</LinearLayout>

โครงร่าง/webview_container.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
        <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:id="@+id/content_fragment"/>
</LinearLayout>    

ป.ล. นี่เป็นผลงานครั้งแรกของฉันดังนั้นฉันยินดีที่จะมีคำวิจารณ์บ้าง

PPS: ฉันเป็นคนฝรั่งเศส ฉันอาจลืมข้อผิดพลาดภาษาอังกฤษไปบ้าง ขออภัยด้วย

person PierreB    schedule 17.08.2012

ฉันไม่คิดว่าคุณควรตั้งค่า ContentView อีกครั้ง สามารถตั้งค่าได้เพียงครั้งเดียวเท่านั้น ตั้งค่าแฟรกเมนต์ที่เหมาะสมลงในคอนเทนเนอร์แฟรกเมนต์โดยใช้แฟรกเมนต์Transaction.replace()

person Rajdeep Dua    schedule 15.12.2011
comment
ดังที่ฉันได้กล่าวไว้ในความคิดเห็นก่อนหน้านี้ ในการหมุนแท็บเล็ต FragmentTransaction.replace จะถูกเรียก (ตามที่ onTabSelected จะถูกเรียก) จากนั้น Android จะสร้างแฟรกเมนต์ของฉันขึ้นมาใหม่ด้วย ดังนั้นชิ้นส่วนของฉันจะถูกสร้างขึ้นสองครั้ง ฉันสามารถหลีกเลี่ยงสิ่งนี้ได้หรือไม่? - person ; 15.12.2011