GithubHelp home page GithubHelp logo

li2 / update-and-replace-fragment-in-viewpager Goto Github PK

View Code? Open in Web Editor NEW
100.0 7.0 23.0 17.68 MB

Android: Show how to update and replace Fragment in ViewPager

Java 100.00%
viewpager-fragment android-fragments

update-and-replace-fragment-in-viewpager's Introduction

How to update and replace fragment in viewpager?

ListView的工作原理

在了解ViewPager的工作原理之前,先回顾ListView的工作原理:

  1. ListView只有在需要显示某些列表项时,它才会去申请可用的视图对象;如果为所有的列表项数据创建视图对象,会浪费内存;
  2. ListView找谁去申请视图对象呢? 答案是adapter。adapter是一个控制器对象,负责从模型层获取数据,创建并填充必要的视图对象,将准备好的视图对象返回给ListView
  3. 首先,通过调用adapter的getCount()方法,ListView询问数组列表中包含多少个对象(为避免出现数组越界的错误);紧接着ListView就调用adapter的getView(int, View, ViewGroup)方法。

ViewPager某种程度上类似于ListView,区别在于:ListView通过ArrayAdapter.getView(int position, View convertView, ViewGroup parent)填充视图;ViewPager通过FragmentPagerAdapter.getItem(int position)生成指定位置的fragment.

而我们需要关注的是:

ViewPager和它的adapter是如何配合工作的?

声明:本文内容针对android.support.v4.app.* ViewPager有两个adapter:FragmentPagerAdapter和FragmentStatePagerAdapter:

继承自android.support.v4.view.PagerAdapter,每页都是一个Fragment,并且所有的Fragment实例一直保存在Fragment manager中。所以它适用于少量固定的fragment,比如一组用于分页显示的标签。除了当Fragment不可见时,它的视图层(view hierarchy)有可能被销毁外,每页的Fragment都会被保存在内存中。(翻译自代码文件的注释部分)

继承自android.support.v4.view.PagerAdapter,每页都是一个Fragment,当Fragment不被需要时(比如不可见),整个Fragment都会被销毁,除了saved state被保存外(保存下来的bundle用于恢复Fragment实例)。所以它适用于很多页的情况。(翻译自代码文件的注释部分)

它俩的子类,需要实现getItem(int)android.support.v4.view.PagerAdapter.getCount().

先通过一段代码了解ViewPager和FragmentPagerAdapter的典型用法

稍后做详细分析:

  // Set a PagerAdapter to supply views for this pager.
  ViewPager viewPager = (ViewPager) findViewById(R.id.my_viewpager_id);
  viewPager.setAdapter(mMyFragmentPagerAdapter);
 
  private FragmentPagerAdapter mMyFragmentPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
    @Override
    public int getCount() {
      return 2; // Return the number of views available.
    }
 
    @Override
    public Fragment getItem(int position) {
      return new MyFragment(); // Return the Fragment associated with a specified position.
    }
 
    // Called when the host view is attempting to determine if an item's position has changed.
    @Override
    public int getItemPosition(Object object) {
      if (object instanceof MyFragment) {
        ((MyFragment)object).updateView();
      }
      return super.getItemPosition(object);
    }
  };
 
  private class MyFragment extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      // do something such as init data
    }
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
      View view = inflater.inflate(R.layout.fragment_my, container, false);
      // init view in the fragment
      return view;
    }
 
    public void updateView() {
      // do something to update the fragment
    }
  }

FragmentPagerAdapter和FragmentStatePagerAdapter对Fragment的管理略有不同,在详细考察二者区别之前,我们通过两种较为直观的方式先感受下:

通过两张图片直观的对比FragmentPagerAdapter和FragmentStatePagerAdapter的区别

说明:这两张图片来自于《Android权威编程指南》,原图有3个Fragment,我增加了1个Fragment,以及被调到的方法。 FragmentPagerAdapter的Fragment管理:

image-11-4-FragmentPagerAdapter的fragment管理-方法调用

FragmentStatePageAdapter的Fragment管理: image-11-3FragmentStatePagerAdapter的fragment管理-方法调用

详细分析 adapter method和fragment lifecycle method 的调用情况

好啦,感受完毕,我们需要探究其详情,梳理adapter创建、销毁Fragment的过程,过程中adapter method和fragment lifecycle method哪些被调到,有哪些一样,有哪些不一样。

最开始处于第0页时,adapter不仅为第0页创建Fragment实例,还为相邻的第1页创建了Fragment实例:

// 刚开始处在page0
D/Adapter (25946): getItem(0)
D/Fragment0(25946): newInstance(2015-09-10)  // 注释:newInstance()调用了Fragment的构造器方法,下同。
D/Adapter (25946): getItem(1)
D/Fragment1(25946): newInstance(Hello World, I'm li2.)
D/Fragment0(25946): onAttach()
D/Fragment0(25946): onCreate()
D/Fragment0(25946): onCreateView()
D/Fragment1(25946): onAttach()
D/Fragment1(25946): onCreate()
D/Fragment1(25946): onCreateView()

第1次从第0页滑到第1页,adapter同样会为相邻的第2页创建Fragment实例;

// 第1次滑到page1
D/Adapter (25946): onPageSelected(1)
D/Adapter (25946): getItem(2)
D/Fragment2(25946): newInstance(true)
D/Fragment2(25946): onAttach()
D/Fragment2(25946): onCreate()
D/Fragment2(25946): onCreateView()

FragmentPagerAdapter和FragmentStatePagerAdapter齐声说:呐,请主公贰放心,属下定会为您准备好相邻的下一页视图哒!么么哒! 它俩对待下一页的态度是相同的,但对于上上页,它俩做出了不一样的事情: FragmentPagerAdapter说:上上页的实例还保留着,只是销毁了它的视图

// 第N次(N不等于1)向右滑动选中page2
D/Adapter (25946): onPageSelected(2)
D/Adapter (25946): destroyItem(0)  // 销毁page0的视图
D/Fragment0(25946): onDestroyView()
D/Fragment3(25946): onCreateView()  // page3的Fragment实例仍保存在FragmentManager中,所以只需创建它的视图

FragmentStatePagerAdapter说:上上页的实例和视图都被俺销毁啦

// 第N次(N不等于1)向右滑选中page2
D/Adapter (27880): onPageSelected(2)
D/Adapter (27880): destroyItem(0)  // 销毁page0的实例和视图
D/Adapter (27880): getItem(3)  // 创建page3的Fragment
D/Fragment3(27880): newInstance()
D/Fragment0(27880): onDestroyView()
D/Fragment0(27880): onDestroy()
D/Fragment0(27880): onDetach()
D/Fragment3(27880): onAttach()
D/Fragment3(27880): onCreate()
D/Fragment3(27880): onCreateView()

Fragment getItem(int position)

// Return the Fragment associated with a specified position.
public abstract Fragment getItem(int position);

当adapter需要一个指定位置的Fragment,并且这个Fragment不存在时,getItem就被调到,返回一个Fragment实例给adapter。 所以,有必要再次强调,getItem是创建一个新的Fragment,但是这个方法名可能会被误认为是返回一个已经存在的Fragment。 对于FragmentPagerAdapter,当每页的Fragment被创建后,这个函数就不会被调到了。对于FragmentStatePagerAdapter,由于Fragment会被销毁,所以它仍会被调到。 由于我们必须在getItem中实例化一个Fragment,所以当getItem()被调用后,Fragment相应的生命周期函数也就被调到了:

D/Adapter (25946): getItem(1)
D/Fragment1(25946): newInstance(Hello World, I'm li2.)  // newInstance()调用了Fragment的构造器方法;
D/Fragment1(25946): onAttach()
D/Fragment1(25946): onCreate()
D/Fragment1(25946): onCreateView()

void destroyItem(ViewGroup container, int position, Object object)

// Remove a page for the given position. 
public void FragmentPagerAdapter.destroyItem(ViewGroup container, int position, Object object) {
    mCurTransaction.detach((Fragment)object);
}

public void FragmentStatePagerAdapter.destroyItem(ViewGroup container, int position, Object object) {
    mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
    mFragments.set(position, null);
    mCurTransaction.remove(fragment);
}

销毁指定位置的Fragment。从源码中可以看出二者的区别,一个detach,一个remove,这将调用到不同的Fragment生命周期函数:

// 对于FragmentPagerAdapter
D/Adapter (25946): onPageSelected(2)
D/Adapter (25946): destroyItem(0)
D/Fragment0(25946): onDestroyView()  // 销毁视图

// 对于FragmentStatePagerAdapter
D/Adapter (27880): onPageSelected(2)
D/Adapter (27880): destroyItem(0)
D/Fragment0(27880): onDestroyView()  // 销毁视图
D/Fragment0(27880): onDestroy()  // 销毁实例
D/Fragment0(27880): onDetach()

FragmentPagerAdapter和FragmentStatePagerAdapter对比总结

二者使用方法基本相同,唯一的区别就在卸载不再需要的fragment时,采用的处理方式不同:

  • 使用FragmentStatePagerAdapter会销毁掉不需要的fragment。事务提交后,可将fragment从activity的FragmentManager中彻底移除。类名中的“state”表明:在销毁fragment时,它会将其onSaveInstanceState(Bundle) 方法中的Bundle信息保存下来。用户切换回原来的页面后,保存的实例状态可用于恢复生成新的fragment.
  • FragmentPagerAdapter的做法大不相同。对于不再需要的fragment,FragmentPagerAdapter则选择调用事务的detach(Fragment) 方法,而非remove(Fragment)方法来处理它。也就是说,FragmentPagerAdapter只是销毁了fragment的视图,但仍将fragment实例保留在FragmentManager中。因此, FragmentPagerAdapter创建的fragment永远不会被销毁。

(摘抄自《Android权威编程指南11.1.4》)

更新ViewPager中的Fragment

调用notifyDataSetChanged()时,2个adapter的方法的调用情况相同,当前页和相邻的两页的getItemPosition都会被调用到

// Called when the host view is attempting to determine if an item's position has changed. Returns POSITION_UNCHANGED if the position of the given item has not changed or POSITION_NONE if the item is no longer present in the adapter.
public int getItemPosition(Object object) {
    return POSITION_UNCHANGED;
}

从网上找到的解决办法是,覆写getItemPosition使其返POSITION_NONE,以触发Fragment的销毁和重建。可是这将导致Fragment频繁的销毁和重建,并不是最佳的方法。 后来我把注意力放在了入口参数object上,"representing an item", 实际上就是Fragment,只需要为Fragment提供一个更新view的public方法:

@Override
// To update fragment in ViewPager, we should override getItemPosition() method,
// in this method, we call the fragment's public updating method.
public int getItemPosition(Object object) {
    Log.d(TAG, "getItemPosition(" + object.getClass().getSimpleName() + ")");
    if (object instanceof Page0Fragment) {
        ((Page0Fragment) object).updateDate(mDate);
    } else if (object instanceof Page1Fragment) {
        ((Page1Fragment) object).updateContent(mContent);
    } else if (object instanceof Page2Fragment) {
        ((Page2Fragment) object).updateCheckedStatus(mChecked);
    } else if (...) {
    }
    return super.getItemPosition(object);
};

// 更新界面时方法的调用情况
// 当前页为0时
D/Adapter (21517): notifyDataSetChanged(+0)
D/Adapter (21517): getItemPosition(Page0Fragment)
D/Fragment0(21517): updateDate(2015-09-12)
D/Adapter (21517): getItemPosition(Page1Fragment)
D/Fragment1(21517): updateContent(Hello World, I am li2.)

// 当前页为1时
D/Adapter (21517): notifyDataSetChanged(+1)
D/Adapter (21517): getItemPosition(Page0Fragment)
D/Fragment0(21517): updateDate(2015-09-13)
D/Adapter (21517): getItemPosition(Page1Fragment)
D/Fragment1(21517): updateContent(Hello World, I am li2.)
D/Adapter (21517): getItemPosition(Page2Fragment)
D/Fragment2(21517): updateCheckedStatus(true)

在最开始调用notifyDataSetChanged试图更新Fragment时,我是这样做的:用arraylist保存所有的Fragment,当需要更新时,就从arraylist中取出Fragment,然后调用该Fragment的update方法。这种做法非常鱼唇,当时完全不懂得adapter的Fragment manager在替我管理所有的Fragment。而我只需要:

  • 覆写getCount告诉adapter有几个Fragment;
  • 覆写getItem以实例化一个指定位置的Fragment返回给adapter;
  • 覆写getItemPosition,把入口参数强制转型成自定义的Fragment,然后调用该Fragment的update方法以完成更新。

只需要覆写这几个adapter的方法,adapter会为你完成所有的管理工作,不需要自己保存、维护Fragment

替换ViewPager中的Fragment

应用场景可能是这样,比如有一组按钮,Day/Month/Year,有一个包含几个Fragment的ViewPager。点击不同的按钮,需要秀出不同的Fragment。 具体怎么实现,请参考下面的代码: github.com/li2/Update_Replace_Fragment_In_ViewPager/ContainerFragment.java

一些误区

ViewPager.getChildCount() 返回的是当前ViewPager所管理的没有被销毁视图的Fragment,并不是所有的Fragment。想要获取所有的Fragment数量,应该调用ViewPager.getAdapter().getCount().

一个Demo

为了总结ViewPager的用法,以及写这篇笔记,我写了一个demo,你可以从这里获取它的源码 github.com/li2/

这一张gif图片,演示了一个包含4个Fragment的ViewPager,通过上面的date+-1 button、EditText、Checkbox来更新前3个Fragment的界面;最后一个Fragment嵌套着2个Fragment,通过ToggleButton来切换。

image-update_fragment_in_viewpager_demo

这一张gif演示了切换ViewPager页以及更新Fragment时,相关的方法调用。通过一个ScrollView和TextView展示出来。

image-update_fragment_in_viewpager_withlog

参考

关于作者

Copyright (C) 2015 WeiYi Li (li21) [email protected] li2.me 特别声明:禁止转载

update-and-replace-fragment-in-viewpager's People

Contributors

li2 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.