簡述Fragment

經常看到咱們開發的小夥伴們說掉進Fragment的坑裏,今天就來說明一下Fragment,對其有個瞭解之後再來使用,也許不會遇到那麼多坑

Fragment是在Android 3.0 以後引入的,如果你想在3.0以前使用那就只能引入v4包了,它很好的解決了Android的碎片問題,尤其是在平板上更能顯示出Fragment的優勢.
Fragment既然這麼好,如何加載呢?一種方法可以直接使用fragment佈局,靜態加載,另一種動態加載,在我們的應用中很少用到Fragment的靜態加載方法,所以我們這裏只討論Fragment的動態加載.

1.Fragment是什麼?

Fragment是一個控件,是一個window,是視圖的一部分?讓源碼來告訴你Fragment到底是什麼

public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener 

從這一句源碼部分看不到任何View的影子,我們繼續看

@Override
    protected void onCreate(Bundle savedInstanceState) {
        mFragments.attachActivity(this, mContainer, null);
        // Old versions of the platform didn't do this!
        if (getLayoutInflater().getFactory() == null) {
            getLayoutInflater().setFactory(this);
        }

        super.onCreate(savedInstanceState);

        NonConfigurationInstances nc = (NonConfigurationInstances)
                getLastNonConfigurationInstance();
        if (nc != null) {
            mAllLoaderManagers = nc.loaders;
        }
        if (savedInstanceState != null) {
            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
            mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
        }
        mFragments.dispatchCreate();
    }

Fragment先綁定Activity,Activity對於Fragment來說本質上是個控制器,用於分發不同的狀態和業務!

if (getLayoutInflater().getFactory() == null) {
            getLayoutInflater().setFactory(this);
        }

這一部分就是你意想的那個view,這個是用來幹嘛的呢?實際上,你如果瞭解一點LayoutInflater的源碼其實並不會感到陌生,因爲這個接口是爲了構造你的View而存在。這就是偉大的Androidsdk開發者給你提供的擴展機會,不論你配置的東西是否繼承於View,只要你實現了這個接口,你就可以按照既定的規則構造出View。這個接口的實現方法在:

     /**
     * Add support for inflating the <fragment> tag.
     */
    @Override
    public View onCreateView(String name, @NonNull Context context, @NonNull AttributeSet attrs) {
        if (!"fragment".equals(name)) {
            return super.onCreateView(name, context, attrs);
        }

        final View v = mFragments.onCreateView(name, context, attrs);
        if (v == null) {
            return super.onCreateView(name, context, attrs);
        }
        return v;
    }

還是看不出fragment是什麼,我們繼續

@Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        if (!"fragment".equals(name)) {
            return null;
        }

        String fname = attrs.getAttributeValue(null, "class");
        TypedArray a =  context.obtainStyledAttributes(attrs, FragmentTag.Fragment);
        if (fname == null) {
            fname = a.getString(FragmentTag.Fragment_name);
        }
        int id = a.getResourceId(FragmentTag.Fragment_id, View.NO_ID);
        String tag = a.getString(FragmentTag.Fragment_tag);
        a.recycle();

        if (!Fragment.isSupportFragmentClass(mActivity, fname)) {
            // Invalid support lib fragment; let the device's framework handle it.
            // This will allow android.app.Fragments to do the right thing.
            return null;
        }

        View parent = null; // NOTE: no way to get parent pre-Honeycomb.
        int containerId = parent != null ? parent.getId() : 0;
        if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
            throw new IllegalArgumentException(attrs.getPositionDescription()
                    + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname);
        }

        // If we restored from a previous state, we may already have
        // instantiated this fragment from the state and should use
        // that instance instead of making a new one.
        Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null;
        if (fragment == null && tag != null) {
            fragment = findFragmentByTag(tag);
        }
        if (fragment == null && containerId != View.NO_ID) {
            fragment = findFragmentById(containerId);
        }

        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x"
                + Integer.toHexString(id) + " fname=" + fname
                + " existing=" + fragment);
        if (fragment == null) {
            fragment = Fragment.instantiate(context, fname);
            fragment.mFromLayout = true;
            fragment.mFragmentId = id != 0 ? id : containerId;
            fragment.mContainerId = containerId;
            fragment.mTag = tag;
            fragment.mInLayout = true;
            fragment.mFragmentManager = this;
            fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState);
            addFragment(fragment, true);

        } else if (fragment.mInLayout) {
            // A fragment already exists and it is not one we restored from
            // previous state.
            throw new IllegalArgumentException(attrs.getPositionDescription()
                    + ": Duplicate id 0x" + Integer.toHexString(id)
                    + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId)
                    + " with another fragment for " + fname);
        } else {
            // This fragment was retained from a previous instance; get it
            // going now.
            fragment.mInLayout = true;
            // If this fragment is newly instantiated (either right now, or
            // from last saved state), then give it the attributes to
            // initialize itself.
            if (!fragment.mRetaining) {
                fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState);
            }
        }

        // If we haven't finished entering the CREATED state ourselves yet,
        // push the inflated child fragment along.
        if (mCurState < Fragment.CREATED && fragment.mFromLayout) {
            moveToState(fragment, Fragment.CREATED, 0, 0, false);
        } else {
            moveToState(fragment);
        }

        if (fragment.mView == null) {
            throw new IllegalStateException("Fragment " + fname
                    + " did not create a view.");
        }
        if (id != 0) {
            fragment.mView.setId(id);
        }
        if (fragment.mView.getTag() == null) {
            fragment.mView.setTag(tag);
        }
        return fragment.mView;
    }

代碼雖然很長,當你看到返回fragment.mView的時候,你應該就得到答案了!

Fragment生命週期

這裏寫圖片描述

上面這張圖片是官網上的fragment的生命週期圖,明瞭,易懂

  • onAttach方法:Fragment和Activity建立關聯的時候調用。
  • onCreateView方法:爲Fragment加載佈局時調用。
  • onActivityCreated方法:當Activity中的onCreate方法執行完後調用。
  • onDestroyView方法:Fragment中的佈局被移除時調用。
  • onDetach方法:Fragment和Activity解除關聯的時候調用。

Fragment如何使用

在使用fragmnt時,官方給了這樣一個注意事項
Default constructor. Every fragment must have an empty constructor, so it can be instantiated when restoring its activity’s state. It is strongly recommended that subclasses do not have other constructors with parameters, since these constructors will not be called when the fragment is re-instantiated; instead, arguments can be supplied by the caller with setArguments(Bundle) and later retrieved by the Fragment withgetArguments().

Applications should generally not implement a constructor. The first place application code an run where the fragment is ready to be used is in onAttach(Activity), the point where the fragment is actually associated with its activity. Some applications may also want to implementonInflate(Activity, AttributeSet, Bundle) to retrieve attributes from a layout resource, though should take care here because this happens for the fragment is attached to its activity.

默認構造函數。每一個片段都必須有一個空的構造函數,所以它可以在恢復其活性的狀態被實例化。強烈建議在子類不具有其他構造帶參數,因爲這些構造函數將不會被調用時的片段重新實例;相反,參數可以被調用者提供有setArguments(捆綁),後來被碎片withgetArguments()檢索。

應用程序通常不應實施構造。首先應用程序代碼的運行,其中該片段已準備好被使用的是在onAttach(活動),其中所述片段實際上是與它的活動相關聯的點。有些應用程序可能還需要implementonInflate(活動,AttributeSet中,包)從一個佈局資源檢索屬性,但應注意在這裏,因爲這種情況下的片段連接到它的活動。

/**
     * Create a new instance of a Fragment with the given class name.  This is
     * the same as calling its empty constructor.
     *
     * @param context The calling context being used to instantiate the fragment.
     * This is currently just used to get its ClassLoader.
     * @param fname The class name of the fragment to instantiate.
     * @param args Bundle of arguments to supply to the fragment, which it
     * can retrieve with {@link #getArguments()}.  May be null.
     * @return Returns a new fragment instance.
     * @throws InstantiationException If there is a failure in instantiating
     * the given fragment class.  This is a runtime exception; it is not
     * normally expected to happen.
     */
    public static Fragment instantiate(Context context, String fname, Bundle args) {
        try {
            Class<?> clazz = sClassMap.get(fname);
            if (clazz == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = context.getClassLoader().loadClass(fname);
                sClassMap.put(fname, clazz);
            }
            Fragment f = (Fragment)clazz.newInstance();
            if (args != null) {
                args.setClassLoader(f.getClass().getClassLoader());
                f.mArguments = args;
            }
            return f;
        } catch (ClassNotFoundException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (java.lang.InstantiationException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (IllegalAccessException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        }
    }

整個過程中,Fragment的創建其實也是利用了無參數的構造方法去實例化
Fragment依附在Activity中,如果Activity爲null,那麼Fragment肯定要出事兒. 或者比如手機屏幕豎屏橫屏切換,導致Activity重建了,於是Fragment中的所有原先傳遞過去的值也會失去,所以不推薦使用帶參數的構造函數,將Bundle傳類新建的Fragment,這樣舊的Fragment和新的Fragment就能擁有一樣的Bundle,從而達到利用Bundle傳遞參數的目的.

至於如何使用fragment,簡單給出一個小栗子

FragmentTransaction t = getSupportFragmentManager().beginTransaction();  
        t.add(android.R.id.content, new TestFragment());  
        t.commit();

下面是關於事務開啓之後的一些api:

  • transaction.add() 往Activity中添加一個Fragment
  • transaction.remove() 從Activity中移除一個Fragment,如果被移除的Fragment沒有添加到回退棧(回退棧後面會詳細說),這個Fragment實例將會被銷燬。
  • transaction.replace() 使用另一個Fragment替換當前的
  • transaction.hide() 隱藏當前的Fragment,僅僅是設爲不可見,並不會銷燬
  • transaction.show() 顯示之前隱藏的Fragment

Fragment事務到回退棧:

FragmentTransaction.addToBackStack(String)
  1. 如果你Activity中包含自己管理的Fragment的引用,可以通過引用直接訪問所有的Fragment的public方法
  2. 如果Activity中未保存任何Fragment的引用,那麼沒關係,每個Fragment都有一個唯一的TAG或者ID,可以通過getFragmentManager.findFragmentByTag()或者findFragmentById()獲得任何Fragment實例,然後進行操作。
  3. 在Fragment中可以通過getActivity得到當前綁定的Activity的實例,然後進行操作。

從Fragment是什麼部分你可以瞭解到它其實可以看作一個view,所以那些fragment嵌套fragment的方式儘量避免,不提倡使用這種方式!

其實fragment很重要的兩點,一個是狀態的改變,一個是事務管理,這兩點把整個fragment貫穿全部,感興趣的先去了解一下.

                                                                        杏樹林研發 郭莉莉
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章