Android nrg格式,《Android Programming BNRG》笔记十七

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

本章创建了适配tablet的界面布局——在phone下默认是list,点击item后展现detail;在tablet下是list|detail双视图模式。

0119558ab5da4c9578eb2a47ba534c8d.png

本章要点:为资源创建别名

为不同尺寸的屏幕创建不同的布局

在res/values右键 > New > Values Resource File:

d65b249c453826cfd026168637f0aa2b.png

创建文件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:

996b7fbd8c797890f22f98195e128c4e.png

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上。

d71e9540ad211f136064581cb00abfff.png 有个问题需要回头再研究的: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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值