> 本博客是对第一行代码的精简总结,仅供个人学习使用。如需系统学习请购买正版或者电子书籍。 |
|
> 链接附上 🔗图灵社区:https://www.ituring.com.cn/book/2744/ |
|
## 第 4 章 手机平板要兼顾——探究碎片 |
|
## 4.1 碎片是什么 |
|
碎片(Fragment)是一种可以嵌入在活动当中的UI片段,它能让程序更加合理和充分地利用大屏幕的空间,因而在平板上应用得非常广泛。虽然碎片对你来说应该是个全新的概念,但我相信你学习起来应该毫不费力,因为它和活动实在是太像了,同样都能包含布局,同样都有自己的生命周期。你甚至可以将碎片理解成一个迷你型的活动,虽然这个迷你型的活动有可能和普通的活动是一样大的。 |
|
## 4.2 碎片的简单用法 |
|
动态添加碎片主要分为5步 |
|
(1) 创建待添加的碎片实例。 |
|
(2) 获取FragmentManager,在活动中可以直接通过调用getSupportFragmentManager() 方法得到。 |
|
(3) 开启一个事务,通过调用beginTransaction() 方法开启。 |
|
(4) 向容器内添加或替换碎片,一般使用replace() 方法实现,需要传入容器的id和待添加的碎片实例。 |
|
(5) 提交事务,调用commit() 方法来完成。 |
|
```java |
public class MainActivity extends AppCompatActivity implements View.OnClickListener { |
|
@Override |
protected void onCreate(Bundle savedInstanceState) { |
super.onCreate(savedInstanceState); |
setContentView(R.layout.activity_main); |
Button button = (Button) findViewById(R.id.button); |
button.setOnClickListener(this); |
replaceFragment(new RightFragment()); |
} |
|
@Override |
public void onClick(View v) { |
switch (v.getId()) { |
case R.id.button: |
replaceFragment(new AnotherRightFragment()); |
break; |
default: |
break; |
} |
} |
|
private void replaceFragment(Fragment fragment) { |
FragmentManager fragmentManager = getSupportFragmentManager(); |
FragmentTransaction transaction = fragmentManager.beginTransaction(); |
transaction.replace(R.id.right_layout, fragment); |
transaction.commit(); |
} |
} |
|
|
``` |
|
## 4.3 在碎片中模拟返回栈 |
|
其实很简单,FragmentTransaction中提供了一个addToBackStack() 方法,可以用于将一个事务添加到返回栈中,修改MainActivity中的代码,如下所示: |
|
```java |
public class MainActivity extends AppCompatActivity implements View.OnClickListener { |
|
... |
|
private void replaceFragment(Fragment fragment) { |
FragmentManager fragmentManager = getSupportFragmentManager(); |
FragmentTransaction transaction = fragmentManager.beginTransaction(); |
transaction.replace(R.id.right_layout, fragment); |
transaction.addToBackStack(null); |
transaction.commit(); |
} |
|
} |
|
``` |
|
这里我们在事务提交之前调用了FragmentTransaction的addToBackStack() 方法,它可以接收一个名字用于描述返回栈的状态,一般传入null 即可。现在重新运行程序,并点击按钮将AnotherRightFragment添加到活动中,然后按下Back键,你会发现程序并没有退出,而是回到了RightFragment界面,继续按下Back键,RightFragment界面也会消失,再次按下Back键,程序才会退出。 |
|
## 4.4 碎片和活动之间进行通信 |
|
虽然碎片都是嵌入在活动中显示的,可是实际上它们的关系并没有那么亲密。你可以看出,碎片和活动都是各自存在于一个独立的类当中的,它们之间并没有那么明显的方式来直接进行通信。如果想要在活动中调用碎片里的方法,或者在碎片中调用活动里的方法,应该如何实现呢? |
|
为了方便碎片和活动之间进行通信,FragmentManager提供了一个类似于findViewById() 的方法,专门用于从布局文件中获取碎片的实例,代码如下所示: |
|
```java |
RightFragment rightFragment = (RightFragment) getSupportFragmentManager() |
.findFragmentById(R.id.right_fragment); |
``` |
|
调用FragmentManager的findFragmentById() 方法,可以在活动中得到相应碎片的实例,然后就能轻松地调用碎片里的方法了。 |
|
掌握了如何在活动中调用碎片里的方法,那在碎片中又该怎样调用活动里的方法呢?其实这就更简单了,在每个碎片中都可以通过调用getActivity() 方法来得到和当前碎片相关联的活动实例,代码如下所示: |
|
```java1 |
MainActivity activity = (MainActivity) getActivity(); |
|
``` |
|
|
|
有了活动实例之后,在碎片中调用活动里的方法就变得轻而易举了。另外当碎片中需要使用Context 对象时,也可以使用getActivity() 方法,因为获取到的活动本身就是一个Context 对象。 |
|
说实在的,这个问题并没有看上去那么复杂,它的基本思路非常简单,首先在一个碎片中可以得到与它相关联的活动,然后再通过这个活动去获取另外一个碎片的实例,这样也就实现了不同碎片之间的通信功能,因此这里我们的答案是肯定的。 |
|
## 4.5 碎片的生命周期 |
|
还记得每个活动在其生命周期内可能会有哪几种状态吗?没错,一共有运行状态、暂停状态、停止状态和销毁状态这4种。类似地,每个碎片在其生命周期内也可能会经历这几种状态,只不过在一些细小的地方会有部分区别。 |
|
1. 运行状态 |
|
当一个碎片是可见的,并且它所关联的活动正处于运行状态时,该碎片也处于运行状态。 |
|
2. 暂停状态 |
|
当一个活动进入暂停状态时(由于另一个未占满屏幕的活动被添加到了栈顶),与它相关联的可见碎片就会进入到暂停状态。 |
|
3. 停止状态 |
|
当一个活动进入停止状态时,与它相关联的碎片就会进入到停止状态,或者通过调用FragmentTransaction的remove() 、replace() 方法将碎片从活动中移除,但如果在事务提交之前调用addToBackStack() 方法,这时的碎片也会进入到停止状态。总的来说,进入停止状态的碎片对用户来说是完全不可见的,有可能会被系统回收。 |
|
4. 销毁状态 |
|
碎片总是依附于活动而存在的,因此当活动被销毁时,与它相关联的碎片就会进入到销毁状态。或者通过调用FragmentTransaction的remove() 、replace() 方法将碎片从活动中移除,但在事务提交之前并没有调用addToBackStack() 方法,这时的碎片也会进入到销毁状态。 |
|
活动中有的回调方法,碎片中几乎都有,不过碎片还提供了一些附加的回调方法,那我们就重点看一下这几个回调。 |
|
- onAttach() 。当碎片和活动建立关联的时候调用。 |
- onCreateView() 。为碎片创建视图(加载布局)时调用。 |
- onActivityCreated() 。确保与碎片相关联的活动一定已经创建完毕的时候调用。 |
- onDestroyView() 。当与碎片关联的视图被移除的时候调用。 |
- onDetach() 。当碎片和活动解除关联的时候调用。 |
|
|
|
## 4.6 使用限定符 |
|
平板电脑的屏幕足够大,完全可以同时显示下两页的内容,但手机的屏幕一次就只能显示一页的内容,因此两个页面需要分开显示。 |
|
那么怎样才能在运行时判断程序应该是使用双页模式还是单页模式呢?这就需要借助限定符(Qualifiers)来实现了 |
|
layout/activity_main布局只包含了一个碎片,即单页模式,而layout-large/ activity_main布局包含了两个碎片,即双页模式。其中large 就是一个限定符,那些屏幕被认为是large 的设备就会自动加载layout-large文件夹下的布局,而小屏幕的设备则还是会加载layout文件夹下的布局。 |
|
Android中一些常见的限定符可以参考下表。 |
|
|
|
|
|
|
|
### 4.6.1 使用最小宽度限定符 |
|
在上一小节中我们使用large 限定符成功解决了单页双页的判断问题,不过很快又有一个新的问题出现了,large 到底是指多大呢?有的时候我们希望可以更加灵活地为不同设备加载布局,不管它们是不是被系统认定为large ,这时就可以使用最小宽度限定符(Smallest-width Qualifier)了。 |
|
最小宽度限定符允许我们对屏幕的宽度指定一个最小值(以dp为单位),然后以这个最小值为临界点,屏幕宽度大于这个值的设备就加载一个布局,屏幕宽度小于这个值的设备就加载另一个布局。 |
|
在res目录下新建layout-sw600dp文件夹,然后在这个文件夹下新建activity_main.xml布局,代码如下所示: |
|
|
|
这就意味着,当程序运行在屏幕宽度大于等于600dp的设备上时,会加载layout-sw600dp/activity_main布局,当程序运行在屏幕宽度小于600dp的设备上时,则仍然加载默认的layout/activity_main布局。 |