通过对 Activity 和 Fragment 的封装, 更加理解其生命周期, 一个 Activity 和 Fragment 的通用基本操作进行封装, 方便对其使用. 同时封装了 ButterKnife 注解框架, 方便我们的使用.
封装来自于 Mooc 课程学习 慕课网
BaseFragment
package com.dcr.italker.common.app;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import butterknife.ButterKnife;
import butterknife.Unbinder;
/**
* @author Dcr
*/
public abstract class BaseFragment extends Fragment {
protected View mRoot;
protected Unbinder mRootUnBinder;
@Override
public void onAttach(Context context) {
super.onAttach(context);
// 初始化参数
initArgs(getArguments());
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
int layoutId = getContentLayoutId();
if (mRoot == null) {
// 初始化当前的根布局, 但是不在创建时就添加到 container 里面
mRoot = inflater.inflate(layoutId, container, false);
// 如果第一次初始化, 创建 mRroot 并且初始化控件
initWidget(mRoot);
} else {
if (mRoot.getParent() != null) {
// 把当前 Root 从其父控件中移除
((ViewGroup) mRoot.getParent()).removeView(mRoot);
}
}
return mRoot;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 当 View 初始化完成后, 初始化数据
initData();
}
/**
* 得到当前界面的资源文件 id
*
* @return 资源文件 id
*/
protected abstract int getContentLayoutId();
/**
* 初始化控件
*/
protected void initWidget(View root) {
// 完成 ButterKnife 的绑定
mRootUnBinder = ButterKnife.bind(this, root);
}
/**
* 初始化数据
*/
protected void initData() {
}
/**
* 初始化参数
*
* @param args 参数
*/
protected void initArgs(Bundle args) {
}
/**
* 返回按键触发时调用
*
* @return true 代表已处理返回逻辑, Activity 就不用关心了, 否则需要上层 Activity 处理; 默认情况下不拦截.
*/
public boolean onBackPressed() {
return false;
}
}
BaseFragment
的基本封装如上所示, 下面进行每一部分的分析.
Fragment 生命周期
首先我们需要分析一下 Fragment 的生命周期, 详细的 Fragment 生命周期的讲解我会在另一篇文章中分析, 这里提供一篇 Fragment 生命周期的简略介绍, 非常简明但是使用, 可以作为手册查看 (当然, Fragment 的生命周期还是要熟记的)
其中我们这里封装用到的生命周期包括以下几个:
onAttach()
onCreateView()
onActivityCreated()
我们会在 onAttach()
中初始化参数, 在 onCreateView()
中 inflate View 并且初始化控件, 在 onViewCreated()
中初始化数据.
初始化参数
onAttach()
是 Fragment 经历的第一个生命周期, 我们在 onAttach()
中封装一个接收参数的操作.
@Override
public void onAttach(Context context) {
super.onAttach(context);
// 初始化参数
initArgs(getArguments());
}
/**
* 初始化参数
*
* @param args 参数
*/
protected void initArgs(Bundle args) {
}
我们通过 Fragment 所提供的 getArguments()
方法来获取参数, 所获取的参数是 Bundle
类型, 同时我们提供接口 initArgs()
来供子类来完成对参数的处理.
初始化控件
在 Fragment 的 onCreateView()
方法中, 我们给定 layout 资源文件, 来生成对应的实际 View 对象, 我们对其的封装如下所示.
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
int layoutId = getContentLayoutId();
if (mRoot == null) {
// 初始化当前的根布局, 但是不在创建时就添加到 container 里面
mRoot = inflater.inflate(layoutId, container, false);
// 如果第一次初始化, 创建 mRroot 并且初始化控件
initWidget(mRoot);
} else {
if (mRoot.getParent() != null) {
// 把当前 Root 从其父控件中移除
((ViewGroup) mRoot.getParent()).removeView(mRoot);
}
}
return mRoot;
}
getContentLayoutId()
我们在创建 View 时, 需要布局资源文件, 因此我们提供一个 getContentLayoutId()
的接口, 用于子类提供资源文件 Id.
这个方法子类必须实现, 因此声明为了抽象方法:
/**
* 得到当前界面的资源文件 id
*
* @return 资源文件 id
*/
protected abstract int getContentLayoutId();
onCreateView() 流程分析
在 onCreateView()
中, 我们首先通过 getContentLayoutId()
获取资源文件, 然后需要判断一下是否有 mRoot
缓存, 如果没有, 则通过 inflater 创建 view; 如果有, 则将当前的 root 从其父控件中移除 (这里其实不太懂, 还需要学习一个).
如果需要新建 view, 那么调用 initWidget()
方法完成初始化.
/**
* 初始化控件
*/
protected void initWidget(View root) {
// 完成 ButterKnife 的绑定
mRootUnBinder = ButterKnife.bind(this, root);
}
在 initWidget()
中, 完成了 ButterKnife 的绑定.
数据初始化
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 当 View 初始化完成后, 初始化数据
initData();
}
/**
* 初始化数据
*/
protected void initData() {
}
在 onViewCreated()
方法中, 调用 initData()
接口来进行数据初始化.
对返回按键的处理
/**
* 返回按键触发时调用
*
* @return true 代表已处理返回逻辑, Activity 就不用关心了, 否则需要上层 Activity 处理; 默认情况下不拦截.
*/
public boolean onBackPressed() {
return false;
}
在 BaseFragment
中, 添加了对返回按键的处理, Fragment 本身没有对返回按键的处理, 这里需要从 Activity 传入这个事件, 进而在 Fragment 中完成处理, 可以参见 BaseActivity 的相关部分.
BaseActivity
对 Activity 的封装也很简单, 也是按照生命周期进行封装. 完整的封装结果如下:
package com.dcr.italker.common.app;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import java.util.List;
/**
* @author Dcr
*/
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 在界面未初始化之前调用的初始化操作
initWindows();
if (initArgs(getIntent().getExtras())) {
// 得到界面Id并设置到Activity界面中
setContentView(getContentLayoutId());
initWidget();
initData();
} else {
// 初始化参数失败
finish();
}
}
/**
* 初始化窗口, 在界面未初始化之前调用的初始化操作
*/
protected void initWindows() {
}
/**
* 初始化参数
*
* @param args 参数
* @return 如果参数正确, 返回 True, 错误返回 False.
*/
protected boolean initArgs(Bundle args) {
return true;
}
/**
* 得到当前界面的资源文件 id
*
* @return 资源文件 id
*/
protected abstract int getContentLayoutId();
/**
* 初始化控件
*/
protected void initWidget() {
}
/**
* 初始化数据
*/
protected void initData() {
}
@Override
public boolean onSupportNavigateUp() {
// 当点击界面导航返回时, finish 当前界面
finish();
return super.onSupportNavigateUp();
}
@Override
public void onBackPressed() {
// 获取所有的 Fragments
List fragments = getSupportFragmentManager().getFragments();
// 判断 fragments 是否为空
if (fragments.size() > 0) {
// 遍历每个 Fragment
for (Fragment fragment :
fragments) {
// 是否是我们的 BaseFragment
if (fragment instanceof BaseFragment) {
if (((BaseFragment) fragment).onBackPressed()) {
// 如果 Fragment 拦截了 back 按键, 直接返回
return;
}
}
}
}
super.onBackPressed();
finish();
}
}
onCreate()
initWidget()
initWidget()
是在界面初始化之前进行的操作.
/**
* 初始化窗口, 在界面未初始化之前调用的初始化操作
*/
protected void initWindows() {
}
获取参数
传入 Activity 的参数, 我们需要进行封装处理, 传入参数可能会报错, 我们需要进行判断.
if (initArgs(getIntent().getExtras())) {
// 获取参数成功
// 得到界面Id并设置到Activity界面中
setContentView(getContentLayoutId());
initWidget();
initData();
} else {
// 初始化参数失败
finish();
}
在传入参数解析完成后, 我们进行界面的初始化.
setContentView()
Activity 需要调用 setContentView()
来设置布局资源, 因此我们提供一个接口 getConentLayoutId()
来获取资源文件, 并设置到 Activity 中.
/**
* 得到当前界面的资源文件 id
*
* @return 资源文件 id
*/
protected abstract int getContentLayoutId();
getContentLayoutId()
在子类种必须实现. 在 onCreate()
生命周期中, 我们会调用并且进行设置.
界面初始化和数据初始化
我们提供两个接口给子类使用, 分别进行界面的初始化和数据的初始化.
/**
* 初始化控件
*/
protected void initWidget() {
}
/**
* 初始化数据
*/
protected void initData() {
}
返回按钮的处理
Activity 需要处理两个返回事件, 一个是点击了界面导航的返回按钮, 一个是点击了实体的返回按钮.
@Override
public boolean onSupportNavigateUp() {
// 当点击界面导航返回时, finish 当前界面
finish();
return super.onSupportNavigateUp();
}
对界面导航的返回按钮, 我们只需要结束当前 Activity, 然后返回上一层即可.
对于实体的按钮, 因为 Activity 可能嵌套有 Fragment, 因此, 我们需要判断子 Fragment 中是否对返回事件进行了拦截, 如果进行了拦截, 我们就交由 Fragment 来处理, Activity 不做进一步处理, 否则也结束当前页面, 返回上一层.
@Override
public void onBackPressed() {
// 获取所有的 Fragments
List fragments = getSupportFragmentManager().getFragments();
// 判断 fragments 是否为空
if (fragments.size() > 0) {
// 遍历每个 Fragment
for (Fragment fragment :
fragments) {
// 是否是我们的 BaseFragment
if (fragment instanceof BaseFragment) {
if (((BaseFragment) fragment).onBackPressed()) {
// 如果 Fragment 拦截了 back 按键, 直接返回
return;
}
}
}
}
super.onBackPressed();
finish();
}
这里, 我们使用了 getSupportFragmentManager().getFragments()
接口的方式获取 Activity 的所有 Fragment, 然后对 Fragment 进行遍历, 判断是否对返回事件进行了拦截.
以上便是对 Activity 和 Fragment 的简单封装, 随着学习的进步, 还会对其封装进行修改完善, 会及时更新本文.