碎片概述(Fragments Overview)----(1)

詳細參考:http://developer.android.com/reference/android/app/Fragment.html



java.lang.Object
   ↳ android.app.Fragment
已知的直接子類


碎片是一段兒應用程序界面或行爲,它可被植入活動內。與碎片的交互是通過FragmentManager來進行的,它可由Activity.getFragmentManager()Fragment.getFragmentManager()獲得。


有多種方式使用碎片類去實現各種各樣的效果。在其內部,它表現爲運行在一個更大活動內的特殊操作或界面。碎片與活動緊密相連,且不能在活動以外使用碎片。儘管碎片定義了自己的生命週期,但它是依賴於它的活動:如果活動被停止了,在活動內碎片都不能被啓動;當活動被銷燬時,所有的碎片也將被銷燬。



所有碎片的子類都必須包含一個公共的空構造函數。當在需要時,框架往往會重新實例化一個碎片類,尤其是在狀態恢復期間,需要能夠找到這個構造函數來初始化它。如果沒有可用的空構造函數,在狀態恢復期間的某些狀況下回發生運行時異常。


這裏涉及到的主題有:

  1. Older Platforms (較老的平臺)
  2. Lifecycle(生命週期)
  3. Layout(佈局)
  4. Back Stack(後退堆棧)



較老的平臺(Older Platforms)


碎片API是由 HONEYCOMB引入的,憑藉FragmentActivit,該版本API在較早的平臺上依然可用。詳情請參考博文Fragments For All。


生命週期(Lifecycle)


儘管碎片的生命週期被捆綁在它所屬的活動上,但除了標準的活動生命週期外,它有屬於自己的新特徵。它不包括了基本的活動生命週期方法,譬如onResume(),重要的是還有與活動和UI生成相交互有關的方法。


被用來促使碎片恢復狀態(與用戶交互)的核心序列方法是:

  1. onAttach(Activity) ,一旦碎片與活動有關聯時即被調用。
  2. onCreate(Bundle) ,被調用來創建碎片。
  3. onCreateView(LayoutInflater, ViewGroup, Bundle) ,創建並返回與碎片有關聯的視圖層級。
  4. onActivityCreated(Bundle) , 告訴碎片,它的活動已經完成它的Activity.onCreate()
  5. onStart() , 使得碎片對用戶可見(基於包含它的活動已被啓動)。
  6. onResume() ,使得用戶可以與碎片交互(基於包含它的活動正被喚起) 。



隨着碎片不再被使用,它會經歷一個相反序列的回調:


  1. onPause() ,碎片不再與用戶交互,或許是因爲它的活動被暫停了,或者是在它的活動內某個碎片操作正在修改它。
  2. onStop() ,碎片不在對用戶可見,或許是因爲它的活動正在被停止,或者是在它的活動內某個碎片操作正在修改它。
  3. onDestroyView() ,允許碎片清理與它的視圖有關聯的資源。
  4. onDestroy() ,被調用來做碎片狀態的最後清理。
  5. onDetach() ,不再與它的活動有關聯之前立即被調用。



佈局(Layout)


碎片可被用作應用程序佈局的一部分,它允許你更好地模塊化代碼以及更容易地把用戶界面調整到它正在運行的屏幕上。作爲一個例子,我們會看到一個簡單的程序,它包含了一個列表,並顯現每個列表項的細節。


活動的佈局XML可以包括<fragment>標記來嵌入碎片實例到其佈局內。例如,這裏有個嵌入了一個碎片的簡單佈局:


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">
    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles"
            android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>

該佈局在活動內按照通常方式被裝載:


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

    setContentView(R.layout.fragment_layout);
}

標題碎片顯示一個主題列表,它相當的簡單,它的大部分工作都依靠ListFragment。注意點擊一列表項的實現:根據當前的活動佈局,它可以創建並顯示一個新的碎片來就地顯示項目細節(更多細節在其後),或是啓動一個新的活動來顯示細節。


public static class TitlesFragment extends ListFragment {
    boolean mDualPane;
    int mCurCheckPosition = 0;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Populate list with our static array of titles.
        setListAdapter(new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));

        // Check to see if we have a frame in which to embed the details
        // fragment directly in the containing UI.
        View detailsFrame = getActivity().findViewById(R.id.details);
        mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;

        if (savedInstanceState != null) {
            // Restore last state for checked position.
            mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
        }

        if (mDualPane) {
            // In dual-pane mode, the list view highlights the selected item.
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            // Make sure our UI is in the correct state.
            showDetails(mCurCheckPosition);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("curChoice", mCurCheckPosition);
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        showDetails(position);
    }

    /**
     * Helper function to show the details of a selected item, either by
     * displaying a fragment in-place in the current UI, or starting a
     * whole new activity in which it is displayed.
     */
    void showDetails(int index) {
        mCurCheckPosition = index;

        if (mDualPane) {
            // We can display everything in-place with fragments, so update
            // the list to highlight the selected item and show the data.
            getListView().setItemChecked(index, true);

            // Check what fragment is currently shown, replace if needed.
            DetailsFragment details = (DetailsFragment)
                    getFragmentManager().findFragmentById(R.id.details);
            if (details == null || details.getShownIndex() != index) {
                // Make new fragment to show this selection.
                details = DetailsFragment.newInstance(index);

                // Execute a transaction, replacing any existing fragment
                // with this one inside the frame.
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                ft.replace(R.id.details, details);
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }

        } else {
            // Otherwise we need to launch a new activity to display
            // the dialog fragment with selected text.
            Intent intent = new Intent();
            intent.setClass(getActivity(), DetailsActivity.class);
            intent.putExtra("index", index);
            startActivity(intent);
        }
    }
}


顯示被選項目細節的碎片只是顯示一條文本字符串, 它是基於內置在應用中的字符數組的一個索引:


public static class DetailsFragment extends Fragment {
    /**
     * Create a new instance of DetailsFragment, initialized to
     * show the text at 'index'.
     */
    public static DetailsFragment newInstance(int index) {
        DetailsFragment f = new DetailsFragment();

        // Supply index input as an argument.
        Bundle args = new Bundle();
        args.putInt("index", index);
        f.setArguments(args);

        return f;
    }

    public int getShownIndex() {
        return getArguments().getInt("index", 0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (container == null) {
            // We have different layouts, and in one of them this
            // fragment's containing frame doesn't exist.  The fragment
            // may still be created from its saved state, but there is
            // no reason to try to create its view hierarchy because it
            // won't be displayed.  Note this is not needed -- we could
            // just run the code below, where we would create and return
            // the view hierarchy; it would just never be used.
            return null;
        }

        ScrollView scroller = new ScrollView(getActivity());
        TextView text = new TextView(getActivity());
        int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                4, getActivity().getResources().getDisplayMetrics());
        text.setPadding(padding, padding, padding, padding);
        scroller.addView(text);
        text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
        return scroller;
    }
}


當用戶選擇一個標題時,而在當前活動裏沒有容納標題細節的空間,這種情況下,標題碎片的點擊代碼將啓動一個新的活動來顯示細節碎片:


public static class DetailsActivity extends Activity {

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

        if (getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    }
}


然而,屏幕可能有足夠大的空間來顯示標題列表和當前被選中的標題細節。爲在橫向屏幕上使用如此佈局,該佈局可被放置在layout-land目錄下:



<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent" />

    <FrameLayout android:id="@+id/details" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent"
            android:background="?android:attr/detailsElementBackground" />

</LinearLayout>



注意,先前的代碼將如何適應該可選的UI流: 現在,標題碎片將把細節碎片嵌入到它(標題)的活動內部,並且細節活動將會結束它自己,如果它正在一個可以就地顯示的屏幕配置中運行。


當屏幕配置發生變化時,導致持有那些碎片的活動被重新啓動,它(活動)的實例可能使用一個不同的佈局,該佈局不包括和先前佈局裏一樣的碎片。這種情況下,所有先前的碎片將依然被初始化並運行在活動的新實例中。然而,在視圖層級中,任何與 <fragment>標記不再有關聯的碎片將不會擁有內容視圖,並從isInLayout()中返回false。(這裏的代碼也顯示瞭如何確定是否容器內的碎片不再運行在該容器內的佈局中,並在此情況下避免創建它的視圖層級。)


當附加碎片視圖到父視圖容器內時,<fragment>標籤屬性用來控制此時提供的佈局參數(LayoutParams)。他們也可以作爲onInflate(Activity, AttributeSet, Bundle)的參數由碎片來解析。


被初始化的碎片必須具備一些獨特的標示,目的是爲了它可以被重新關聯,如果它的所屬活動需要被銷燬並重新創建。可以通過如下方式提供標示:

  • 如果沒有明確地提供標示,將使用容器的視圖ID。
  • 在<fragment>中使用android:tag來提供一個明確的碎片標記名。
  • 在<fragment>中使用android:id來提供一個明確的碎片標示符。




後退堆棧(Back Stack)


修改碎片的事務可被放置在宿主活動內部的後退堆棧裏。當用戶在活動內按下返回鍵時,在其結束前,後退堆內的任何事務都會被彈出。


例如,考慮這個簡單的碎片,它由一個整數參數初始化,並在其UI裏的一個TextView上顯示它(整數):


public static class CountingFragment extends Fragment {
    int mNum;

    /**
     * Create a new instance of CountingFragment, providing "num"
     * as an argument.
     */
    static CountingFragment newInstance(int num) {
        CountingFragment f = new CountingFragment();

        // Supply num input as an argument.
        Bundle args = new Bundle();
        args.putInt("num", num);
        f.setArguments(args);

        return f;
    }

    /**
     * When creating, retrieve this instance's number from its arguments.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mNum = getArguments() != null ? getArguments().getInt("num") : 1;
    }

    /**
     * The Fragment's UI is just a simple text view showing its
     * instance number.
     */
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.hello_world, container, false);
        View tv = v.findViewById(R.id.text);
        ((TextView)tv).setText("Fragment #" + mNum);
        tv.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb));
        return v;
    }
}


可以這樣寫一個方法,它創建了碎片的新實例,此實例取代了任何當前正在被顯示的碎片,並把這種改變放入後退堆內:


void addFragmentToStack() {
    mStackLevel++;

    // Instantiate a new fragment.
    Fragment newFragment = CountingFragment.newInstance(mStackLevel);

    // Add the fragment to the activity, pushing this transaction
    // on to the back stack.
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    ft.replace(R.id.simple_fragment, newFragment);
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
    ft.addToBackStack(null);
    ft.commit();

每次調用該方法之後,在後退堆上都是一個新的記錄,並且按下返回按鈕將會彈出它使用戶返回到活動UI的先前狀態。



                                                                                                                           2012年5月7日, 畢

發佈了13 篇原創文章 · 獲贊 2 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章