经常看到咱们开发的小伙伴们说掉进Fragment的坑里,今天就来说明一下Fragment,对其有个了解之后再来使用,也许不会遇到那么多坑
Fragment是在Android 3.0 以后引入的,如果你想在3.0以前使用那就只能引入v4包了,它很好的解决了Android的碎片问题,尤其是在平板上更能显示出Fragment的优势.
Fragment既然这么好,如何加载呢?一种方法可以直接使用fragment布局,静态加载,另一种动态加载,在我们的应用中很少用到Fragment的静态加载方法,所以我们这里只讨论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的生命周期图,明了,易懂
在使用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:
Fragment事务到回退栈:
FragmentTransaction.addToBackStack(String)
从Fragment是什么部分你可以了解到它其实可以看作一个view,所以那些fragment嵌套fragment的方式尽量避免,不提倡使用这种方式!
其实fragment很重要的两点,一个是状态的改变,一个是事务管理,这两点把整个fragment贯穿全部,感兴趣的先去了解一下.
杏树林研发 郭莉莉