ViewPager+Fragment刷新更新Fragment

需求如下:頂部UI(這裏隨便寫的),一些標籤(服務器給的,這裏寫死),切換標籤展示不同內容,內容分頁展示,要求可以下拉刷新(重要頁面,沒刷新太low了吧驚恐),刷新之後還停在當前標籤下面,但是內容也要刷新,切換內容也會如此。(插插更健康:github源碼有ScrollableLayout三段式懸浮佈局,相同需求)。demo效果如下:


需求分析:看上去很簡單的一個頁面,但是做起來不一定那麼順暢,看我一步步分析。選用的控件毫無疑問是SwipeRefreshLayout+TabLayout+ViewPager+Fragment+FragmentStatePagerAdapter 。

        分析1:由於標籤數量length不確定(比較多),Fragment有預加載機制,如果使用默認的setOffscreenPageLimit提前加載下一個fragment,切換的時候導致看過的fragment被銷燬/detach,再重新切換回去的話又得重新加載頁面,請求api,體驗很不好。如果使用setOffscreenPageLimit(length)預加載所有的fragment,更浪費資源,也不合理。所以需要使用Fragment的懶加載了,具體實現看後面的代碼。

        分析2:爲什麼不用FragmentPagerAdapter而是用FragmentStatePagerAdapter?

        FragmentPagerAdapter類內的每一個生成的 Fragment 都將保存在內存之中,因此適用於那些相對靜態的頁,數量也比較少的那種。Fragment在切換的時候,不會銷燬,而只是調用事務中的detach方法,我們只會把我們的Fragment的view銷燬,而保留了以前的Fragment對象。所以通過這種方式創建的Fragment一直不會被銷燬。

         FragmentSatetePagerAdapter 的實現將只保留當前頁面,當頁面離開視線後,就會被消除,僅保留狀態信息,釋放其資源;而在頁面需要顯示時,恢復狀態信息,這樣更節省內存。

        綜上,根據需求選定FragmentStatePagerAdapter!

代碼實現:

fragment懶加載基類:

import android.support.v4.app.Fragment;

/**
 * Created by zrg on 2018/1/2.
 * Fragment的懶加載模式
 * 使用方法:
 * ***** 必須繼承 FragmentPagerAdapter/FragmentStatePagerAdapter,纔可以對setUserVisibleHint()調用
 * setUserVisibleHint() 設置Fragment的可見狀態
 * getUserVisibleHint() 獲取Fragment的可見狀態
 * mIsVisible 是父類的成員變量,子類不需要重寫
 * mIsprepared 子類需要在OnCreateView()中重寫
 * mHasLoadedOnce 子類需要在lazyLoad()中重寫
 * <p>
 * 接入步驟:
 * 1:繼承BaseLazyFragment,實現lazyLoad()
 * 2:在onCreateView()中,添加以下代碼:
 * mIsprepared = true;
 * lazyLoad();
 * 3:重寫lazyLoad(){
 * if (!mIsprepared || !mIsVisible || mHasLoadedOnce) {
 * return;
 * }
 * mHasLoadedOnce = true;
 * //UI和業務邏輯
 * }
 * 4:拓展方法
 * stopLoad()://當視圖已經對用戶不可見並且加載過數據,
 * //如果需要在切換到其他頁面時停止加載數據,可以覆寫此方法
 * //此方法只在Fragment之間切換生效
 * visibleToUser(): 用戶可看見當前的Fragment (神策埋點需要)
 * inVisibleToUser():用戶看不見當前的Fragment,包含Fragment之間切換和跳轉到Activity (神策埋點需要)
 */
public abstract class BaseLazyFragment extends Fragment {

    /**
     * 判斷當前的Fragment是否可見(相對於其他的Fragment)
     */
    protected boolean mIsVisible;

    /**
     * 標誌位,標誌已經初始化完成
     */
    protected boolean mIsprepared;
    /**
     * 是否已被加載過一次,第二次就不再去請求數據了
     */
    protected boolean mHasLoadedOnce;

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        //設置Fragment的可見狀態
        super.setUserVisibleHint(isVisibleToUser);
        if (getUserVisibleHint()) {//getUserVisibleHint獲取Fragment可見狀態
            mIsVisible = true;
            onVisible();
        } else {
            mIsVisible = false;
            onInvisible();
        }

        if (isResumed()) {
            onVisibilityChangedToUser(isVisibleToUser);
        }
    }

    /**
     * 可見
     */
    protected void onVisible() {
        lazyLoad();
    }

    /**
     * 不可見
     */
    protected void onInvisible() {
        stopLoad();
    }

    /**
     * 延遲加載
     * 子類必須重寫此方法
     */
    protected abstract void lazyLoad();

    /**
     * 當視圖已經對用戶不可見並且加載過數據,如果需要在切換到其他頁面時停止加載數據,可以覆寫此方法
     */
    protected void stopLoad() {
    }

    //region 神策埋點需要,統計Fragement 可見時間
    @Override
    public void onResume() {
        super.onResume();
        if (getUserVisibleHint()) {
            onVisibilityChangedToUser(true);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (getUserVisibleHint()) {
            onVisibilityChangedToUser(false);
        }
    }

    /**
     * 當Fragment對用戶的可見性發生了改變的時候就會回調此方法
     *
     * @param isVisibleToUser true:用戶能看見當前Fragment;false:用戶看不見當前Fragment
     */
    public void onVisibilityChangedToUser(boolean isVisibleToUser) {
        if (isVisibleToUser) {
            visibleToUser();
        } else {
            inVisibleToUser();
        }
    }

    protected void visibleToUser() {

    }

    protected void inVisibleToUser() {

    }
    //endregion

}

ViewPagerActivity

public class ViewPagerActivity extends AppCompatActivity {
    private String[] TITLES = {"語文", "數學", "英語", "化學", "物理", "生物"};

    SwipeRefreshLayout mSwipeRefreshLayout;
    TabLayout mTabLayout;
    ViewPager mViewPager;

    private ViewPagerAdapter mAdapter;
    private int mCurrentIndex;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_pager);

        mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
        mTabLayout = (TabLayout) findViewById(R.id.tab_layout);
        mViewPager = (ViewPager) findViewById(R.id.view_pager);

        mAdapter = new ViewPagerAdapter(getSupportFragmentManager(), TITLES);
        mViewPager.setAdapter(mAdapter);
        mViewPager.setCurrentItem(0);
        mViewPager.setOffscreenPageLimit(TITLES.length - 1);
        mTabLayout.setupWithViewPager(mViewPager);

        final Handler handler = new Handler(Looper.getMainLooper());
        mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                loadData();

                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mSwipeRefreshLayout.setRefreshing(false);
                    }
                }, 1000);
            }
        });

        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                mCurrentIndex = position;
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
    }

    private void loadData() {
        String[] titles = {"語文1", "數學1", "英語1", "化學1", "物理1", "生物1"};
        mAdapter.setTitles(titles);
    }
}

activity_view_pager

<android.support.v4.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/swipeRefreshLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.hp.viewpagerdemo.ViewPagerActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="120dp"
            android:src="@mipmap/ic_launcher"/>

        <android.support.design.widget.TabLayout
            android:id="@+id/tab_layout"
            style="@style/MyTablayoutStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

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

    </LinearLayout>

</android.support.v4.widget.SwipeRefreshLayout>

<!--Tablayout 設置屬性-->
<style name="MyTablayoutStyle" parent="Widget.Design.TabLayout">
    <item name="tabIndicatorColor">#ff4f47</item>
    <item name="tabSelectedTextColor">#4d4d4d</item>
    <item name="tabBackground">@android:color/white</item>
    <item name="tabTextAppearance">@style/MyTabLayoutTextAppearance</item>
</style>

<!--TabLayout 小標題-->
<style name="MyTabLayoutTextAppearance" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
    <item name="android:textColor">#999999</item>
    <item name="android:textSize">14sp</item>
</style>

ViewPagerAdapter

public class ViewPagerAdapter extends FragmentStatePagerAdapter {
    private String[] mTitles;
    public ViewPagerAdapter(FragmentManager fm, String[] titles) {
        super(fm);
        mTitles = titles;
    }
    @Override
    public Fragment getItem(int position) {
        EmptyFragment fragment = EmptyFragment.newInstance(mTitles[position]);
        Log.e("zrg", "getItem: 當前位置position=" + position);
        return fragment;
    }

    @Override
    public int getCount() {
        return mTitles == null ? 0 : mTitles.length;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Log.e("zrg", "instantiateItem: 當前位置position=" + position);
        return super.instantiateItem(container, position);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mTitles == null ? "" : mTitles[position];
    }

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

    public void setTitles(String[] titles) {
        mTitles = titles;
        notifyDataSetChanged();
    }
}

EmptyFragment

public class EmptyFragment extends BaseLazyFragment {
    private static final String ARG_TITLE = "arg_title";

    TextView mTvClick;

    private String mTitle;

    public EmptyFragment() {
    }

    public static EmptyFragment newInstance(String title) {
        EmptyFragment fragment = new EmptyFragment();
        Bundle bundle = new Bundle();
        bundle.putString(ARG_TITLE, title);
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mTitle = getArguments().getString(ARG_TITLE);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_empty, container, false);
        mTvClick = (TextView) view.findViewById(R.id.tv_click);
        mIsprepared = true;
        lazyLoad();
        return view;
    }

    @Override
    protected void lazyLoad() {
        if (!mIsprepared || !mIsVisible || mHasLoadedOnce) {
            return;
        }
        mHasLoadedOnce = true;
        //UI和業務邏輯
        Log.e("zrg", "lazyLoad: 當前的fragment是:" + mTitle);
        mTvClick.setText(mTitle);
    }

    public String getTitle() {
        return mTitle;
    }
}
fragment_empty
<android.support.constraint.ConstraintLayout
    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="match_parent">

    <TextView
        android:id="@+id/tv_click"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

</android.support.constraint.ConstraintLayout>

注意事項

    1、更新fragment不能使用重新設置adapter的方式,只能通過adapter.setTitle(args...)讓adapter完成更新。

private void errorLoadData(){
        String[] titles = {"語文1", "數學1", "英語1", "化學1", "物理1", "生物1"};
        mAdapter = new ViewPagerAdapter(getSupportFragmentManager(), titles);
        mViewPager.setAdapter(mAdapter);
        mViewPager.setCurrentItem(mCurrentIndex);
        mViewPager.setOffscreenPageLimit(titles.length - 1);
        mTabLayout.setupWithViewPager(mViewPager);
    }
    

源碼地址:https://github.com/zrg1215/ViewPagerRefreshDemo

源碼包括ScrollableLayout三段式懸浮佈局,實現方式大致相同,由於切換的時候需要知道當前fragment,源碼裏有得意,如果不瞭解ScrollableLayout的童鞋,傳送門


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章