Shatter — Fragment的替代选择方案

一、前期基础知识储备

        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变得很庞大时,想解耦就困难了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值