重拾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();
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容