简单的效果图如下:
现在利用碎片实现一个简单的动态UI,点击左边标题栏的标题,然后左边正文栏显示对应的文章
1、在activity_main.xml布局中添加两个Fragment。
一个对应左边的标题栏,一个对应右边的正文栏
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<fragment
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
class="com.example.fragment.HeadlinesFragment"/>
<fragment
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:id="@+id/articles_fragment"
class="com.example.fragment.ArticleFragment"/>
</LinearLayout>
2、然后,需要创建一个类来放置内容。内容就是左边的标题和右边的正文了。
public class Ipsum {
static String[] Headlines = {
"Article One",
"Article Two"
};
static String[] Articles = {
"Article One\n\nExcepteur pour-over occaecat squid biodiesel umami gastropub, nulla laborum salvia dreamcatcher fanny pack. Ullamco culpa retro ea, trust fund excepteur eiusmod direct trade banksy nisi lo-fi cray messenger bag. Nesciunt esse carles selvage put a bird on it gluten-free, wes anderson ut trust fund twee occupy viral. Laboris small batch scenester pork belly, leggings ut farm-to-table aliquip yr nostrud iphone viral next level. Craft beer dreamcatcher pinterest truffaut ethnic, authentic brunch. Esse single-origin coffee banksy do next level tempor. Velit synth dreamcatcher, magna shoreditch in american apparel messenger bag narwhal PBR ennui farm-to-table.",
"Article Two\n\nVinyl williamsburg non velit, master cleanse four loko banh mi. Enim kogi keytar trust fund pop-up portland gentrify. Non ea typewriter dolore deserunt Austin. Ad magna ethical kogi mixtape next level. Aliqua pork belly thundercats, ut pop-up tattooed dreamcatcher kogi accusamus photo booth irony portland. Semiotics brunch ut locavore irure, enim etsy laborum stumptown carles gentrify post-ironic cray. Butcher 3 wolf moon blog synth, vegan carles odd future."
};
}
在创建Fragment类之前,先来看看fragment的生命周期。
可以看到,在fragment中,也有onCreate()、onStart()这些方法
由图可以清楚地知道fragment的生命周期
fragment是生命周期与Activity的生命周期。
fragment是嵌入在activity里面,它的生命周期会受到主activity生命周期的直接影响。
比如,当activity处于paused状态,它里面所有的fragment也是处于pasued这一状态。
3、首先来创建一个ArticleFragment类,继承Fragment。这个Fragment是右边的正文栏部分
public class ArticleFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.article_view,container,false);
}
}
碎片通常用作一个活动的用户界面的一部分,并有助于提高布局的灵活性。
需要通过实现onCreateView()回调方法,来绘制fragment的布局,必须要返回一个View值。
而这个布局可以在XML文件中定义,调用onCreateView() 提供的一个 LayoutInflater对象,传入布局的id。
onCreateView()方法传入了三个参数。
看看官方的解释:
The container parameter passed to onCreateView() is the parent ViewGroup (from the activity’s layout) in which your fragment layout will be inserted.
The savedInstanceState parameter is a Bundle that provides data about the previous instance of the fragment, if the fragment is being resumed (restoring state is discussed more in the section about Handling the Fragment Lifecycle).
inflate() 方法也有三个参数:
The resource ID of the layout you want to inflate.
The ViewGroup to be the parent of the inflated layout. Passing the container is important in order for the system to apply layout parameters to the root view of the inflated layout, specified by the parent view in which it’s going.
A boolean indicating whether the inflated layout should be attached to the ViewGroup (the second parameter) during inflation. (In this case, this is false because the system is already inserting the inflated layout into the container—passing true would create a redundant view group in the final layout.)
可以看到savedInstanceStat是一个Bundle,如果 fragmen重新回到resumed状态(这里就涉及到fragment的生命周期了),它用来存放之前的fragment实例放入的数据。所以,可以通过重载 onSaveInstanceState,来保存当前选择文章的位置。
在ArticleFragment还需要有更新方法,根据标题点击的位置来显示文章。
ArticleFragment中添加代码:
public class ArticleFragment extends Fragment {
final static String ARG_POSITION = "ArticleFragementPosition";
int mCurrentPosition = -1; //先把当前的位置设置为-1,即什么都还没选
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (savedInstanceState != null) {
mCurrentPosition = savedInstanceState.getInt(ARG_POSITION);
}
return inflater.inflate(R.layout.article_view,container,false);
}
@Override
public void onStart() {
super.onStart();
// 在OnStart中,检查是否传递过来参数,确保调用方法的时候是安全的
Bundle args = getArguments();
if (args != null)
updateArticleView(args.getInt(ARG_POSITION));
} else if (mCurrentPosition != -1) {
updateArticleView(mCurrentPosition);
}
}
public void updateArticleView(int position) {
TextView article = (TextView) getActivity().findViewById(R.id.article);
article.setText(Ipsum.Articles[position]);
mCurrentPosition = position;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// 保存当前的位置
outState.putInt(ARG_POSITION, mCurrentPosition);
}
}
定义的正文栏布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/article"
android:padding="16dp"
android:textSize="18sp">
</TextView>
</ScrollView>
</LinearLayout>
然后创建一个HeadlinesFragment类,继承ListFragment。这个Fragment是左边的标题部分。
public class HeadlinesFragment extends ListFragment {
}
如果fragment继承的是ListFragment,就已经默认实现了onCreateView()方法,并返回一个ListView,所以这里不需要实现onCreateView()方法。
有时候,需要fragment在activity中分享数据。这里有一个很好的办法,在fragment里面定义接口,在主activity中实现这个接口。当activity通过这个接口接收到数据,在需要的时候就可以把数据分享给其他的fragment。
例如,标题栏这部分的fragment点击标题A,这时候会获得标题A的位置,我们需要根据这个位置来显示想对应的文章。在HeadlinesFragment中添加这个接口。
public class HeadlinesFragment extends ListFragment {
...
public interface OnHeadlinesSelectedListner {
public void onArticleSelected(int position);
}
...
}
接着,在标题栏添加上标题。这里fragment继承了ListFragment,需要适配器(Adapter)把标题的内容添加进来,并且在点击的时候要有相应,能够获取点击的位置。这里的位置从0开始,1,2,3…,不过首先得确保Activity实现上面的接口,需要调用onAttach().
public class HeadlinesFragment extends ListFragment {
OnHeadlinesSelectedListner mcallback;
public interface OnHeadlinesSelectedListner {
public void onArticleSelected(int position);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// 确保Activity实现接口
try{
mcallback = (OnHeadlinesSelectedListner) activity;
} catch (ClassCastException e){
throw new ClassCastException(activity.toString()
+ " must implement onHeadlinesSelectedListner");
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//根据设备选择list_item的样式
int layout = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
android.R.layout.simple_list_item_activated_1 : android.R.layout.simple_list_item_1;
// 创建一个数组适配器,传人Ipsum类中的Headlines数组
setListAdapter(new ArrayAdapter<String>(getActivity(), layout, Ipsum.Headlines));
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
//调用接口方法,这样主activity中实现的时候,传入的position就可以被分享了,
mcallback.onArticleSelected(position);
//设置item被选择
getListView().setItemChecked(position,true);
}
@Override
public void onStart() {
super.onStart();
// 设置为单选模式
if (getFragmentManager().findFragmentById(R.id.articles_fragment) != null) {
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
}
}
}
最后在主activity中实现接口
public class MainActivity extends FragmentActivity implements HeadlinesFragment.OnHeadlinesSelectedListner {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onArticleSelected(int position) {
//这里传入参数position,就是HeadlinesFragment点击选中的位置
ArticleFragment articleFrag = (ArticleFragment)
getSupportFragmentManager().findFragmentById(R.id.articles_fragment);
articleFrag.updateArticleView(position);
}
}
到这里为止,全部代码都已经写完了。运行成功,就跟示例图片一样了。
相对于直接在activity的布局中直接添加fragment,其实还可以使用一个FragmentLayout作为一个fragment container,然后可以在activity运行的时候,通过transaction来添加或者移除。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
...
if (findViewById(R.id.fragment_container) != null) {
if (savedInstanceState != null) {
return;
}
HeadlinesFragment firstFragment = new HeadlinesFragment();
firstFragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, firstFragment).commit();
}
...
在activity中,调用getSupportFragmentManager() 来获得FragmentManagers. 然后调用beginTransaction() 来创建一个FragmentTransaction,再add() 添加一个 fragment.
可以通过同一个FragmentTransaction来操作多个fragment. 当已经对fragment操作完成了,比如添加或者移除, 最后就调用commit().
...
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();
...
BackStack是Activity使用Task来管理的一个栈。
通过调用 addToBackStack(),这次替代的事务就会被保存在返回栈中。因此用户可以按返回键可以看到之前的fragment。