一、前期基础知识储备
Fragment的难点主要在于堆回退栈和生命周期,官网的生命周期图示很少有人能完全理解。其实所谓的Fragment就是一个视图管理器,是Activity的一个辅助Controller(MVC架构中的Controller,和Activity地位一致)。
Fragment中的有些问题是可以规避的,有些问题则是难以解决的。如果不想陷入被Bug包围的境地,请不要随便让Fragment入栈。以下为使用Fragment时常见的问题。
1)Activity为空
在Fragment中常常需要用到content对象,有时候网速很慢,当请求返回时Activity已经被finish了,此时,再调用getActivity()就是null。原因在于调用getActivity()时,Fragment已经执行了onDetach(),并已经脱离了宿主Activiy。
2)startActivityForResult
Fragmet的startActivityForResult()实际上调用了Activity的相应方法,所以并不需要用getActivity.startActivityForResult()启动Activity,只要不特意去掉Activity中的onActivityResult的super调用就好。
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Fragement内部中不要忘记调用super
}
需要说明的是,咋Fragemnt嵌套Fragment的情况下,这种写法可能会收不到回调,具体需要注意一下support包中Bug修复情况。此外Fragment可以接收返回结果,但是自身无法产生返回结果,也就是说Fragment不支持fragment.setResult()。
3)ViewPager的getItem
当我们想获得viewPager当前展示的Fragment时,有人可能会通过getItem()获取,这种做法是错误的,千万不要再外部调用adapter的getItem()。这个方法会返回一个新建的Fragment,并不是当前展示的Fragment。如果想要获得当前的Fragment,必须手动在外部维持一个Fragment数组,然后通过index来获取。
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
// ...
array.remove(position);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
// ...
array.put(position, layout);
}
建立Fragment数组的方式非常简单,直接在adapter中建立一个spaseArray即可。这个数组在adapter的下列方法中进行put和remove,查找当前的Fragment时把Viewpager的index作为Key传入即可。
4)FragmentpagerAdapter
ViewPager中的Fragment没有recyclerView那样的复用机制,开销较大。源码中的个体ItemPosition()默认返回POSITION_UNCHANGED,这表示当前的Fragment不会进行更新。如果进行复写,使其返回POSITION_NONE,则表示当前Fragment需要调用destroyItem()和instantiateItem()来销毁和重建。
5)显示对话框
DialogFragment的显示操作也是一个事务。如果点击一次按钮就调用一次show()方法,在连续,快速点击时,会导致同一时刻多次触发show操作,进而引起崩溃。为了规避此问题,可以对按钮做点击事件的排重处理,更简单点的做法是对Fragment的状态进行判断:
if (mDialogFragment == null){
mDialogFragement = SampleDialogFragment.newInstance();
}
Dialog dialog = SampleDialogFragment.getDialog();
if (mDialog == null || !mDialog.isShowing()) {
mDialogFragement.show(getSupportFragmentManager(), FRAGMENT_TAG_SAMPLE);
}
6)重叠显示的问题
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
final MyFragment fragment = new MyFragment();
Bundle bundle = new Bundle();
bundle.putInt("age", 24);
fragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.add(R.id.container_layout, fragment, "fg")
.commit();
}
运行上面的常规代码,每旋转一次Activity,就会执行一次Fragment的创建和add操作,而之前的Fragment本身也会被自动创建,这就会出现一个界面上有两个Fragment的情况,也就是UI重叠的问题。如果Fragment的容器是linearLayout,则问题会十分明显。解决方案:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
MyFragment fragment;
if (savedInstanceState == null){
fragment = new MyFragment();
ft.add(R.id.fl_fragment, fragment, "fg");
} else {
fragment = (MyFragment) fm.findFragmentByTag("fg");
}
}
利用savedInstanceState进行判断,如果Fragment是null才加载。
二、上代码,具体使用Shatter库
库地址:https://github.com/tianzhijiexian/Shatter
库描述:代替fragment的轻量级解耦类,拥有和activity完全一致的生命周期。
Shatter是一个代替fragment来划分ui模块的库。它主要完成的工作是管理ui区块,并且能和activity保持完全相同的生命周期,没有任何学习成本。
Shatter对于单页面多ui模块的结构有着很好的支持,非常适合用来降低复杂activity的复杂度。但因为设计的关系,它的生命周期仅仅被activity触发的,所以不会有完整的生命周期的概念。
所有的监听工作都是通过shatterManager来实现的,这个类将会把activity的方法对应给shatter:
1) 引入方式
添加JitPack仓库
repositories {
maven {
url "https://jitpack.io"
}
}
添加依赖
implementation 'com.github.tianzhijiexian:Shatter:1.0.8
2)配置方式
让shatter有监听activity全部生命周期的能力
在app的build.gradle中配置aspectj:
apply plugin: 'com.android.application'
apply plugin: 'me.leolin.gradle-android-aspectj'
接着在baseActivity实现IShatterActivity接口
,并复写你需要被shatter感知的生命周期(无需做任何处理,只需复写即可),如:
public class BaseActivity extends AppCompatActivity implements IShatterActivity {
private ShatterManager mShatterManager;
public ShatterManager getShatterManager() {
if (mShatterManager == null) {
mShatterManager = new ShatterManager(this);
}
return mShatterManager;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
@Override
public void onStart() {
super.onStart();
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onPause() {
super.onPause();
}
@Override
public void onStop() {
super.onStop();
}
@Override
public void onRestart() {
super.onRestart();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
}
3)使用方式
定义一个shatter:
public class BottomShatter extends Shatter {
public static final String TAG = "BottomShatter";
private Callback callback;
public BottomShatter(Callback callback){
this.callback = callback;
}
private Button mBottomBtn;
@Override
public String getTag() {
return TAG;
}
@Override
protected int getLayoutResId() {
return R.layout.bottom_shatter;
}
@Override
public void bindViews(View rootView) {
mBottomBtn = findViewById(R.id.bottom_btn);
}
@Override
public void setViews() {
mBottomBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("name", "kale");
intent.setClass(getActivity(), ViewPagerActivity.class);
startActivityForResult(intent, 123);
callback.onClick();
}
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Fragement内部中不要忘记调用super
}
}
当这个类建立好后,通过bindView()方法中进行view的绑定操作,然后调用setViews()来配置view(比如点击方式,接口调用方法),可以实现一个UI模块的基础功能。
方式一:在activity中添加这个shatter
public class MainActivity extends BaseActivity implements Callback {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LeakCanary.install(getApplication());
setContentView(R.layout.main_activity);
/*
* 其實 使用Shatter代替Fragment 用法類似
* ①Activity內部進行Layout的填充,引入Shatter
* ②Shatter構造也是引入布局,找到控件,寫入點擊事件
* */
getShatterManager()
.add(R.id.root_view, new LifeShatter()) // main_activity根View
.add(R.id.top_ll, new TopShatter()) // main_activity顶部View
.add(R.id.middle_fl, new MiddleShatter()) // main_activity中部View
.add(R.id.bottom_fl, new BottomShatter(this)); // main_activity底部View
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume: ");
}
@Override
public void onClick() {
Toast.makeText(this, "Callback", Toast.LENGTH_SHORT).show();
}
}
通过Manager挂载到Activity上,这时Shatter承载的UI就可以被显示了,和Fragment十分类似。
然后可以通过定义外部接口的方式,完成Activity和Fragment之间的交互。
public interface Callback {
void onClick();
}
方式二:在shatter中添加一个shatter(支持多重嵌套):
public class MiddleShatter extends Shatter {
@Override
protected int getLayoutResId() {
return R.layout.middle_shatter;
}
@Override
public void bindViews(View rootView) {
getShatterManager().add(R.id.inner_fl, new InnerShatter());
}
@Override
public void setViews() {
}
public static class InnerShatter extends Shatter {
@Override
protected int getLayoutResId() {
return android.R.layout.simple_list_item_1;
}
@Override
public void bindViews(View rootView) {
}
@Override
public void setViews() {
View root = getRootView();
root.setBackgroundResource(R.drawable.shatter_green_bg);
TextView textView = findViewById(android.R.id.text1);
textView.setGravity(Gravity.CENTER);
textView.setText(R.string.test_text);
}
}
}
方式三:和Viewpager联合使用
Fragment在ViewPager中是没有任何缓存的,如果有数据量很大,那么内存的开销会很大。Shatter库提供了一个shatterPagerAdapter对象,这个对象继承自recyclerPagerAdapter,因此它也就有了缓存的功能。
创建adapter实例:
final String[] data = new String[]{"英国", "法国", "爱尔兰", "荷兰", "比利时",
"卢森堡", "摩纳哥", "泽西", "根西", "马恩岛",
"中欧", "波兰", "瑞士", "列支敦士登", "奥地利", "匈牙利",
"捷克", "斯洛伐克", "斯洛文尼亚 ", "德国"};
final ShatterPagerAdapter adapter = new ShatterPagerAdapter(getShatterManager()) {
@Override
public int getCount() {
return data.length;
}
@Override
public Object getItemType(int position) {
return "Single Type"; // 只有一种类型的Item
}
@NonNull
@Override
public Shatter createItem(Object type) {
return new PagerShatter();
}
/**
* 预加载 這裡可以拿到關鍵的position參數
* 可直接利position參數做設置
* 也可再結合集合或者數組做相應設置
*/
@Override
protected void afterInstantiateItem(Shatter item, int position) {
super.afterInstantiateItem(item, position);
PagerShatter shatter = (PagerShatter) item;
shatter.handleData(data[position]);
}
/**
* 懒加载
*/
@Override
protected void setVisibleToUser(Shatter shatter, int pos) {
super.setVisibleToUser(shatter, pos);
/*if (shatter.isVisibleToUser()) { // 当Shatter可见时才加载数据
((PagerShatter) shatter).handleData(data[pos]);
}*/
}
@Override
public void finishUpdate(ViewGroup container) {
super.finishUpdate(container);
}
};
当ViewPager调用instantiateItem()时,无论新建的Shatter还是从缓存中拿到的Shatter都会传递给afterInstantiateItem(),在这里可以做一些预加载的事:在这个方法中我们获取当前的位置,结合数据List可以让Shatter进行预加载,十分方便。
初始化Viewpgaer, 使用新建的Adapter:
ViewPager viewPager = findViewById(R.id.viewpager);
viewPager.setOffscreenPageLimit(2); // 设置预加载的页面个数(设置缓存,最少为1)
viewPager.setAdapter(adapter);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
Log.d(TAG, "onPageSelected: " + adapter.getCurrentItem() );
Log.d(TAG, "onPageSelected: " + "cache size: " + adapter.getCache().size());
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
adapter.notifyDataSetChanged();
}
展示结果:
总结:在日常开发过程中,很多项目的Activity代码都很混乱,但是真正用Fragment解耦Activity的项目却很少,都是必须用Fragment时才会使用它,没有真正发挥其解耦的威力,当真正将Activity用Fragment或Shatter解耦之后,代码会变得非常清晰,甚至根本用不到MVP这种复杂的架构。
最后建议开发者们针对当前的项目,多思考设计是否合理,在该用Fragment时大胆使用,不要畏首畏尾,等Activity变得很庞大时,想解耦就困难了。