跟Google学写代码:使用Fragment构建可变的界面
2016-06-24 16:08
417 查看
项目介绍
运行示例:UML类图:
控制层:MainActivity
视图层:HeadlinesFragment 的listview, ArticleFragment的textview
数据层:Ipsum
代码分析
首先是MainActivity的布局news_articles.xml,考虑适配,我们写好两种layout满足不同尺寸的设备普通模式:
<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" />
同时在layout-large目录下也有news_articles.xml,通过xml静态定义MainActivity的布局,针对不同尺寸的手机设备,会加载不同的布局
平板模式(large layout)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:name="com.example.android.fragments.HeadlinesFragment" android:id="@+id/headlines_fragment" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent" /> <fragment android:name="com.example.android.fragments.ArticleFragment" android:id="@+id/article_fragment" android:layout_weight="2" android:layout_width="0dp" android:layout_height="match_parent" /> </LinearLayout>
MainActivity.java类
public class MainActivity extends FragmentActivity implements HeadlinesFragment.OnHeadlineSelectedListener { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.news_articles); //检查activity使用的布局版本是否为普通模式下的fragment_container FrameLayout //如果是,我们需要动态添加fragment //如果不是,activity会通过直接加载large-layout模式下的xml文件,来静态加载两个fragment if (findViewById(R.id.fragment_container) != null) { //这里使用findViewById的方式值得一学 // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. if (savedInstanceState != null) { return; } // 创建一个Fragment实例,它其实是ListFragment的子类实现 HeadlinesFragment firstFragment = new HeadlinesFragment(); // In case this activity was started with special instructions from an Intent, // pass the Intent's extras to the fragment as arguments firstFragment.setArguments(getIntent().getExtras()); //将Fragment添加到'fragment_container' 布局中(记住下面四部曲) //get->begi-add->com getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, firstFragment).commit(); } } /** * 用户操作HeadlinesFragment时,回调这个接口 */ public void onArticleSelected(int position) { //在activity中创建article fragment ArticleFragment articleFrag = (ArticleFragment) getSupportFragmentManager().findFragmentById(R.id.article_fragment); if (articleFrag != null) { //如果article fragment是可用的,意味着目前处于large-layout模式下,activity静态加载了两个fragment, //回调ArticleFragment的update方法 通知页面更新 articleFrag.updateArticleView(position); } else { //如果fragment不可用,意味着我们需要去动态添加到布局中,并且替换当前的HeadLineFragment,让ArticleFragment显示到屏幕上 //创建article fragment,并且通知article fragment 选中了哪一个标题,以此来作为根据显示哪一个内容 ArticleFragment newFragment = new ArticleFragment(); Bundle args = new Bundle(); //这里使用ArticleFragment中定义好的.ARG_POSITION的静态字符串常量作为标记 //而不是在Activity中定义静态字符串常量,这种小细节值得注意 args.putInt(ArticleFragment.ARG_POSITION, position); newFragment.setArguments(args); FragmentTransaction transaction = getSupportFrag`这里写代码片`mentManager().beginTransaction(); // 用上一步创建好的article fragment来替换当前fragment_container正在显示的HeadLineFragment //addToBackStack可以让用户向后导航,即退出ArticleFragment时,也可以看到HeadLinFragment的neritic //如果不使用addToBackStack,点击返回键 会退出当前activity transaction.replace(R.id.fragment_container, newFragment); //设置切换动画 int setTransition=TRANSIT_FRAGMENT_OPEN; transaction.setTransition(setTransition); transaction.addToBackStack(null); // 最后记得使用事物提交 fragment的操作 transaction.commit(); } } }
总结一下MainActivity中的 小亮点:
实现HeadlinesFragment的回调接口OnHeadlineSelectedListener,将业务代码放在Activity中,这是一个简单的解耦,下文会解释如何解耦
做了屏幕适配:考虑小屏的情况,动态添加布局;当设备为大屏时,加载资源文件large-layout中的布局,静态初始化两个fragment
通过事物的setTransition来设置切换动画
考虑用户回退操作,即为用户做了向后导航的功能,通过事物的addToBackStack来完成
接着是首先要展示的界面HeadlinesFragment,它继承自ListFragment,ListFragment是一种特殊的fragment,可以直接通过setAdapter来为它加载布局,同时可以使用android提供的默认list布局:
public class HeadlinesFragment extends ListFragment { //内部自定义的回调接口 OnHeadlineSelectedListener mCallback; // 只要Activity实现此接口,就可以通过HeadLineFragment通知ArticleFragment来更新内容了 public interface OnHeadlineSelectedListener { /** 这个接口的方法,在本类的onListItemClikc()中被调用,这样就可以让Activity来回调了 */ public void onArticleSelected(int position); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //在使用Android提供的默认list布局时,需要考虑兼容不同的版本 // We need to use a different list item layout for devices older than Honeycomb 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 onStart() { super.onStart(); //当处于large-layout模式时,为选中的item设置为高亮模式 //当然前提是article fragment是可见的 if (getFragmentManager().findFragmentById(R.id.article_fragment) != null) { getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); //setChoiceMode 这个API值得一记! } } @Override public void onAttach(Activity activity) { super.onAttach(activity); //下面两句代码,确保程序员在Activity中实现了OnHeadlineSelectedListener接口,如果没有实现,抛出异常 //这种写法强调了一种代码规范,即必须按照接口实现规定的内容 try { mCallback = (OnHeadlineSelectedListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnHeadlineSelectedListener"); } } @Override public void onListItemClick(ListView l, View v, int position, long id) { // 通知activity选中的位置 mCallback.onArticleSelected(position); //在larg-layout模式下,通过setItemChecked将选中的item设置高亮状态,这样就不用我们自己实现状态选择器了 getListView().setItemChecked(position, true); } }
总结一下HeadLineFragment中的 亮点:
内部定义了一个回调接口OnHeadlineSelectedListener,交给Activity去实现,业务逻辑放在Activity中,而不是直接在HeadLineFragment的onListItemClick中,这样既完成了解耦:Head fragment和 Article Fragment之间相互不依赖,不持有彼此的引用(实例对象),也因为将业务逻辑放在activity中而使得代码逻辑更清晰,这其实就是MVP的体现。
listview可以通过setChoiceMode() 设置高亮模式
在onAttach中使用抛出异常的作法,强调了一种 代码规范,每个程序员在使用HeadLineFragment时,必须在Activity中实现此接口。 代码规范的作用,就是告诉每一个程序员,你应该做什么,摒弃个人编码习惯,顺应团队的编码规范,才是王道
listview可以通过setItemChecked设置高亮模式
ArticleFragment.java
public class ArticleFragment extends Fragment { final static String ARG_POSITION = "position"; int mCurrentPosition = -1; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // If activity recreated (such as from screen rotate), restore // the previous article selection set by onSaveInstanceState(). // This is primarily necessary when in the two-pane layout. //考虑Fragment 运行时可能发生改变,可以从Bundle对象中恢复数据,用一个变量来保存 //这种写法在large-layout模式下非常有用 if (savedInstanceState != null) { mCurrentPosition = savedInstanceState.getInt(ARG_POSITION); } return inflater.inflate(R.layout.article_view, container, false); } @Override public void onStart() { super.onStart(); //在启动fragment过程中,检查是否将参数传递给当前fragment //onStart是一个绝佳的位置来更新视图,比如设置textview的内容 //为什么在onStart设置视图?因为在onCreateView中已经加载完毕布局 Bundle args = getArguments(); if (args != null) { //基于外部传进来的索引,来设置article fragment的内容 updateArticleView(args.getInt(ARG_POSITION)); } else if (mCurrentPosition != -1) { //基于onCreateView初始化的mCurrentPosition来设置 内容 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); //保存选中的位置,以备fragment可能重建的情况 outState.putInt(ARG_POSITION, mCurrentPosition); } }
ArticleFragment 中有 几个小亮点值得注意:
mCurrentPosition 临时变量来保存 上一次ArticleFragment显示的内容(其实是索引,根据索引调用Inpus中的字符串数组Articles来显示内容),防止因为屏幕旋转,或者用户其他行为,导致需要重建页面时 数据(索引)丢失的情况,onSaveInstate的用法大家都不陌生,只是什么时候用,需要提前考虑好,而不是bug出现了,才去加代码解决,好好提升代码质量把!
在onStart中设置视图内容,这是一种非常安全的作法,以免在onCreatView中设置视图内容时,出现空指针异常
ArticleFragment 的布局article_view.xml很简单,就一个textview:
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/article" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" android:textSize="18sp" />
Ipsum.java
public class Ipsum { static String[] Headlines = { "Article One", "Article Two" }; static String[] Articles={"Article One \n\n 本人的老爸有个朋友,以前身体不舒服去检查,结果医生告诉他," + "病得有点严重,这个要控制,那个也要控制,要不然会很快恶化。\n" + "忍了一段时间后他受不了了,由于经济条件不错,所以他有什么想吃的就吃,有什么想喝的就喝,过得相当自在。\n" + "结果就这样过了很多很多年,可能是心态影响身体,当初给他看病的那几个医生基本都挂了,他还活得活蹦乱跳的……", //Article Two---------------------- "Article Two \n\n 端午佳节博得你一笑 祝你节日快乐!\n" + "老外来到中国吃粽子,吃完后深感体会的说!你们中国的粽子真好吃,就是包粽子的那几棵青菜太老了,好半天才咽下去![呲牙][呲牙][呲牙]" }; }
总结
1. 代码规范:MainAcitity实现接口的方式,来对两个Fragment解耦2. 屏幕适配:创建默认layout和大屏模式large-layout两种情况的布局
3. 切换动画:setTrasition()设置Fragment 切换动画
4. listview设置高亮:setChoiceMode(),setItemChecked()
5. Fragment状态发生改变时,数据的保存和恢复:onSaveInstanceState(),临时变量记录数据,Bundle存储
6. 多个Fragment切换,通过addToBackStack()实现用户向后导航的功能
7. 在onStart()中为Fragment的视图设置数据
资源
Building a Dynamic UI with Fragments示例demo1
示例demo2
相关文章推荐
- 浅析php与数据库代码开发规范
- javascript代码规范小结
- PHP 代码规范小结
- Android Fragment 基本了解(图文介绍)
- Android程序开发之Fragment实现底部导航栏实例代码
- Android应用开发中Fragment与Activity间通信示例讲解
- Android的Fragment的生命周期各状态和回调函数使用
- 浅谈Android App开发中Fragment的创建与生命周期
- 实例探究Android开发中Fragment状态的保存与恢复方法
- Android Fragment的使用方法(翻译)
- 实例探究Android应用编写时Fragment的生命周期问题
- Android App开发中创建Fragment组件的教程
- Android中的Fragment类使用进阶
- 使用Fragment来处理Andoird app的UI布局的实例分享
- Android App中使用ListFragment的实例教程
- Android中Fragment子类及其PreferenceFragment的创建过程演示
- Android应用开发中Fragment间通信的实现教程
- Android 开发之BottomBar+ViewPager+Fragment实现炫酷的底部导航效果
- Android 中 Fragment的使用大全
- Android基于ViewPager Fragment实现选项卡