《Android编程权威指南》-读书笔记(十一) 完善CriminalIntent
在上篇文章中,我们跟随本书作者,使用了单个的Fragment做了最简单的插入操作。本篇文章将跟随作者进行更深入的完善各种功能。
本章目标:
- 使用ListFragment显示列表
- fragment之间传递数据
- 使用ViewPager来实现划屏显示
- 对话框
使用ListFragment显示列表
如果是挑战,或者没有UI我会给出草图之类的UI。或手画或Axure原型。每次例子本书都给出了最终UI,所以这步基本都可以省了。本章在例子CriminalIntent中使用ListFragment。以达到如下的效果:
资源相关
在这个UI中所有的数据都是根据动态数据显示的,因为没有用到任何图片资源或者字符串资源。所以没有修改的地方。(目前的资源默认的为按钮文字,界面上出现的文字等)
数据相关
现在需要显示一串数据,书中新增了一个可以容纳多个Crime对象ArrayList类。它定义了2个私有变量
private static CrimeLab sCrimeLab;
private Context mAppContext;
s开头的变量是开发的命名约定。它代表了变量sCrimeLab是一个静态变量。
这个类的代码现阶段如下:
http://git.oschina.net/canglin/CriminalIntent/commit/2f5580a992c804949a14a921dae7535a6097532f
这个类里面定义了个一个get方法用来传入一个Context。而在18行sCrimeLab直接调用了构造函数,构造函数的参数却是getApplicationContext(),这是因为Context可能是一个Activity,也可能是一另一个Context对象,如Service。在应用的整个生命周期里,无法保证只要CrimeLab需要用到Context,Context就一定会存在。所以才使用getApplicationContext()。至于12行public 的构造函数我写错了,我将会在下个版本的Git镜像中做修正。
将一些Crime对象保存到CrimeLab中去。增加一个Crime的ArrayList列表,并添加Getter()方法。
然后在增加一个查询操作在CrimeLab中为getCrime(UUID id)。我更倾向于使用getCrimeById()这种命名法。
完成后的CrimeLab最干净版不包含模拟数据代码如下:
http://git.oschina.net/canglin/CriminalIntent/commit/e4a9e6fac36f7eca76e063b406e194338f3fa778
逻辑相关
下图是CriminalIntent应用的整体规划设计
这个应用是在容器视图中显示列表。我们要创建一个ListFragment和一个Activity,还有与ListFragment相匹配的layout。
Fragment
创建CrimeListFragment类扩展自ListFragment。HoneyComb系统版本引入了ListFragment类,相应的,支持库也引入了该类。
import android.support.v4.app.ListFragment;
ListFragment是通过ListView将列表项展示给用户。而ListView通过adapter来申请视图对象。
Adapter负责:
创建必要的视图对象;
用模型层数据填充视图对象;
将准备好的视图对象返回给ListView。
随意例子中采用了setListAdapter(ListAdapter)来为CrimeListFragment管理内置ListView设置adapter。(详情参看后面的代码链接,现在就可以打开它对比观看)
FragmentActivity
由于每一个ActivityFragment都有相似的代码,于是作者创建了一个SingleFragmentActivity抽象类用来减少以后的代码输入。在书中的例子都是在一个FragmentContainer动态添加一个Fragment,所以唯一不同的代码就是在事物添加Fragment之前动态创建的代码。
修改CrimeActivity扩展自SingleFragmentActivity。
创建CrimeListActivity扩展自SingleFramentActivity。
因为这2个类唯一的区别
都只是在23行而已。采用抽象的方法后,每个扩展自SingleFragmentActivity的类都必须@Override掉createFragment()。
视图相关
需要在res/layout/list_item_crime.xml中的如下:
代码如下:
http://git.oschina.net/canglin/CriminalIntent/commit/05a1da49e017dbdcef2d795d6da07eca41a3a006
在本章中只关注下面的类和layout资源文件。其他的删除掉都可以,不会影响应用的正常运行。
对象
Crime 列表子元素的对象
CrimeLab 可以创建和获取一个Crime 列表。
Activity
CrimeListActivity 扩展自SingleFragmentActivity 创建CrimeListFragment 事物
SingleFragmentActivity
Fragment
CrimeListFragment 根据list_item_crime.xml 生成相应的View
Res/layout
Activity_fragment.xml 定义了容易视图
List_item_crime.xml 定义了列表子元素的视图
由于默认的Activity 不是CrimeListActivity
<activity android:name=".CrimeListActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
这样设置后默认的将会是CrimeListActivity。
在例子初期做的很随意,为后面几个目标做准备,我将源代码做了一些调整,调整后代码如下:
http://git.oschina.net/canglin/CriminalIntent/commit/05a1da49e017dbdcef2d795d6da07eca41a3a006
使用fragment argment
我们学习过在Activity中是调用startActivity(Intent)来启动另外一个activity。现在在Fragment中还是调用startActivity(Intent)来启动Activity。
在点击ListFragment将会显示详细的信息。详细信息界面应该是在第8章中完成。
界面设置好后,资源文件的字符串最初是这个样子。因为增加了几个字符串,但是还没有在strings.xml中添加,效果如下图所示:
资源文件填写完成后
代码如下:
http://git.oschina.net/canglin/CriminalIntent/commit/918aed3dd490b12f39f00701e4806cada3ad0b63
到现在为止界面界面基本成型,里面所有的事件,以及数据处理,还有业务逻辑我都把它精简到了最低的程度。中间很多地方在本书中是要求加入一些非UI代码的,我都没有加入。至于列表的数据那一块,也只是为了让界面逻辑能够完整。
给CrimeFragment填充数据
使用之前学到的方法用Intent传递数据,在CrimeFragment的onCreate()中从Intent读取数据。
首先定义一个id
public static final String EXTRA_CRIME_ID =
"com.example.lijing.criminalintent.crime_id";
…..
然后在onCreate()中
UUID crimeId = (UUID)getActivity().getIntent().getSerializableExtra(EXTRA_CRIME_ID);
mCrime = CrimeLab.get(getActivity()).getCrime(crimeId);
数据在CrimeListFragment中startActivity()之
putExtra(CrimeFragment.EXTRA_CRIME_ID,c.getId());
完成后界面是这个样子:
代码发生如下变动:
http://git.oschina.net/canglin/CriminalIntent/commit/657c3d4164e08968dca123c3babec0ee80f97327
界面之间传递数据的改进
原先的代码如下
UUID crimeId = (UUID)getActivity().getIntent().getSerializableExtra(EXTRA_CRIME_ID);
在这段代码中crimeId是存储在CrimeActivity中的。现在将它由CrimeActivity的intent内的extra改为arguments bundle。每个fragment实例都可以附带一个Bundle对象。该bundle包含有key-value对,我们可以如同附加extra到Activity的intent中那样使用它们。一个key-value对即一个argument。
UUID crimeId = (UUID)getArguments().getSerializable(EXTRA_CRIME_ID);
并给fragment一个newInstance()来创建自己,并在这个静态方法里创建arguments。
总的来说就是由以前从Activity中获取参数,改成从自己的存储区里获取参数。而自己的存储区里的参数是在创建该fragment是写入的。
这是代码的改动:
http://git.oschina.net/canglin/CriminalIntent/commit/95d7da1d76c2ac316cdb1f1af823df3cb745e133
使用ViewPager来实现划屏显示
为了实现这个效果,我们需要创建一个ViewPager的activity,命名为CrimePagerActivity来取代CrimeActivity。本章采用了以代码的方式创建视图它包含以下步骤:
- 为ViewPager创建资源ID;
- 创建ViewPager实例并赋值给mViewPager;
- 赋值资源ID给ViewPager,并对其进行配置;
- 设置ViewPager为activity的内容视图。
创建独立资源ID(res/values/ids.xml)
定义独立资源ID与定义字符串资源ID并没有什么不同:在res/values目录下的XML文件中创建一个项目元素。创建一个名为res/values/ids.xml的Android XML 资源文件。
以代码的方式创建内容视图(CrimePagerActivity.java)
mViewPager = new ViewPager(this);
mViewPager.setId(R.id.viewPager);
setContentView(mViewPager);
然后我们使用FragmentStatePagerAdaper为我们的代理,负责管理与ViewPager的对话并协同工作。
代码如下:
http://git.oschina.net/canglin/CriminalIntent/commits/master
到这里在详细信息界面就实现了拖动。
现在程序有一个Bug就是,当我点击一个详细信息的时候,详细信息界面中显示的永远是第一条。现在我们通过设置setCurrnetItem(index)是当前详细页面的信息是选中的选项。
有关ViewPager.OnPageChangeListener
在本书的例子中,当页面发生改变的时候将标题设置给CrimePagerActivity。在我的代码中我貌似将显示标题的位置拿掉了。
这个是官方的api地址
这个方法必须重写3个抽象的方法,如果现在不知道写什么可以将3个方法复制进去就可以了。
mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
public void onPageScrollStateChanged(int state) {}
public void onPageScrolled (int position, float positionOffset, int positionOffsetPixels) {}
public void onPageSelected (int position) {
Crime crime = mCrimes.get(position);
if (crime.getTitle() != null) {
setTitle(crime.getTitle());
}
}
});
就像这个样子,但是这段代码在现在的程序里,是不会有界面改变的。所以在这里特别强调一下。
完成后代码如下:
http://git.oschina.net/canglin/CriminalIntent/commits/master
对话框
在我们CrimeFragment对应的详细信息界面里有一个时间设置按钮。现在我们就按照书中的要求完善这个按钮的对话框。
作者采用将AlertDialog封装在DialogFragment的方法来显示对话框,因为有如下优点:
- 使用FragmentManager管理对话框,可以使用更多配置选项来显示对话框;
- 发生旋转时封装在fragment中的AlertDialog不会消失
在屏幕上显示DialogFragment时,托管activity的FragmentManager会调用onCreateDialog()。在onCreateDialog里我们需要返回一个AlertDialog.Builder。
在显示对话框的时候要注意。要将DialogFragment添加给FragmentManager管理并放置到屏幕上,可以调用fragment的show方法。
Public void show (FragmentManager manager, String tag)
Public void show(FragmentTransactiong transaction, String tag)
String参数是用来队列中的DialogFragment。在FragmentManager和FragmentTransaction的选择上,书中选择了FragmentManager因为传入这个参数,事物可以自动创建提交。
最简单的界面效果如下:
现阶段代码如下:
http://git.oschina.net/canglin/CriminalIntent/commit/ddf4b0365937ea287d0f64f7b437c1822f8745b0
对话框之间的数据交互,在本章已经简单介绍过了。书中在这里是继续完成了的。本着达到目标最简单的代码原则,这些代码先不提交到Git。
在每个小段落最后都会有当前阶段的Git代码地址。
小结:
到现在为止,基本上Android的一些最基本的界面,最基本的业务逻辑,数据逻辑都已经完成了。每个阶段的代码都是尽量的删减到最少,为了方便在Git中查看修改的过程,避免不必要的误导。