重拾Android之路之ViewPager


引言

项目实施过程中,最常见的莫过于ViewPager(多页面滑动控件)。


基本概念叙述

FragmentPagerAdapter

对于Fragment,它所使用的适配器是:FragmentPagerAdapter。先看看官方对于这个类的解释:

原文:

Class Overview


Implementation of PagerAdapter that represents each page as a Fragment that is persistently kept in the fragment manager as long as the user can return to the page.

This version of the pager is best for use when there are a handful of typically more static fragments to be paged through, such as a set of tabs. The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible. This can result in using a significant amount of memory since fragment instances can hold on to an arbitrary amount of state. For larger sets of pages, consider FragmentStatePagerAdapter.

When using FragmentPagerAdapter the host ViewPager must have a valid ID set.

Subclasses only need to implement getItem(int) and getCount() to have a working adapter.

译文:

FragmentPagerAdapter派生自PagerAdapter,它是用来呈现Fragment页面的,这些Fragment页面会一直保存在fragment manager中,以便用户可以随时取用。

这个适配器最好用于有限个静态fragment页面的管理。尽管不可见的视图有时会被销毁,但用户所有访问过的fragment都会被保存在内存中。因此fragment实例会保存大量的各种状态,这就造成了很大的内存开销。所以如果要处理大量的页面切换,建议使用FragmentStatePagerAdapter.

每一个使用FragmentPagerAdapterViewPager都要有一个有效的ID集合,有效ID的集合就是Fragment的集合。

对于FragmentPagerAdapter的派生类,只需要重写getItem(int)和getCount()就可以了。

适配器实现——FragmentPagerAdapter

先看完整代码,再细讲:

public class FragAdapter extends FragmentPagerAdapter {  
  
    private List<Fragment> mFragments;  
      
    public FragAdapter(FragmentManager fm,List<Fragment> fragments) {  
        super(fm);  
        // TODO Auto-generated constructor stub  
        mFragments=fragments;  
    }  
  
    @Override  
    public Fragment getItem(int arg0) {  
        // TODO Auto-generated method stub  
        return mFragments.get(arg0);  
    }  
  
    @Override  
    public int getCount() {  
        // TODO Auto-generated method stub  
        return mFragments.size();  
    }  
  
} 

这里有三个函数,根据第一部分的官方文档,可知,对于FragmentPagerAdapter的派生类,只重写getItem(int)和getCount()就可以了。

对于构造函数,这里申请了一个Fragment的List对象,用于保存用于滑动的Fragment对象,并在创造函数中初始化:

public FragAdapter(FragmentManager fm,List<Fragment> fragments) {  
    super(fm);  
    // TODO Auto-generated constructor stub  
    mFragments=fragments;  
}  

然后在getItem(int arg0)中,根据传来的参数arg0,来返回当前要显示的fragment,下面是getItem的官方解释,难度不大,不再细讲。

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

最后,getCount()返回用于滑动的fragment总数;

从构造函数所以看出,我们要构造Fragment的集合才行,所以下面我们就先产生我们所需要的Fragment类;

Fragment类

XML:(fragment_layout.xml)

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:background="#ffffff"  
    android:orientation="vertical" >  
      
    <Button android:id="@+id/fragment1_btn"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="show toast"  
        />  
</LinearLayout>  

Java代码:

public class CustomFragment extends Fragment {  
      
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
            Bundle savedInstanceState) {  
        // TODO Auto-generated method stub  
        View view= inflater.inflate(R.layout.fragment_layout, container, false);  
          
        //对View中控件的操作方法  
        Button btn = (Button)view.findViewById(R.id.fragment1_btn);  
        btn.setOnClickListener(new View.OnClickListener() {  
              
            @Override  
            public void onClick(View v) {  
                // TODO Auto-generated method stub  
                Toast.makeText(getActivity(), "点击了第一个fragment的BTN", Toast.LENGTH_SHORT).show();  
            }  
        });  
        return view;  
    }  
}  

在onCreateView()中返回要显示的View,上面这段代码简单演示了如何对视图里的控件进行操作,难度不大,不再细讲。

主activity实现

核心代码:

public class MainActivity extends FragmentActivity {  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
  
        //构造适配器  
        List<Fragment> fragments=new ArrayList<Fragment>();  
        fragments.add(new Fragment1());  
        fragments.add(new Fragment2());  
        fragments.add(new Fragment3());   
        FragAdapter adapter = new FragAdapter(getSupportFragmentManager(), fragments);  
          
        //设定适配器  
        ViewPager vp = (ViewPager)findViewById(R.id.viewpager);  
        vp.setAdapter(adapter);  
    }  
  
}  

首先有一个最值得注意的地方:Activity派生自FragmentActivity,其实这是有关Fragment的基础知识,只有FragmentActivity才能内嵌fragment页面,普通Activity是不行的。

这段代码主要分为两步,第一步:构造适配器;第二步:设定适配器。
先看构造适配器的过程:

//构造适配器  
List<Fragment> fragments=new ArrayList<Fragment>();  
fragments.add(new Fragment1());  
fragments.add(new Fragment2());  
fragments.add(new Fragment3());   
FragAdapter adapter = new FragAdapter(getSupportFragmentManager(), fragments);  

构造一个fragment列表,然后将上面的三个Fragment类对应的实例添加进去,最后生成FragAdapter实例。
至于第二步,设定适配器,没什么好讲的。

可能出现的问题

问题:在MainActivity中,当写到这句:fragments.add(new Fragment1()); 向Fragment列表中添加Fragement对象实例时,会提示“无法将Fragment1()转换为fragment”

解决办法 :这是因为导入包不一致,一般的问题在于:在Fragment1中导入的是android.app.Fragment, 而在这里导入类却是:android.support.v4.app.Fragment,包不同当然无法转换,统一导入为android.support.v4.app.Fragment之后就正常了.

Fragment初探

我们都知道,Android上的界面展示都是通过Activity实现的,Activity实在是太常用了,我相信大家都已经非常熟悉了,这里就不再赘述。

但是Activity也有它的局限性,同样的界面在手机上显示可能很好看,在平板上就未必了,因为平板的屏幕非常大,手机的界面放在平板上可能会有过分被拉长、控件间距过大等情况。这个时候更好的体验效果是在Activity中嵌入"小Activity",然后每个"小Activity"又可以拥有自己的布局。因此,我们今天的主角Fragment登场了。

为了让界面可以在平板上更好地展示,Android在3.0版本引入了Fragment(碎片)功能,它非常类似于Activity,可以像Activity一样包含布局。Fragment通常是嵌套Activity中使用的,现在想象这种场景:有两个Fragment,Fragment 1包含了一个ListView,每行显示一本书的标题。Fragment 2包含了TextView和ImageView,来显示书的详细内容和图片。

如果现在程序运行竖屏模式的平板或手机上,Fragment 1可能嵌入在一个Activity中,而Fragment 2可能嵌入在另一个Activity中,如下图所示:

而如果现在程序运行在横屏模式的平板上,两个Fragment就可以嵌入在同一个Activity中了,如下图所示:

由此可以看出,使用Fragment可以让我们更加充分地利用平板的屏幕空间,下面我们一起来探究下如何使用Fragment。

首先需要注意,Fragment是在3.0版本引入的,如果你使用的是3.0之前的系统,需要先导入android-support-v4的jar包才能使用Fragment功能。

新建一个项目叫做Fragments,然后在layout文件夹下新建一个名为fragment1.xml的布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:background="#00ff00" >  
  
    <TextView  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="This is fragment 1"  
        android:textColor="#000000"  
        android:textSize="25sp" />  
  
</LinearLayout>  

可以看到,这个布局文件非常简单,只有一个LinearLayout,里面加入了一个TextView。我们如法炮制再新建一个fragment2.xml :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:background="#ffff00" >  
  
    <TextView  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="This is fragment 2"  
        android:textColor="#000000"  
        android:textSize="25sp" />  
  
</LinearLayout>  

然后新建一个类Fragment1,这个类是继承自Fragment的:

public class Fragment1 extends Fragment {  
  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
        return inflater.inflate(R.layout.fragment1, container, false);  
    }  
  
}  

我们可以看到,这个类也非常简单,主要就是加载了我们刚刚写好的fragment1.xml布局文件并返回。同样的方法,我们再写好Fragment2 :

public class Fragment2 extends Fragment {  
  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
        return inflater.inflate(R.layout.fragment2, container, false);  
    }  
  
}  

然后打开或新建activity_main.xml作为主Activity的布局文件,在里面加入两个Fragment的引用,使用android:name前缀来引用具体的Fragment:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:baselineAligned="false" >  
  
    <fragment  
        android:id="@+id/fragment1"  
        android:name="com.example.fragmentdemo.Fragment1"  
        android:layout_width="0dip"  
        android:layout_height="match_parent"  
        android:layout_weight="1" />  
  
    <fragment  
        android:id="@+id/fragment2"  
        android:name="com.example.fragmentdemo.Fragment2"  
        android:layout_width="0dip"  
        android:layout_height="match_parent"  
        android:layout_weight="1" />  
  
</LinearLayout>  

最后打开或新建MainActivity作为程序的主Activity,里面的代码非常简单,都是自动生成的:

public class MainActivity extends Activity {  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
    }  
  
}  

现在我们来运行一次程序,就会看到,一个Activity很融洽地包含了两个Fragment,这两个Fragment平分了整个屏幕,效果图如下:

动态添加Fragment

你已经学会了如何在XML中使用Fragment,但是这仅仅是Fragment最简单的功能而已。Fragment真正的强大之处在于可以动态地添加到Activity当中,因此这也是你必须要掌握的东西。当你学会了在程序运行时向Activity添加Fragment,程序的界面就可以定制的更加多样化。下面我们立刻来看看,如何动态添加Fragment。

还是在上面代码的基础上修改,打开activity_main.xml,将其中对Fragment的引用都删除,只保留最外层的LinearLayout,并给它添加一个id,因为我们要动态添加Fragment,不用在XML里添加了,删除后代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false" >

</LinearLayout>

然后打开MainActivity,修改其中的代码如下所示:

public class MainActivity extends Activity {  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        Display display = getWindowManager().getDefaultDisplay();  
        if (display.getWidth() > display.getHeight()) {  
            Fragment1 fragment1 = new Fragment1();  
            getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment1).commit();  
        } else {  
            Fragment2 fragment2 = new Fragment2();  
            getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment2).commit();  
        }  
    }  
  
}  

首先,我们要获取屏幕的宽度和高度,然后进行判断,如果屏幕宽度大于高度就添加fragment1,如果高度大于宽度就添加fragment2。动态添加Fragment主要分为4步:

1.获取到FragmentManager,在Activity中可以直接通过getFragmentManager得到。

2.开启一个事务,通过调用beginTransaction方法开启。

3.向容器内加入Fragment,一般使用replace方法实现,需要传入容器的id和Fragment的实例。

4.提交事务,调用commit方法提交。

现在运行一下程序,效果如下图所示:

如果你是在使用模拟器运行,按下ctrl + F11切换到竖屏模式。效果如下图所示:

Fragment的生命周期

Activity一样,Fragment也有自己的生命周期,理解Fragment的生命周期非常重要,我们通过代码的方式来瞧一瞧Fragment的生命周期是什么样的:

public class Fragment1 extends Fragment {  
    public static final String TAG = "Fragment1";  
  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
        Log.d(TAG, "onCreateView");  
        return inflater.inflate(R.layout.fragment1, container, false);  
    }  
  
    @Override  
    public void onAttach(Activity activity) {  
        super.onAttach(activity);  
        Log.d(TAG, "onAttach");  
    }  
  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        Log.d(TAG, "onCreate");  
    }  
  
    @Override  
    public void onActivityCreated(Bundle savedInstanceState) {  
        super.onActivityCreated(savedInstanceState);  
        Log.d(TAG, "onActivityCreated");  
    }  
  
    @Override  
    public void onStart() {  
        super.onStart();  
        Log.d(TAG, "onStart");  
    }  
  
    @Override  
    public void onResume() {  
        super.onResume();  
        Log.d(TAG, "onResume");  
    }  
  
    @Override  
    public void onPause() {  
        super.onPause();  
        Log.d(TAG, "onPause");  
    }  
  
    @Override  
    public void onStop() {  
        super.onStop();  
        Log.d(TAG, "onStop");  
    }  
  
    @Override  
    public void onDestroyView() {  
        super.onDestroyView();  
        Log.d(TAG, "onDestroyView");  
    }  
  
    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        Log.d(TAG, "onDestroy");  
    }  
  
    @Override  
    public void onDetach() {  
        super.onDetach();  
        Log.d(TAG, "onDetach");  
    }  
  
}  

可以看到,上面的代码在每个生命周期的方法里都打印了日志,然后我们来运行一下程序,可以看到打印日志如下:

这时点击一下home键,打印日志如下:

如果你再重新进入进入程序,打印日志如下:

然后点击back键退出程序,打印日志如下:

看到这里,我相信大多数朋友已经非常明白了,因为这和Activity的生命周期太相似了。只是有几个Activity中没有的新方法,这里需要重点介绍一下:

  • onAttach方法:Fragment和Activity建立关联的时候调用。

  • onCreateView方法:为Fragment加载布局时调用。

  • onActivityCreated方法:当Activity中的onCreate方法执行完后调用。

  • onDestroyView方法:Fragment中的布局被移除时调用。

  • onDetach方法:Fragment和Activity解除关联的时候调用。

Fragment之间进行通信

通常情况下,Activity都会包含多个Fragment,这时多个Fragment之间如何进行通信就是个非常重要的问题了。我们通过一个例子来看一下,如何在一个Fragment中去访问另一个Fragment的视图。

还是在第一节代码的基础上修改,首先打开fragment2.xml,在这个布局里面添加一个按钮:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical"  
    android:background="#ffff00" >  
  
    <TextView  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="This is fragment 2"  
        android:textColor="#000000"  
        android:textSize="25sp" />  
      
    <Button   
        android:id="@+id/button"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="Get fragment1 text"  
        />  
  
</LinearLayout>  

然后打开fragment1.xml,为TextView添加一个id:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:background="#00ff00" >  
  
    <TextView  
        android:id="@+id/fragment1_text"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="This is fragment 1"  
        android:textColor="#000000"  
        android:textSize="25sp" />  
  
</LinearLayout>  

接着打开Fragment2.java,添加onActivityCreated方法,并处理按钮的点击事件:

public class Fragment2 extends Fragment {  
  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
        return inflater.inflate(R.layout.fragment2, container, false);  
    }  
  
    @Override  
    public void onActivityCreated(Bundle savedInstanceState) {  
        super.onActivityCreated(savedInstanceState);  
        Button button = (Button) getActivity().findViewById(R.id.button);  
        button.setOnClickListener(new OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                TextView textView = (TextView) getActivity().findViewById(R.id.fragment1_text);  
                Toast.makeText(getActivity(), textView.getText(), Toast.LENGTH_LONG).show();  
            }  
        });  
    }  
  
}  

现在运行一下程序,并点击一下fragment2上的按钮,效果如下图所示:

我们可以看到,在fragment2中成功获取到了fragment1中的视图,并弹出Toast。这是怎么实现的呢?主要都是通过getActivity这个方法实现的。getActivity方法可以让Fragment获取到关联的Activity,然后再调用Activity的findViewById方法,就可以获取到和这个Activity关联的其它Fragment的视图了。

ViewPager+Fragment

viewpager内包含了多个fragment,也就是我们说的滑动的页面,这里我只用了两个页面的滑动,借用一张界面分析图。

布局文件

1、main_common_layout.xml 这是一个顶部菜单栏

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            style="@style/MyToolbar"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            android:layout_alignParentTop="true"
            android:layout_alignParentStart="true">

            <TextView
                style="@style/MytoolbarTitle"
                android:text="聊天"
                android:id="@+id/tb_title"/>

            <ImageButton
                style="@style/MytoolbarButton"
                android:id="@+id/tb_bt"
                android:src="@drawable/iab_popup_ic_retry_pressed"
                android:onClick="refresh"/>

        </android.support.v7.widget.Toolbar>
    </LinearLayout>

    <LinearLayout
        android:id="@+id/menu_top"
        android:layout_width="match_parent"
        android:layout_height="55dp"
        android:background="@color/mytoolbarcolor"
        android:gravity="center"
        android:layout_alignParentStart="true">

        <LinearLayout
            android:layout_weight="1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center">

            <ImageView
                android:id="@+id/friend_realtime_info"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@color/transparent"
                android:src="@drawable/gnb_chats_bg"/>

        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:layout_weight="1">

            <ImageView

                android:id="@+id/friend_name_group_list"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@color/transparent"
                android:src="@drawable/gnb_friends_bg" />

        </LinearLayout>

    </LinearLayout>

    <LinearLayout
        android:id="@+id/line_layout"
        android:layout_width="match_parent"
        android:layout_height="3dp"
        android:background="@color/mytoolbarcolor">
             <ImageView
                 android:id="@+id/tabline"
                 android:layout_width="wrap_content"
                 android:layout_height="match_parent"
                 android:background="@color/white" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/gray">
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#e9e8e8"/>
    </LinearLayout>

</LinearLayout>

2、myfragment_activiy.xml 是MyFragmentActivity的布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">

    <include layout="@layout/main_common_layout"/>


    <android.support.v4.view.ViewPager
        android:id="@+id/main_fragment_vp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

3、两个Fragment内容一样,就一个TextView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/msg"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white" />

</LinearLayout>

Java代码

1、MFragmentActivity.java

package com.example.administrator.wechat.activity;

import android.content.ActivityNotFoundException;
...
import butterknife.Bind;
import butterknife.ButterKnife;

public class MyFragmentActivity extends Netcheck_Activity {

    @Bind(R.id.tb_title)
    TextView tb_title;
    @Bind(R.id.tb_bt)
    ImageButton tb_button;
    @Bind(R.id.friend_realtime_info)
    ImageView friend_realtime_info;
    @Bind(R.id.friend_name_group_list)
    ImageView friend_name_group_list;
    @Bind(R.id.tabline)
    ImageView tabline;
    @Bind(R.id.main_fragment_vp)
    ViewPager viewPager;// 声明一个viewpager对象

    private List<Fragment> frag_list;// 声明一个list集合存放Fragment(数据源)
    private int tabLineLength;// 1/3屏幕宽
    private int currentPage = 0;// 初始化当前页为0(第一页)


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.myfragment_activity);
        ButterKnife.bind(this); //注解,用的是ButterKnife包
        // 初始化滑动条1/2
        initTabLine();
        // 初始化界面
        initView();
        //单击事件
        initListener();
    }

    private void initTabLine() {

        // 得到显示屏宽度(网上一堆方法,这里我是直接调用的) ,tabline为1/3屏幕宽度
        tabLineLength = MyApplication.screenWidth / 2;
        // 控件参数
        ViewGroup.LayoutParams lp = tabline.getLayoutParams();
        lp.width = tabLineLength;
        tabline.setLayoutParams(lp);
    }

    //单击事件监听
    private  void initListener(){
        //再加按钮,只需要add,并改initTabLine中的数字即可
        list_imgview = new ArrayList<>();
        list_imgview.add(friend_realtime_info);
        list_imgview.add(friend_name_group_list);

        for (int i= 0;i< list_imgview.size();i++){
            final int finalI = i;
            list_imgview.get(i).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    viewPager.setCurrentItem(finalI);
                }
            });
        }


    }
    private void initView() {
        // 设置数据源
        MainFragment fragment1 = new MainFragment();
        FriendGroupFragment fragment2 = new FriendGroupFragment();
        // 实例化对象
        frag_list = new ArrayList<Fragment>();
        frag_list.add(fragment1);
        frag_list.add(fragment2);


        // 设置适配器
        FragmentPagerAdapter adapter = new FragmentPagerAdapter(
                getSupportFragmentManager()) {

            @Override
            public int getCount() {
                return frag_list.size();
            }

            @Override
            public Fragment getItem(int arg0) {
                return frag_list.get(arg0);
            }


        };

        // 绑定适配器
        viewPager.setAdapter(adapter);
        viewPager.setCurrentItem(0);
        // 设置滑动监听
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

            @Override
            public void onPageSelected(int position) {
                // 当页面被选择时,先将2个imageview的图标初始化
                friend_realtime_info.setImageResource(R.drawable.gnb_ic_chats_normal);
                friend_name_group_list.setImageResource(R.drawable.gnb_ic_friends_normal);

                // 再改变当前选择页(position)对应的图标和textview
                switch (position) {
                    case 0:
                        friend_realtime_info.setImageResource(R.drawable.gnb_ic_chats_selected);
                        tb_title.setText("聊天");
                        break;
                    case 1:
                        friend_name_group_list.setImageResource(R.drawable.gnb_ic_friends_selected);
                        tb_title.setText("好友");
                        break;

                }

                currentPage = position;

            }

            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {
                //arg0在第一页到第二页的过程中curentPage=0,arg0=0,最后到达第二页curentPage=1,arg0=1,中间过程全为0
                // arg1是个偏移值

                // 取得该控件的实例
                LinearLayout.LayoutParams ll = (android.widget.LinearLayout.LayoutParams) tabline
                        .getLayoutParams();

                if (currentPage == 0 && arg0 == 0) { 
                // 0->1移动(第一页到第二页)
                }

                tabline.setLayoutParams(ll);

            }

            @Override
            public void onPageScrollStateChanged(int arg0) {
                // TODO Auto-generated method stub

            }
        });

    }
}

2、fragment1.java (第二个省略)

public class FriendGroupFragment extends Fragment {


    private TextView tview;
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.friendgroup,container,false);

        tview= view.findViewById(R.id.tv);
        tview.setText("这是第一个");
        return view;
    }
}

ViewPager + Fragment组合实现局部刷新Fragment

在开发过程中,经常会用到ViewPager与Fragment实现多页面切换效果,有时,我们想要局部刷新某些Fragment,而其他Fragment保持状态不变,该如何做到呢?

先上代码!

/**
 * Created by  .
 */
public abstract class BaseFragmentPagerAdapter extends FragmentPagerAdapter {

    private FragmentManager mFragmentManager;

    //保存每个Fragment的Tag,刷新页面的依据
    protected SparseArray<String> tags = new SparseArray<>();

    public BaseFragmentPagerAdapter(FragmentManager fm) {
        super(fm);
        mFragmentManager = fm;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        //得到缓存的fragment
        Fragment fragment = (Fragment) super.instantiateItem(container, position); 
        String tag = fragment.getTag();
        //保存每个Fragment的Tag
        tags.put(position, tag);
        return fragment;
    }

    //拿到指定位置的Fragment
    public Fragment getFragmentByPosition(int position) {
        return mFragmentManager.findFragmentByTag(tags.get(position));
    }

    public List<Fragment> getFragments(){
        return mFragmentManager.getFragments();
    }

    //刷新指定位置的Fragment
    public void notifyFragmentByPosition(int position) {
        tags.removeAt(position);
        notifyDataSetChanged();
    }

    @Override
    public int getItemPosition(Object object) {
        Fragment fragment = (Fragment) object;
        //如果Item对应的Tag存在,则不进行刷新
        if (tags.indexOfValue(fragment.getTag()) > -1) {
            return super.getItemPosition(object);
        }
        return POSITION_NONE;
    }
}
/**
 * Created by .
 */
public class CustomLrcPagerAdapter extends BaseFragmentPagerAdapter {
    private List<String> lrcs = new ArrayList<>();
    private MusicInfo info;

    public CustomLrcPagerAdapter(FragmentManager fm, MusicInfo info) {
        super(fm);
        this.info = info;
    }

    public void addDatas(List<String> lrcs) {
        this.lrcs.addAll(lrcs);
        notifyDataSetChanged();
    }

    @Override
    public Fragment getItem(int position) {
        return CustomLrcFragment.newInstance(info, lrcs.get(position), position);
    }

    //除了给定位置,其他位置的Fragment不进行刷新
    public void notifyChangeWithoutPosition(int position) {
        String valueP = tags.valueAt(position);
        tags.clear();
        tags.put(position, valueP);
        notifyDataSetChanged();
    }


    @Override
    public int getCount() {
        return lrcs.size();
    }
}

刷新的核心原理很简单,相信看过源码的都会,在PagerAdapter中提供了一个方法:

/**
 * Called when the host view is attempting to determine if an item's position
 * has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given
 * item has not changed or {@link #POSITION_NONE} if the item is no longer present
 * in the adapter.
 *
 * <p>The default implementation assumes that items will never
 * change position and always returns {@link #POSITION_UNCHANGED}.
 *
 * @param object Object representing an item, previously returned by a call to
 *               {@link #instantiateItem(View, int)}.
 * @return object's new position index from [0, {@link #getCount()}),
 *         {@link #POSITION_UNCHANGED} if the object's position has not changed,
 *         or {@link #POSITION_NONE} if the item is no longer present.
 */
public int getItemPosition(Object object) {
    return POSITION_UNCHANGED;
}

注释中已经说明了,当我们返回了POSITION_UNCHANGED,则表示页面数据不变,不进行更新;
返回POSITION_NONE,则表示页面不存在,需要进行更新。

因此,我重写了该方法:

    @Override
    public int getItemPosition(Object object) {
        Fragment fragment = (Fragment) object;
        //如果Item对应的Tag存在,则不进行刷新
        if (tags.indexOfValue(fragment.getTag()) > -1) {
            return super.getItemPosition(object);
        }
        return POSITION_NONE;
    }

在接触公司项目的过程中,发现公司项目中ViewPager+Fragment组合的使用方式存在问题,估计很多人也这么用过,就是定义一个集合用来缓存放到ViewPager中的Fragment,类似我们公司项目的这种做法:

/**
 * Created by Administrator on 2016/11/30.
 */
public class ViewPagerAdapter extends FragmentStatePagerAdapter {

    private List<Fragment> mList_Fragment = new ArrayList<>();
    private HashMap<Integer, Boolean> mList_Need_Update = new HashMap<>();
    private FragmentManager mFragmentManager;

    public ViewPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);
        mFragmentManager = fm;
        mList_Need_Update.clear();
        mList_Fragment.clear();
        if (fragments != null) {
            mList_Fragment.addAll(fragments);
        }
    }

//    @Override
//    public Object instantiateItem(ViewGroup container, int position) {
//        Fragment fragment = (Fragment) super.instantiateItem(container, position); //得到缓存的fragment
//
//        Boolean update = mList_Need_Update.get(position);
//        if (update != null && update) {
//            String fragmentTag = fragment.getTag(); //得到tag,这点很重要
//            FragmentTransaction ft = mFragmentManager.beginTransaction();
//            ft.remove(fragment); //移除旧的fragment
//            fragment = getItem(position); //换成新的fragment
//            ft.add(container.getId(), fragment, fragmentTag); //添加新fragment时必须用前面获得的tag,这点很重要
//            ft.attach(fragment);
//            ft.commit();
//            mList_Need_Update.put(position, false); //清除更新标记(只有重新启动的时候需要去创建新的fragment对象),防止正常情况下频繁创建对象
//        }
//
//        return fragment;
//    }

    public List<Fragment> getListFragment(){
        return mList_Fragment;
    }

    public void setListFragment(List<Fragment> list_Fragment) {
//        if(list_Fragment != null){
//            FragmentTransaction ft = mFragmentManager.beginTransaction();
//            for (int i = 0; i < mList_Fragment.size(); i++) {
//                Fragment fragment = (Fragment) mList_Fragment.get(i);
//                ft.remove(fragment);
//            }
//            ft.commit();
//            ft = null;
//            mFragmentManager.executePendingTransactions();
//        }
        mList_Need_Update.clear();
        this.mList_Fragment.clear();
        if (list_Fragment != null) {
            this.mList_Fragment.addAll(list_Fragment);
        }
        notifyDataSetChanged();
    }

    public void setListNeedUpdate(List<Fragment> fragments) {
        mList_Fragment.clear();
        if (fragments != null) {
            mList_Fragment.addAll(fragments);
        }
        mList_Need_Update.clear();
        for (int i = 0; i < mList_Fragment.size(); i++) {
            mList_Need_Update.put(i, true);
        }
    }

    @Override
    public Fragment getItem(int position) {
        if(mList_Fragment.size() < position){
            return null;
        }
        return mList_Fragment.get(position);
    }


    @Override
    public int getCount() {
        return mList_Fragment.size();
    }

    @Override
    public int getItemPosition(Object object) {
        return PagerAdapter.POSITION_NONE;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
        try {
            super.restoreState(state, loader);
        } catch (Exception e) {

        }

    }
}

这种做法看似方便我们操作ViewPager中的Fragment,但是存在一个很致命的问题。

某些情况下,我们在从其他页面回退到ViewPager,在进行Fragment数据更新时,会发现居然没有效果(或者效果很诡异,例如会出现多次调用的情况)。

这种情况其实就是Fragment进行了热启动。(我的说法不知是否准确,指的就是内存不足时,页面被销毁了并调用了onSaveInstanceState方法,在重新回到页面时,我们可以从Bundle savedInstanceState中拿到之前缓存的数据。)

由于FragmentPagerAdapter中的FragmentManager已经帮我们缓存了所有Fragment,并且在数据恢复时,也自动帮我们进行恢复处理。
所以,个人猜测 (未经源码验证的!) ,在FragmentManager进行数据恢复时,如果我们本地通过集合缓存了一份Fragment,则这份Fragment与FragmentManager进行数据恢复后的Fragment是不同的!

我个人的做法是,每次需要操作ViewPager中的Fragment时,都从FragmentManager中拿:

    //拿到指定位置的Fragment
    public Fragment getFragmentByPosition(int position) {
        return mFragmentManager.findFragmentByTag(tags.get(position));
    }

    public List<Fragment> getFragments(){
        return mFragmentManager.getFragments();
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容