在这张来说是为了适应平板界面来开发,利用双版面主从用户界面
这边的代码验证需要用到AVD或时平板设备,首先需要选择Tools->android->AVDManager菜单项,然后点击Create Virtual Device按钮,在弹出的界面中选择Table类别,然后选择目标硬件配置后,点击Next继续,确认api至少有21以上
一.增加布局的灵活性,需要它在手机上显示单版面布局,在平板上显示主从视图,我们这边需要为它生成双版面的布局
在双版面布局中,CrimeListActivity将同时托管CrimeListFragment和CrimeFragment,
下面修改SingleFragmentActivity,为了让这个类灵活易用,我们让它的子类自己提供布局资源ID
在这个类中,提供一个protected方法,返回activity需要的布局资源ID,
@LayoutRes
protected int getLayoutResId(){
return R.layout.activity_fragment;
}
在onCreate()方法中换成
setContentView(getLayoutResId());
现在如果子类不想用这个布局就可以去覆盖上面的方法去返回子类自己需要的布局,上面那个注解的意思是任何时候该方法都必须返回有效的布局资源id
二创建包含两个fragment容器的布局,先建立一个以LinearLayout为,名为activity_twopane根目录的布局,然后在下面写两个FrameLayout,利用
llayout_weight 这个属性来分区间大小
但是如果想要宽度来按比例的话,那么layout_width = "0dp"
此时如果在CrimeListActivity中返回生成双版面的布局的话,那么此时不管是在手机上还是平板上都会返回双版面的布局的
三使用别名资源来自由转换布局的选择
别名资源是一种指向其他资源的特殊资源,它放在res/values/目录下面,并约定定义在refs.xml文件中
右击res/values/目录,新建values资源文件,在弹出的新建xml文件界面中选择资源类型为values,并将文件命名为refs.xml,确认文件不带任何修饰符
在新建的文件里面添加item节点
<resources>
<item name = "activity_materdetail" type = "layout">@layout/activity_fragment</item>
</resources>
此时别名资源指向了单版面的布局资源文件,别名资源也有自身的id:R.layout.activity_masterdetail,别名的tpye属性决定了资源id属于什么内部类,
然后把布局切换成
protected int getLayoutResId(){
return R.layout.activity_masterdetail;
}
下面来创建平板设备专用的可选资源,现在创建一个可选的别名资源,然后让activity_masterdetail别名指向activity——twopane.xml双版面资源布局
右击res/values/目录,然后在弹出的窗户里面把资源文件和目录名依然选为refs.xml和values,这次要用>>按钮把Available qualifiers中的 Screen Width选到右边窗口中去,在Smallest Screen Width的数值中选择600,然后点击OK,在这里需要把activity_masterdetail别名资源指向activity_twopane
<resources>
<item name = "activity_materdetail" type = "layout">@layout/activity_twopane</item>
</resources>
现在,对于小于指定尺寸的设备,我们使用activity_fragment.xml资源文件,对于大于指定尺寸的设备,使用activity_twopane.xml资源文件
这里的sw指的是最小宽度,实际指的是屏幕的最小尺寸
四activity :fragment的托管者
在双版面界面中如果我们要更新细节界面,我们可以直接在CrimeListFragment.CrimeHolder中实现下面的代码
Fragment fragment = CrimeFragment.newInstance(mCrime.getId());
FragmentManager fm = getActivity().getSupportFragmentManager();
fm.beginTransaction().add(R.id.detail_fragment_container,fragment).commit();
这样可以,但是fragment天生是一个独立的开发构件,如果开发fragment用来添加其他fragment到activity的FragmentManager,那么这个fragment就必须知道托管activity是怎么样工作的,此时fragment就不能独立开发了
为了让fragment独立,我们可以在fragment中定义接口,然后委托托管activity来完成那些不应由fragment处理的任务
六fragment回调接口
要委托工作任务给托管的activity,那么通常的做法就是由fragment定义Callbacks的回调接口,回调接口定义了fragment委托给托管activity处理的工作任务,任何打算托管目标fragment的activity都必须实现它
有了回调接口,就不用关心谁是托管者,fragment都可以调用托管的activity的方法,
(1)实现CrimeListFragment.Callbacks回调接口
为了实现Callbacks接口,首先要定义一个成员变量,用于存放Callbacks接口的对象,然后将托管activity强制类型转换为Callbacks对象并赋值给Callbacks类型变量
activity的赋值是在Fragment的生命周期方法调用的,在onAttach()方法中
最后需要在相应的onDetach()方法中将Callbacks变量设置为null
public class CrimeListFragment extends Fragment {
private Callbacks mCallbacks;
public interface Callbacks {
void onCrimeSelected(Crime crime);
}
public void onAttach(Context context){
super.onAttch(context);
mCallbacks = (Callbacks)activity;
}
public void onDetach(){
super.onDetach();
mCallbacks = null;
}
}
现在CrimeListFragment就有方法来调用托管activity的方法了,它不管托管activity是谁,只要它实现了CrimeListFragment.Callbacks接口就好
未经类安全检查就将托管activity强制转换为CrimeListFragment.Callbacks的对象,说明托管activity必须实现该接口
二在托管activity中去继承接口并实现接口的方法
public class CrimeListActivity extends SingleFragmentActivity implements CrimeListFragment.Callbacks{
public void onCrimeSelected(Crime crime){
if(findViewById(R.id.detail_fragment_container) == null){
Intent intent = CrimePagerActivity.newIntent(this,crime.getId());
startActivity(intent);
}else{
Fragment newDetail = CrimeFragment.newInstance(crime.getId());
getSupportFragmentManager().beginTransaction().replace(R.id.detail_fragment_container,newDetail).commit();
}
}
}
最后用户在创建新的Crime记录时,CrimeListFragment将在CrimeHolder.onClick()方法中调用onCrimeSelected(crime)方法
在这个方法中我们可以通过检查布局中是不是有detail_fragment_container,因为我们只关心布局文件是不是包含可以放入CrimeFragment的detail_fragment_container中就好,具体实现看上面方法
现在只需要在CrimeHolder.onClick(View)方法和onOptionsItemSelected(MenuItem)两个方法中调用上面的方法就可以了,也就是在CrimeListFragment中需要更新的地方运行上面的回调函数
mCallbacks.onCrimeSelected(crime);
但是在onOptionItemSelected方法中要在调用这个方法前去使用updateUI方法来更新列表,因为在平板设备上,新增crime记录后,crime列表依然会存在
三但此时还不够完美,因为如果修改了crime明细内容后列表项不会显示最新的内容,在CrimeListFragment.onResume()方法中只要新添加crime记录,我们就可以立即刷新显示列表界面,但是在平板设备上,CrimeListFragment和CrimeFragment同时存在,因此CrimeFragment出现时,CrimeListFragment不会暂停,不会暂停怎么会恢复尼?这也就是crime列表项不能刷新的根本原因,
所以我们可以在CrimeFragment中添加另一个回调接口来修正这个问题,
public interfact Callbacks{
void onCrimeUpdated(Crime crime);
}
如果CrimeFragment需要刷新数据,需要做两件事,首先这个应用的数据源是SQLite数据库,那么它需要将crime保存到CrimeLab里,然后CrimeFragment类会调用托管activity的onCrimeUpdate(Crime)方法,CrimeListActivity类会负责实现onCrimeUpdated(Crime)方法,从数据库获取和展现最新数据,重新加载CrimeListFragment的列表
(1)首先需要先把CrimeListFragment的updateUI方法的可见性改为public
(2)在CrimeFragment中
private Callbacks mCallbacks;
public interfact Callback{
void onCrimeUpdated(Crime crime);
}
public void onAttach(Context context) {
super.onAttach(context);
mCallbacks = (Callbacks) context;
}
public void onDetach(){
super.onDetach();
mCallback = null;
}
(3)在CrimeListActivity中实现CrimeFragment.Callbacks接口,以实现在onCrimeUpdate(Crime)方法中重新加载crime列表
public class CrimeListActivity extends SingleFragmentActivity implements CrimeListFragment.Callbacks,CrimeFragment.Callbacks{
public void onCrimeUpdate(Crime crime){
CrimeListFragment listFragment = (CrimeListFragment)getSupportFragmentManager().findFragmentById(R.id.fragment_container);
listFragment.updateUI();
}
}
所有托管CrimeFragment的所有activity都必须实现CrimeFragment.Callback接口
五.在CrimeFragment中有两个重复的任务,(1)在CrimeLab中保存mCrime,(2)调用mCallbacks.onCrimeUpdated(Crime)所以可以使用一个私有的方法来处理他们
private void updateCrime(){
CrimeLab.get(getActivity()).updateCrime(mCrime);
mCallbacks.onCrimeUpdated(mCrime);
}
然后就需要在crime对象的标题或是问题处理状态有变动时触发调用updateCrime()方法
六设备屏幕的尺寸的确认
屏幕大小修饰符将不同的设备分出为四大类别,small,noral,large,xlarge
small 320*426
normal 320*470
large 480*640
xlarge 720*960
新修饰符
xXXXdp 有效宽度大于或是等于XXXdp
hXXXdp 有效高度大于或是等于XXXdp
swXXXdp 最小宽度或高度(两两者最小那个)大于或是等于XXXdp