• 寻找Fragment的替代品的尝试


        自从Android在3.0推出Fragment之后,现在很多应用的设计都是建立在Fragment的基础上,像是多个tab切换这种需求,就可以使用Fragment,并且Fragment提供了一系列生命周期的回调,可以帮助我们实现很多特殊的需求,像是数据保存和恢复等。

        Fragment本身的出现是为了解决平板多屏界面展示问题,因为平板可以展示比手机更多的内容,所以使用Fragment可以实现根据不同尺寸展示不同内容的需求,而这不同内容更多是指在更大的尺寸显示更多的内容。

        随着人们的实际编码工作,发现使用Fragment可以更好的管理界面,因为一个Activity可以管理多个Fragment,如果将Fragment当做一个界面,我们可以实现多个界面的切换,并且这种工作比起以前在布局文件中控制可见来讲,更好管理,并且布局可以复用,导致Activity的作用就只是Fragment的管理容器而已,加上Fragment拥有和Activity同步的生命周期,所以很多业务工作都可以放在Fragment中。

        现在很多界面的开发工作都是使用Activity加多个Fragment的设计模式,这是很好的方式,但要想完全掌握Fragment这个利器,需要了解的工作非常多,并且有关Fragment可以开展相当多的话题,像是Fragment之间的参数传递,Fragment之间的切换和状态的保存,等等,这些都是相当大的范围,而且谷歌也看到Fragment的使用前景,封装了DialogFrament,ListFragment等方便开发者使用,Fragment和Activity之间生命周期的关系,还可以做监听Activity生命周期实现某些功能,像是结束的时候停止当前异步任务等需求。

        仔细看Fragment的代码,我们发现这无非就是在Activity的布局中指定的地方添加相应的布局,然后绑定一堆监听用以实现各种生命周期的回调。

        我们甚至可以模拟Android源码,自己搞一个Fragment的替代品。

        我们这次的尝试是实现Fragment界面复用的功能,这是最常用的场景。

        对应FragmentManager,我们用ViewHolderManager来管理View,对应Fragment,用PartViewHolder。

        为了方便我们替换View的时候能够更快的找到对应的View,需要一个HashMap,类似FragmentManager在查找Fragment一样,key值为我们指定的id,value则是对应的PartViewHolder。

        PartViewHolder只是一个抽象,它表示的是View的占位,更确切的说,是View的控制类,因此它具有一些共同的行为,实现上是一个抽象类。

        在Java中,定义一组抽象有两种:抽象类和接口,这两种都是多态的表现。抽象类表示的是"is a"的关系,而接口表示的是"has a"的关系。我们见过一些代码,类似Duck这类的名字,竟然只是一个接口,接口代表的应该是一组行为协议,类似flyable这样的接口名,如果抽象的名称是一个名词,可以考虑这是否是一个抽象类,如果是一个形容词或者动词,可以考虑这是否是一个接口。

        PartViewHolder的实现如下:

     1 public abstract class PartViewHolder {
     2 
     3    protected View rootView;
     4 
     5    protected LayoutInflater inflater;
     6 
     7    public PartViewHolder(LayoutInflater inflater) {
     8 
     9       this.inflater = inflater;
    10 
    11    }
    12 
    13    protected abstract View getRootView();
    14 
    15    protected abstract void bindViews();
    16 
    17    public abstract void resetView();
    18 
    19 }

         我们这里并没有传入Activity的Context,很多人在实现有关View的自定义时候,为了方便,都会传入Context,因为LayoutInflater需要通过Context来获取,但如果不是为了在布局中渲染,可以不用传Context的,这也是为了防止内存泄露的方法。

         传入的rootView是为了提供View要添加到的root上,而bindViews是为了初始化控件,resetView提供了View的清理。

         我们现在来实现一个PartViewHolder的子类。

     1 public class LoginViewHolder extends PartViewHolder {
     2     private LinearLayout llLogin;
     3     private LinearLayout llRegister;
     4     public LoginViewHolder(LayoutInflater inflater) {
     5         super(inflater);
     6         rootView = inflater.inflate(R.layout.view_login_register, null);
     7         rootView.setTag(false);
     8     }
     9     @Override
    10     protected View getRootView() {
    11         return rootView;
    12     }
    13     @Override
    14     protected void bindViews() {
    15         llLogin = (LinearLayout) rootView.findViewById(R.id.ll_login);
    16         llRegister = (LinearLayout) rootView.findViewById(R.id.ll_register);
    17         llLogin.setOnClickListener(new View.OnClickListener() {
    18             @Override
    19             public void onClick(View v) {
    20                ...
    21             }
    22         });
    23         llRegister.setOnClickListener(new View.OnClickListener() {
    24             @Override
    25             public void onClick(View v) {
    26                ...
    27             }
    28         });
    29     }
    30     @Override
    31     public void resetView() {
    32     }
    33 }

         上面实现的就是一个登录的页面。

         我们在构造器中指定rootView,然后通过getRootView返回rootView,以便上层业务能够对rootView进行操作,bindViews进行组件的初始化和事件的监听。

         PartViewHolder就是用来负责View的初始化和对应的操作。

         我们来看一个最重要的类:ViewHolderManager。

         ViewHolderManager的实现如下:

     1 public class ViewHolderManager {
     2     private static Map<String, PartViewHolder> viewMap;
     3     private static Map<String, PartInnerViewHolder> innerViewMap;
     4     public ViewHolderManager() {
     5         if(viewMap == null) {
     6             viewMap = new HashMap<>();
     7         }
     8         if(innerViewMap == null) {
     9             innerViewMap = new HashMap<>();
    10         }
    11     }
    12     public void add(String key, PartViewHolder viewHolder) {
    13         viewMap.put(key, viewHolder);
    14     }
    15     public void addInner(String innerKey, PartInnerViewHolder viewHolder) {
    16         innerViewMap.put(innerKey, viewHolder);
    17     }
    18     public void showView(ViewGroup viewGroup, String key) throws Exception {
    19         if (viewMap.size() == 0) {
    20             throw new Exception("You have not add any views");
    21         }
    22         if (viewGroup == null) {
    23             throw new Exception("not rootView");
    24         }
    25         if (viewGroup.getChildCount() > 0) {
    26             viewGroup.removeAllViews();
    27         }
    28         PartViewHolder viewHolder = viewMap.get(key);
    29         viewGroup.addView(viewHolder.getRootView());
    30         if (!(boolean) viewHolder.getRootView().getTag()) {
    31             viewHolder.bindViews();
    32         }
    33     }
    34     public void showInnerView(String innerKey) throws Exception {
    35         if (viewMap.size() == 0) {
    36             throw new Exception("You have not add any views");
    37         }
    38         PartInnerViewHolder viewHolder = innerViewMap.get(innerKey);
    39         ViewGroup viewGroup = (ViewGroup) viewHolder.getRootView();
    40         if (viewGroup.getChildCount() > 0) {
    41             viewGroup.removeAllViews();
    42         }
    43         viewGroup.addView(viewHolder.getInnerRootView());
    44         if (!(boolean) viewHolder.getInnerRootView().getTag()) {
    45             viewHolder.bindInnerViews();
    46         }
    47     }
    48     public void resetView(String key) {
    49         if (viewMap.containsKey(key)) {
    50             PartViewHolder viewHolder = viewMap.get(key);
    51             viewHolder.resetView();
    52         }
    53         if (innerViewMap.containsKey(key)) {
    54             PartInnerViewHolder viewHolder = innerViewMap.get(key);
    55             viewHolder.resetView();
    56         }
    57     }
    58 }

       ViewHolderManager负责维护一个HashMap,这个HashMap存放的是各种PartViewHolder。和FragmentManager类似,通过add添加对应的PartViewHolder,然后在调用showView的时候,通过id来指定要展示的PartViewHolder。

       我们的做法很简单,就是在指定的ViewGroup中添加rootView,来达到将需要的View展示在布局上的目的,在添加之前,要判断是否已经添加过了,如果有,就要remove掉。

       这里我们还增加了一个PartInnerViewHolder,这是对应Fragment本身也可以添加Fragment的情况。

     1 public abstract class PartInnerViewHolder{
     2     protected View rootView;
     3     protected View innerRootView;
     4     protected LayoutInflater inflater;
     5     public PartInnerViewHolder(LayoutInflater inflater, View rootView){
     6         this.inflater = inflater;
     7         this.rootView = rootView;
     8     }
     9     protected abstract View getInnerRootView();
    10     protected abstract void bindInnerViews();
    11     protected abstract View getRootView();
    12     protected abstract void resetView();
    13 }

       相比PartViewHolder,PartInnerViewHolder只不过是增加了一个innerRootView,用于指定该Fragment本身的布局。

       到了现在,一个Fragment的简单模仿品已经完成了,当然,我们为啥要自己造轮子呢?在使用嵌套Fragment的时候,遇到了很多问题,比较直观的就是APP放后台后,在内存不足的情况下,会导致Fragment重叠,还有其他各种情况,所以我们尝试了如何去模仿一个最简化的Fragment,只是利用它View复用这个特性而不涉及其他复杂的用法。

       

  • 相关阅读:
    DBG
    gdb Debugging Full Example
    Java Warmup
    Dtrace for Linux 2016
    分布式系统理论进阶
    Java theory and practice
    Dealing with InterruptedException
    JVM 虚拟化
    Intro to Filtering with Network Monitor 3.0
    spring 官方文档
  • 原文地址:https://www.cnblogs.com/wenjiang/p/6067562.html
Copyright © 2020-2023  润新知