8种机械键盘轴体对比
本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?
本章创建了适配tablet的界面布局——在phone下默认是list,点击item后展现detail;在tablet下是list|detail双视图模式。
本章要点:为资源创建别名
为不同尺寸的屏幕创建不同的布局
在res/values右键 > New > Values Resource File:
创建文件res/values/refs.xml如下:1
2
3
4
5
type="layout">@layout/activity_fragment
这就为R.layout.activity_fragment创建了一个别名R.layout.activity_masterdetail,原先使用前者的地方,现在可以直接使用后者:1
2
3
4
5
6
7
8
9public class extends SingleFragmentActivity{
...
@Override
protected int getLayoutResId(){
return R.layout.activity_masterdetail;
// return R.layout.activity_fragment; // 这两行代码是等价的
}
}
根据设备尺寸为指定资源生成两套实现
本节为设备宽度大于600dp时创建了refs.xml的另一套实现,操作步骤为:res/values右键 > New > Values Resource File > 弹出New Resource File > 在Available qualifiers中选择Smallest Screen Width > 点击 >> > 在Smallest Screen Width:中填写600 > 点击OK:
sw是smallest width的缩写,sw600dp表示当设备的宽度大于600dp时,使用版本refs.xml(sw600dp)否则使用版本refs.xml。res/values/refs.xml(sw600dp)文件内容为:1
2
3
4
5
type="layout">@layout/activity_twopane
在屏幕宽度<600dp的phone下,activity_masterdetail=@layout/activity_fragment;
在屏幕宽度≥600dp的tablet,activity_masterdetail=@layout/activity_twopane。
在代码中使用activity_masterdetail,同时实现activity_twopane的布局,即可完成目标效果。
适配tablet的布局
目标是:让布局在phone下是List > 点击 > Details模式,在tablet下是List|Details模式,需要修改:
1、修改SingleFragmentActivity,使得它的布局不是hardcode写死的
2、创建新的布局,包含两个fragment容器
3、修改CrimeListActivity,使之在phone下加载一个Fragment容器,在tablet下加载两个容器
1.修改SingleFragmentActivity1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public abstract class SingleFragmentActivity extends AppCompatActivity{
protected abstract Fragment createFragment();
@LayoutRes
protected int getLayoutResId(){
return R.layout.activity_fragment;
}
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(getLayoutResId());// 修改此处,不再hardcode
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragment_container);
if(fragment == null){
fragment = createFragment();
fm.beginTransaction().add(R.id.fragment_container, fragment).commit();
}
}
}
把原先写死的加载布局R.layout.activity_fragment改成了从函数获取,这样子类就有机会重写函数getLayoutResId(),也就可以在子类中重新定义需要加载的布局了。
SingleFragmentActivity有两个子类:CrimeListActivity和CrimeActivity。要修改的是前者:在phone下只包含一个CrimeListFragment,在tablet下包含CrimeListFragment和CrimeFragment。
@LayoutRes关键字
[email protected],见书中P2464,表明该函数一定返回一个合法的layout资源ID。
2.创建容纳两个Fragment的新布局
activity_twopane.xml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="?android:attr/dividerHorizontal"
android:orientation="horizontal"
android:showDividers="middle">
android:id="@+id/fragment_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
android:id="@+id/detail_fragment_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3">
3.让CrimeListActivity兼容tablet1
2
3
4
5
6
7
8
9
10
11
12
13
14public class extends SingleFragmentActivity
implements CrimeListFragment.Callbacks, CrimeFragment.Callbacks{
@Override
protected Fragment createFragment(){
return new CrimeListFragment();
}
@Override
protected int getLayoutResId(){
return R.layout.activity_masterdetail;
}
...
}
这三步组合的逻辑是:SingleFragmentActivity根据子类提供的LayoutResId决定要加载的布局,并把子类提供的Fragment添加到Activity。
对于CrimeListActivity,在屏幕宽度<600dp的phone下,加载到的布局是单Fragment,创建的Fragment是CrimeListFragment;在屏幕宽度≥600dp的tablet下,加载到的布局是双Fragment,创建的Fragment也是CrimeListFragment,并对应到fragment_container上。
有个问题需要回头再研究的:Fragment和布局里的id究竟是怎么关联起来的呢?我记得在FragmentManager的事务中,id仅用来做标识的。
4.展现detail视图
其实仅这三步并没有完成对tablet的适配,因为details还没有出场。一般的想法是在CrimeListFragment中响应item点击,让CrimeFragment展现出来,但这样会导致list和detail视图之间的耦合,他们是平级的,相互之间应该解耦,这属于上层CrimeListActivity的业务逻辑,应该交给它处理。
设置回调
因此在本节由CrimeListActivity提供回调函数给CrimeListFragment,当item被点击时,后者只负责回调该函数,并不知道其内部细节。本节处理这块的手法非常精妙:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// CrimeListFragment.java
public class CrimeListFragment extends Fragment{
...
private Callbacks mCallbacks;
public interface Callbacks{
void onCrimeSelected(Crime crime);
}
@Override
public void onAttach(Context context){
super.onAttach(context);
mCallbacks = (Callbacks)context;
}
@Override
public void onDetach(){
super.onDetach();
mCallbacks = null;
}
...
}
它定义了一个接口类,该类必须有一个void onCrimeUpdated(Crime crime)接口。
回顾笔记七·fragment的生命周期,当CrimeListFragment被载入Activity时,Activity会调用它的onAttach(Context)函数,并把自己当做参数传入,即这个Contetxt其实是宿主Activity,他又被赋给了Callbacks类型的成员变量,这就要求宿主Activity必须有void onCrimeUpdated(Crime crime)接口,否则会出编译错误。
实现回调
接下来需要让宿主Activity遵守该接口,并响应点击事件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class extends SingleFragmentActivity
implements CrimeListFragment.Callbacks, CrimeFragment.Callbacks{
@Override
protected Fragment createFragment(){
return new CrimeListFragment();
}
...
@Override
protected int getLayoutResId(){
return R.layout.activity_masterdetail;
}
@Override
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();
}
}
}
回顾“创建容纳两个Fragment的新布局”,R.id.detail_fragment_container是在activity_twopane.xml中定义的。
再回顾“让CrimeListActivity兼容tablet”:在phone下它提供的布局是activity_fragment,因此找不到资源R.id.detail_fragment_container,if分支为真,将启动CrimePagerActivity;
在tablet下它提供的布局是activity_twopane,走else分支,使用CrimeFragment替换掉R.id.detail_fragment_container。
调用回调
最后一步是在点击菜单“+”或点击item时调用回调函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// CrimeListFragment.java
public class CrimeListFragment extends Fragment{
...
private class CrimeHolder extends RecyclerView.ViewHolder
implements View.OnClickListener{
...
@Override
public void onClick(View view){ // 响应item点击
mCallbacks.onCrimeSelected(mCrime);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
switch (item.getItemId()){
case R.id.new_crime: // 响应“+”
Crime crime = new Crime();
CrimeLab.get(getActivity()).addCrime(crime);
updateUI();
mCallbacks.onCrimeSelected(crime);
return true;
...
}
}
}
5.更新CrimeListFragment
完善业务逻辑,还需要在CrimeFragment中的数据被编辑更新后,同步更新左侧的CrimeListFragmen,手法和展现detail视图是一模一样的:在CrimeFragment中定义内部接口类,让CrimeListActivity遵守该接口类,并定义回调,供CrimeFragment调用。没什么新内容,这里就不再重复了。
设备尺寸决定的配置
在Android3.2之前使用设备的具体尺寸修饰资源文件,使得该资源文件能匹配不同的设备尺寸,具体的尺寸被分为四类:small, normal, large和xlarge,下表给出这四类对应的屏幕最小尺寸:
NameMinimum screen sizesmall320×426dp
normal320×470dp
large480×640dp
xlarge720×960dp
到了Android3.2之后具体尺寸修饰方式被废掉了,取代它的是维度修饰:
Qualifier formatDescriptionwXXXdp可用宽度≥XXXdp
hXXXdp可用高度≥XXXdp
swXXXdp最小宽度或高度≥XXXdp
例如为宽度≥300dp的屏幕创建布局文件,应为res/layout-w300dp。
sw的含义是最小宽度,例如在1024×800的屏幕sw就是800,在800×1024的屏幕,sw依然是800。