浅谈Activity,Fragment模块化封装

在项目开发过程中,会有很多个模块,每个模块实现特定的几个相近功能,在这里我们可以使用一个activity实现一个模块,这个模块里几个相近的功能所对应的页面用几个fragment去处理。

继承关系

1
2
java.lang.Object
↳ android.support.v4.app.Fragment
1
2
3
4
5
java.lang.Object
↳ android.content.Context
↳ android.content.ContextWrapper
↳ android.view.ContextThemeWrapper
↳ android.app.Activity

这里的继承关系大家肯定十分熟悉了,这里贴出的是v4包中fragment,可以看出fragment是直接继承于object的,与四大组件没有任何关系。

场景分析

效果图如下:

这里写图片描述

如图示,用户登录注册模块有三个功能,分别为:登录,注册,修改密码(分别对应三个界面);其中登录为主界面,点击 快速注册 跳转到注册页面,从注册页面点击返回可以返回到登录主界面,在登录主界面点击 忘记密码 跳转到修改密码界面,同样点击返回可以返回到登录主界面。

代码分析

从上面分析可以看出这里只需要一个activity(对应模块)和 一至三个fragment(对应三个功能界面)

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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
* Created by zhulei on 16/5/27.
*/
public abstract class AppActivity extends AppCompatActivity{

//由于有些跳转无需参数,所以这里无需抽象方法
protected void handleIntent(Intent intent){
};
protected abstract int getContentViewId();
protected abstract BaseFragment getFirstFragment();
protected abstract int getFragmentContainerId();

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//写死竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
//处理Intent(主要用来获取其中携带的参数)
if (getIntent() != null){
handleIntent(getIntent());
}
//设置contentView
setContentView(getContentViewId());
//添加栈底的第一个fragment
if (getSupportFragmentManager().getBackStackEntryCount() == 0){
if (getFirstFragment() != null){
pushFragment(getFirstFragment());
}else {
throw new NullPointerException("getFirstFragment() cannot be null");
}
}
}

public void pushFragment(BaseFragment fragment){
if (fragment != null){
getSupportFragmentManager().beginTransaction()
.replace(getFragmentContainerId(), fragment)
.addToBackStack(((Object)fragment).getClass().getSimpleName())
.commitAllowingStateLoss();
}
}

public void popFragment(){
if (getSupportFragmentManager().getBackStackEntryCount() > 1){
getSupportFragmentManager().popBackStack();
}else {
finish();
}
}

@Override
public boolean onSupportNavigateUp() {
if (getSupportFragmentManager().getBackStackEntryCount() > 1){
getSupportFragmentManager().popBackStack();
return true;
}
return super.onSupportNavigateUp();
}

@Nullable
@Override
public Intent getSupportParentActivityIntent() {
Intent intent = super.getSupportParentActivityIntent();
if (intent == null){
finish();
}
return intent;
}

//回退键处理
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (KeyEvent.KEYCODE_BACK == keyCode){
if (getSupportFragmentManager().getBackStackEntryCount() == 1){
finish();
return true;
}
}
return super.onKeyDown(keyCode, event);
}
}

这里抽象了五件事情:

  • 设置contentview前处理携带到这个activity中的参数(handleintent());
  • 设置contentview,这里讲contentview的布局id抽象出去交由每个具体类实现(getcontentviewid());
  • pushfragment,往activity中添加fragment,这里需要外部实现getfirstfragment(),需要在初始化时候添加第一个fragment;
  • popfragment,从回退栈中抛出fragment;
  • 处理回退事件,这里我使用了actionbar,结合popfragment处理了这两个回调:onSupportNavigateUp()和getSupportParentActivityIntent();

再看下base的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Created by zhulei on 16/5/27.
*/
public abstract class BaseActivity extends AppActivity {
@Override
protected int getContentViewId() {
return R.layout.activity_base;
}

@Override
protected int getFragmentContainerId() {
return R.id.fragment_container;
}
}

这里只是实现一个基础布局

最后看到这个模块的activity所对应的代码:

1
2
3
4
5
6
7
8
9
10
11
/**
* Created by zhulei on 16/5/27.
*/
public class LoginActivity extends BaseActivity {

@Override
protected BaseFragment getFirstFragment() {
return new LoginFragment();
}

}

这里可以看出非常简单,因为基础工作都在底下做完了,这里只负责处理要添加的主界面的fragment

fragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* Created by zhulei on 16/5/27.
*/
public abstract class AppFragment extends Fragment {

protected abstract int getLayoutId();

protected abstract void initView(View view, Bundle savedInstanceState);
protected void releaseView(){

};
protected abstract void initActionBar(ActionBar actionBar);

protected BaseActivity getHoldingActivity(){
if (getActivity() instanceof BaseActivity){
return (BaseActivity)getActivity();
}else {
throw new ClassCastException("activity must extends BaseActivity");
}
}

protected void pushFragment(BaseFragment fragment){
getHoldingActivity().pushFragment(fragment);
}

protected void popFragment(BaseFragment fragment){
getHoldingActivity().popFragment();
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(getLayoutId(), container, false);
initView(view, savedInstanceState);
return view;
}

@Override
public void onStart() {
super.onStart();
ActionBar actionBar = getHoldingActivity().getSupportActionBar();
if (actionBar != null){
initActionBar(actionBar);
}
}

@Override
public void onDestroyView() {
super.onDestroyView();
releaseView();
}

}

这里抽象了六件事情:

  • 在oncreateview之前提供了initview接口交由外部去初始化内部的所有控件;
  • oncreareview,这里需要一个view的布局id,通过getLayoutId()获取;
  • 我这里使用的是actionbar,所以这里公布了一个initactionbar的接口交由外部去设置actionbar上ui交互;
  • pushfragment,由于大部分的交互都在fragment 内部,所以这里公布了这个接口
  • popfragment,同4;
  • releaseview,在onDestroyView时候交由外部处理(如需);

再看下basefragment:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Created by zhulei on 16/5/27.
*/
public abstract class BaseFragment extends AppFragment {

@Override
protected void initView(View view, Bundle savedInstanceState) {
ButterKnife.bind(this, view);
}

@Override
protected void initActionBar(ActionBar actionBar) {
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
}
}

由于我使用了注解框架butterknife,在这里直接注入,以及actionbar的ui交互基本操作

然后看下三个功能所对应的两个fragment(登录:loginfragment,注册和修改密码:verifyfragment)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* Created by zhulei on 16/5/27.
*
* 登陆主界面
*/
public class LoginFragment extends BaseFragment {

@BindView(R.id.check_visible)
CheckBox mCheckVisible;
@BindView(R.id.user_name)
TextInputEditText mUserName;
@BindView(R.id.user_password)
TextInputEditText mUserPsw;

@OnClick(R.id.login_btn)
public void onLoginBtnClicked(){

}
@OnClick(R.id.register_btn)
public void onRegisterBtnClicked(){
pushFragment(VerifyFragment.newInstance(VerifyFragment.REGISTER));
}
@OnClick(R.id.forget_button)
public void onForgetBtnClicked(){
pushFragment(VerifyFragment.newInstance(VerifyFragment.RESET));
}

@Override
protected int getLayoutId() {
return R.layout.fragment_login;
}

@Override
protected void initActionBar(ActionBar actionBar) {
super.initActionBar(actionBar);
actionBar.setTitle(R.string.login);
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* Created by zhulei on 16/5/27.
*
* 注册.修改密码界面
*/
public class VerifyFragment extends BaseFragment {

public static final String ARG_VERIFY = "verify";

public static final int REGISTER = 0;
public static final int RESET = 1;

private int mAction;

@Override
protected int getLayoutId() {
return R.layout.fragment_verify;
}

@Override
protected void initActionBar(ActionBar actionBar) {
super.initActionBar(actionBar);
if (mAction == RESET){
actionBar.setTitle(R.string.reset_psw);
}else {
actionBar.setTitle(R.string.register);
}
}

public static VerifyFragment newInstance(int action){
VerifyFragment fragment = new VerifyFragment();
Bundle arg = new Bundle();
arg.putInt(ARG_VERIFY, action);
fragment.setArguments(arg);
return fragment;
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null){
mAction = getArguments().getInt(ARG_VERIFY);
}
}

}

这里由于注册和修改密码界面ui一样,只有文字显示区别,可以通过传标记位的方式来处理ui显示逻辑;

总结

这篇博文代码比较多,但是对于一个项目来说这样的一个搭建方式对后续模块的增加会起到积极的影响,base里面完全可以添加一些其它需要初始化的公共逻辑(比如设置actionbar的颜色)。
如有不足之处,还请大家多多指正。

具体代码请看我的github: Android练习小项目