第五章 第二个activity
一、Android Tools属性
<TextView
android:id="@+id/answer_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
tools:text="Answer"/>
1、介绍
注意这个TextView组件,它的 tools 和 tools:text 属性的命名空间比较特别。该命名空间可以覆盖某个组件的任何属性。这样,就可以在Android Studio预览中看到效果。TextView有 text 属性,可以为它提供初始值,因而在应用运行前就知道它大概的样子。而在应用运行时,Answer 文字不会显示出来。
2、具体解释和使用
(1)解释
我们写的UI效果很多时候需要提前预览,我们一般的做法是这样的:比如像ImageView、TextView这种基础控件,我们会添加android:text="xxx"
和 android:src="@drawable/xxx"
这种属性去进行测试和预览,而这些数据多了之后,很有可能遇到:测试的时候某些地方出现了本不该出现的数据,也就是某些测试数据未删除的情况。
所以我们会想:有没有一种方法既能预览数据又能在真实运行的时候移除这些无关的数据?
而 Android 的 Tools Attributes 就可以实现这个效果。
(2)使用
https://www.cnblogs.com/moosphon/p/11565829.html#3222832688
这里面有详细介绍,我觉得挺好(这地址是人家的帖子,不是我的~~~)
第六章 Android SDK版本与兼容
一、Android SDK版本
1、SDK最低版本(minSdkVersion)
以最低版本设置值为标准,操作系统会拒绝将应用安装在系统版本低于标准的设备上。
2、SDK目标版本(targetSdkVersion)
目标版本的设定告诉Android:应用是为哪个API级别设计的。大多数情况下,目标版本即最新发布的Android版本。
什么时候需要降低SDK目标版本呢?新发布的SDK版本会改变应用在设备上的显示方式,甚至连操作系统后台运行行为都会受影响。如果应用已经开发完成,应该确认它在新版本上能否正常运行,根据测试结果,要么修改应用以适应新版本系统,要么降低SDK目标版本。
降低SDK目标版本可以保证的是,即便在高于目标版本的设备上,应用仍然可以正常运行,且运行行为和目标版本保持一致,这是因为新发布版本中的变化已被忽略。
3、SDK编译版本(compileSdkVersion)
SDK最低版本和目标版本会通知操作系统,而SDK编译版本只是你和编译器之间的私有信息。
在编译代码时,SDK编译版本(即编译目标)指定具体要使用的系统版本。Android Studio在寻找类包导入语句中的类和方法时,编译目标确定具体的基准系统版本。
编译目标的最佳选择为最新的API级别,当然,需要的话也可以改变应用的编译目标。例如,Android新版本发布时,可能就需要更新编译目标,以便使用新版本引入的方法和类。
4、安全添加新版本API中的代码
SDK最低版本和编译版本之间差异太大,由此会带来兼容性问题。比如:最低版本为API 19,编译版本为API 21,那如果调用了API 21的代码而API 19中没有这个代码的话会怎样呢?结果显示如果在API 19 设备上安装,程序会崩溃。
随着Android lint的精进,现在在老版本系统上调用新版本代码时,Android lint会告警提示,如果没有看到提示,就选择Analyze——>Inspect Code...菜单项手动触发lint。
因为SDK编译版本为API 21,编译器本身编译代码没有问题,而Android lint知道项目SDK最低版本,所以及时指出了问题,这个告警信息你可以忽略,但是运行的时候就可能会崩溃,所以最好还是消除这些错误信息。一种方法是提升SDK最低版本到21,但是这种方法只是回避了兼容问题,应用不能在低于API 21的设备上安装了,自然就没有刚才提到的兼容问题了,但是这实际上并没有解决兼容问题;比较好的做法是在代码中判断Android设备版本,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//如果设备API高于LOLLIPOP,就可以做一些操作
}else{
//如果没有,就需要避免使用高版本API中才有的方法
}
5、Android开发文档
Android文档可脱机查看,浏览SDK安装文件所在目录,找到 docs 目录,该目录包含了全部的Android开发者文档内容。
第七章 UI fragment与 fragment 管理器
一、Gradle中添加依赖包
添加依赖包的示例:
compile 'com.android.support:appcompat-v7:26.0.1'
这个依赖项字符串使用了Maven坐标模式:groupId:artifactId:version。(Maven是一个依赖包管理工具,详见其官方网站:https://maven.apache.org/ )。
groupId,通常是类库的基础包名,能唯一标识Maven仓库中的依赖类库,就像上面的com.android.support
artifactId,是包中的特定库名,这里就是指 appcompat-v7
version,是指类库的版本号
二、fragment的生命周期
三、Activity中添加fragment
1、activity添加fragment的方式
activity托管fragment有两种方式:
- 在activity布局中添加fragment
- 在activity代码中添加fragment
第一种方式就是使用布局fragment。这种方式简单但是不够灵活。在activity布局中添加fragment,就等同于将fragment及其视图绑定在一起,并且在activity的生命周期过程中,无法替换fragment视图。
第二种方式比较复杂,但也是能够动态控制fragment的唯一方式。何时添加fragment以及随后可以完成何种具体任务由你定;也可以移除fragment、替换fragment、重新添加已移除的fragment等。
2、fragment事务
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction().add(R.id.fragment_container,fragment).commit();
fragment事务被用来添加、移除、附加、分离或替换fragment队列中的fragment。这是使用fragment动态组装和重新组装用户界面的关键。FragmentManager管理着fragment事务回退栈。
第八章 使用RecyclerView显示列表
一、RecyclerView的优势
Android操作系统核心库包含ListView、GridView、Adapter这三个类。Android 5.0之前,创建列表项和网格项都应该优先使用这些类。
这些类的API与RecyclerView的API非常相似。ListView和GridView不关心具体的展示项,只负责展示项的滚动,Adapter负责创建列表项的所有视图,不过使用ListView和GridView时不一定非要使用ViewHolder模式(虽然可以并且应该使用)。
过去传统的实现方式现已被RecyclerView的实现方式取代,因此不用再费力的调整ListView和GridView的工作行为了。
举例来说,ListView API不支持创建水平滚动的ListView,因此需要很多额外的定制工作。使用RecyclerView时,虽然创建定制布局和滚动行为也需要额外的工作,但RecyclerView天生支持拓展,所以使用体验很好。
此外,RecyclerView还有支持列表项动画效果的优点。如果要让ListView和GridView支持添加和删除列表项的动画效果,实现起来既复杂又容易出错;而对于天生支持动画特效的RecyclerView来说,对付这些任务简直是小菜一碟。
二、使用单例的优缺点
Android开发实践中,经常会用到单例模式。然而单例如果使用不当,会导致应用难以维护,因此它也常遭人诟病。
Android开发常用到单例的一大原因是,它们比 fragment 或 activity 活得久。例如,在设备旋转或是在 fragment 或 activity间跳转的场景下,单例却不会受到影响,而旧的 fragment 或 activity 已经不存在了。
1、单例的优点
单例能方便地存储和控制模型对象。假设一个模型类DataBean,在一个比较复杂的应用中,它的许多个fragment和activity都会修改DataBean的数据,某个控制单元修改了DataBean的数据之后,怎么保证发送给其他控制单元的是最新数据呢?如果创建一个单例类来控制数据对象 DataBean,所有的修改都由它来处理,是不是控制数据的一致性就容易多了?而且,在控制单元间流转时,还可以给每个 dataBean 添加 ID 表示,让控制单元使用 ID 标识从单例类中获取完整的 dataBean 数据。
2、单例的缺点
虽然单例能存储数据,活的也比控制单元长,但并不代表它能永存。在我们切换至其他应用,或逢Android回收内存时,单例连同那些实例变量也就不复存在了。结论很明显:单例无法做到持久存储。(将文件写入磁盘或者发送到web服务器是不错的数据持久化存储方案)。
单例还不利于单元测试。
使用单例很方便,因而它很容易被滥用。在想用就用、想存就存之前,希望能深思熟虑:数据究竟用在哪里?用在哪里能真正解决问题?
这里讲的这些只是从本书上看来的,并不详细,如果想要详细了解,可以自行去查找详细解析。
第九章 使用布局与组件创建用户界面
一、日期格式化
public String StringData() {
final Calendar c = Calendar.getInstance();
c.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
String mYear = String.valueOf(c.get(Calendar.YEAR)); // 获取当前年份
String mMonth = String.valueOf(c.get(Calendar.MONTH) + 1);// 获取当前月份
String mDay = String.valueOf(c.get(Calendar.DAY_OF_MONTH));// 获取当前月份的日期号码
String mHour = String.valueOf(c.get(Calendar.HOUR_OF_DAY));//获得当前小时
String mMinute = String.valueOf(c.get(Calendar.MINUTE));//获得当前分钟
String mSecond = String.valueOf(c.get(Calendar.SECOND));//获得当前秒
String mWay = String.valueOf(c.get(Calendar.DAY_OF_WEEK));
if ("1".equals(mWay)) {
mWay = "天";
} else if ("2".equals(mWay)) {
mWay = "一";
} else if ("3".equals(mWay)) {
mWay = "二";
} else if ("4".equals(mWay)) {
mWay = "三";
} else if ("5".equals(mWay)) {
mWay = "四";
} else if ("6".equals(mWay)) {
mWay = "五";
} else if ("7".equals(mWay)) {
mWay = "六";
}
return mYear + "年" + mMonth + "月" + mDay + "日" + mHour + "时" + mMinute + "分" +
mSecond + "秒" + "/星期" + mWay;
}
第十章 使用 fragment argument
一、fragment 获取数据的方式
fragment 有两种方式获取 intent 中的数据,一种简单直接,另一种复杂但比较灵活。
1、简单方式的使用和其缺点
(1)使用
直接使用 getActivity() 方法获取 Activity 的 intent,比如:
UUID crimeId = (UUID) getActivity().getIntent()
.getSerializableExtra(CrimeActivity.EXTRA_CRIME_ID);
这种方式,除了 getActivity() 方法的调用,获取 extra 数据的实现代码与 activity 里获取 extra 数据的代码一样。
(2)缺点
这种方法破坏了fragment的封装。使用此方法的Fragment不再是可复用的构建单元,调用了getActivity() 之后就代表它现在由某个特定的Activity托管着,该特定Activity的 Intent 又定义了名为 EXTRA_CRIME_ID 的extra。这就意味着当前Fragment便再也无法用于其他Activity了。
2、复杂方法
一个比较好的做法是,将 extra 数据存储在Fragment的“某个地方”,而不是保存在Activity的私有空间里,这样就无需依赖Activity的Intent内的extra,Fragment就能获取自己所需的extra数据。“某个地方”就是fragment的 argument bundle。
每个fragment的实例都可附带一个Bundle对象,Bundle对象包含键--值对,一个键值对即一个argument。
要附加 argument bundle 给fragment,需要调用fragment.setArguments(Bundle)方法,而且,必须在fragment创建后、添加给activity前完成。
Android开发人员采取的习惯性做法:
在Fragment类中,编写可以接受UUID参数的newInstance(UUID)方法,创建 argument bundle 和 fragment 实例,然后附加 argument 给 fragment:
private static final String ARG_CRIME_ID = "crime_id";
public static CrimeFragment newInstance(UUID crimeId){
Bundle args = new Bundle();
args.putSerializable(ARG_CRIME_ID,crimeId);
CrimeFragment fragment = new CrimeFragment();
fragment.setArguments(args);
return fragment;
}
创建Fragment时,其托管的Activity应调用CrimeFragment.newInstance(UUID)方法,并传入从它的extra中获取的UUID参数值:
/**
*这个方法是Activity超类的抽象方法,在每个Activity子类实现该方法,此方法代码是在托管Fragmen的
*Activity中实现的
*/
@Override
protected Fragment createFragment() {
UUID crimeId = (UUID) getIntent().getSerializableExtra(EXTRA_CRIME_ID);
return CrimeFragment.newInstance(crimeId);
}
获取 argument:
UUID crimeId = (UUID) getArguments().getSerializable(ARG_CRIME_ID);
注意,Activity和Fragment不需要也无法同时相互保持独立, Activity必须了解Fragment的内部细节,以便托管fragment,但fragment不一定需要知道其托管Activity的细节,至少在需要保持fragment通用独立的时候如此。
二、为何要使用 fragment argument
fragment argument 的使用有点复杂,为什么不直接创建实例变量或者通过构造方法传递参数?
创建实例变量的方式并不可靠。因为操作系统在重建fragment时(设备配置发生改变),用户暂时离开当前应用(操作系统按需回收内存),任何实例变量都将不存在。
通过构造方法传递参数也是有隐患的。根据Android文档说明,当一个Fragment重新创建的时候,系统会再次调用Fragment中的默认构造函数,注意是默认构造函数。即,当你创建了一个带有参数的Fragment的之后,一旦由于什么原因(例如横竖屏切换)导致你的Fragment重新创建。那么,很遗憾,你之前传递的参数都不见了,因为recreate你的Fragment的时候,调用的是默认构造函数。因此,官方推荐使用Fragment.setArguments(Bundle bundle)这种方式来传递参数,而不推荐通过构造方法直接来传递参数。
因此,可以使,fragment argument 就是为应对上述场景而生。
还有一种方法应对上述场景:使用实力状态保存机制。就是将Crime ID 赋值给实例变量,然后在onSaveInstanceState(Bundle)方法中保存下来。但是这种方法维护成本比较高,比如:如果你在若干年后要修改fragment代码以添加其他argument,很可能会忘记在onSaveInstanceState(Bundle)方法里保存新增的argument。
第十一章 使用ViewPager
一、FragmentStatePagerAdapter 与 FragmentPagerAdapter
FragmentManager fragmentManager = getSupportFragmentManager();
mViewPager.setAdapter(new FragmentStatePagerAdapter(fragmentManager) {
@Override
public Fragment getItem(int position) {
Crime crime = mCrimes.get(position);
return CrimeFragment.newInstance(crime.getId());
}
@Override
public int getCount() {
return mCrimes.size();
}
});
FragmentPagerAdapter 用法和 FragmentStatePagerAdapter 基本一致。唯一的区别在于,卸载不再需要的Fragment时,各自采用的处理方法有所不同。
FragmentStatePagerAdapter 会销毁不需要的 Fragment。事务提交后,Activity的 FragmentManager 中的Fragment会被彻底移除。FragmentStatePagerAdapter 类名中的“State”表明:在销毁Fragment时,可在 onSaveInstanceState(Bundle) 方法中保存Fragment的 Bundle 信息,用户切换回来时,保存的实例状态可用来生成新的Fragment。
相比之下,FragmentPagerAdapter 有不同的做法,对于不再需要的Fragment,FragmentPagerAdapter 会选择调用事务的detach(Fragment) 方法来处理它,而非 remove(Fragment) 方法。也就是说,FragmentPagerAdapter 只是销毁了Fragment的视图,而Fragment实例还保存在FragmentManager中。因此,FragmentPagerAdapter 创建的Fragment永远不会被销毁。
选择哪种adapter取决于应用的需求。通常来说,使用FragmentStatePagerAdapter 更节省内存。如果用户界面只需要少量的固定的Fragment,则FragmentPagerAdapter 是安全、合适的选择。最常见的例子就是使用 Tab 选项页显示用户界面。此外,将Fragment保存在内存中,更易于管理控制器层的代码。对于这种类型的用户界面,每个Activity通常只需两三个Fragment,基本不用担心内存不足的危险。
二、深入学习:ViewPager的工作原理
什么时候需要实现PagerAdapter接口?
需要ViewPager托管非Fragment视图时,就需要实现原生PagerAdapter接口。
三、深入学习:以代码的方式创建视图
以代码的方式创建视图很简单:调用视图类的构造方法,并传入Context参数。不创建任何布局文件,用代码就能创建完整的视图层级结构。
但是,最好不要这样做。下面就来说说使用布局文件的好处:
布局文件能很好地分离控制器层和视图层对象:视图定义在XML布局文件中,控制器层对象定义在Java代码中。这样,假设控制器层有代码修改的话,代码变更管理相对容易很多;反之亦然。
另外,使用布局文件,我们还能使用Android的资源适配系统,实现按设备属性自动调用合适的布局文件(就像之前说过的横屏模式的布局文件)。
当然,布局文件也不是毫无缺点。如果应用只需一个视图,估计没人愿意麻烦地创建并实例化布局XML文件。
除此之外,创建布局文件的缺点都不值得一提。