閱讀998 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Android之ActionBar、Tabs、Fragment、ViewPager實現標簽頁切換並緩存頁麵

感覺 Android 到處都是坑,每個地方都要把人折騰半天。

今天來簡單說說 Android之ActionBar、Tabs、Fragment、ViewPager 實現標簽頁切換並緩存頁麵

關於他們的介紹就不多說了,網上到處都是,隻說關鍵的部分:

我在開發的時候遇到幾個疑難問題,花費大量時間處理,總結如下:

1. 關於 Fragment 內部邏輯處理該寫在哪個事件回調部分?

2. ViewPager 頁麵切換動畫卡頓,讓我頭疼了很久。

3. ViewPager 中如何保存 Fragment 當前視圖的狀態,讓 Tabs 頁麵切換後不會重新加載,這地方很坑爹

4. ActionBar 中的 tab 很多時如何滾動顯示


解答:

一、Fragment 的事件回調:

package com.ai9475.meitian.ui.fragment;

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;

import com.ai9475.meitian.R;
import com.ai9475.meitian.view.DiaryList;
import com.ai9475.util.ZLog;

/**
 *
 * Created by ZHOUZ on 14-1-21.
 */
public class DiaryListFragment extends BaseFragment
{
    private static final String TAG = "DiaryListFragment";

    @Override
    public void onAttach(Activity activity)
    {
        ZLog.i(TAG, "onAttach");
        super.onAttach(activity);
    }

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        ZLog.i(TAG, "onCreate");
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        ZLog.i(TAG, "onCreateView");
        return inflater.inflate(R.layout.fragment_diary_list, container, false);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState)
    {
        ZLog.i(TAG, "onActivityCreated");
        super.onActivityCreated(savedInstanceState);
        ZLog.i(TAG, "DiaryList0");
        DiaryList diaryList = new DiaryList(
                getActivity().getApplicationContext(),
                (ListView) getView().findViewById(R.id.diaryListCt)

        );
        ZLog.i(TAG, "DiaryList load0");
        diaryList.load("https://m.ai9475.com/?con=meitian_app");
        this.setRetainInstance(true);
    }

    @Override
    public void onStart()
    {
        ZLog.i(TAG, "onStart");
        super.onStart();
    }

    @Override
    public void onResume()
    {
        ZLog.i(TAG, "onResume");
        super.onResume();
    }

    @Override
    public void onPause()
    {
        ZLog.i(TAG, "onPause");
        super.onPause();
    }

    @Override
    public void onStop()
    {
        ZLog.i(TAG, "onStop");
        super.onStop();
    }

    @Override
    public void onDestroyView()
    {
        ZLog.i(TAG, "onDestroyView");
        super.onDestroyView();
    }

    @Override
    public void onDestroy()
    {
        ZLog.i(TAG, "onDestroy");
        super.onDestroy();
    }

    @Override
    public void onDetach()
    {
        ZLog.i(TAG, "onDetach");
        super.onDetach();
    }
}

上麵的類中的 on 事件就是Fragment主要處理的時間回調,注意複寫父類方法時要回調執行父類同名方法,否則會出錯

主要複寫 onCreateView 方法,返回該 Fragment 所對應的視圖對象,這裏可以在返回視圖對象前進行一些簡單的配置,但千萬不要寫太耗時的處理阻塞UI主線程。

另外 onActivityCreated 方法,是當 activity 的 onCreate 事件結束時的回調,此時當前的Fragment對應的view已經並入到整個布局中,此時可以使用 getView() 方法獲取視圖對象。

其他幾個事件沒什麼太多可說,有些我也還不是太清楚,還有些動畫調用的事件。


二、切換頁麵卡頓問題

這個問題的產生主要可能是兩方麵,

1. 沒有使用 ViewPager 的緩存,每次切換都重新加載。

2. 加載 Fragment 內部有耗時耗資源的邏輯處理。

這裏主要說下第二種情況,我一開始沒處理掉 緩存問題時有一個解決辦法,

    <android.support.v4.view.ViewPager
        android:
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    </android.support.v4.view.ViewPager>

mViewPager = (ViewPager) findViewById(R.id.tabsViewPager);
mViewPager.setOnPageChangeListener(
                new ViewPager.SimpleOnPageChangeListener() {
                    private static final String TAG = "ViewPager.SimpleOnPageChangeListener";
                    private ArrayList hasLoadedPages = new ArrayList<Integer>();
                    @Override
                    public void onPageSelected(int position) {
                        ZLog.i(TAG, "onPageSelected position:"+ position);
                        getSupportActionBar().setSelectedNavigationItem(position);
                    }

                    @Override
                    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
                    {
                        ZLog.i(TAG, "onPageScrolled position: "+ position +", positionOffset:"+ positionOffset +", positionOffsetPixels:"+ positionOffsetPixels);
                    }

                    @Override
                    public void onPageScrollStateChanged(int state) {
                        ZLog.i(TAG, "onPageScrollStateChanged");
                        int position = mViewPager.getCurrentItem();

                        switch (state) {
                            // 正在拖動
                            case ViewPager.SCROLL_STATE_DRAGGING :
                                ZLog.i(TAG, "ViewPager.SCROLL_STATE_DRAGGING position:"+ position);
                                break;
                            // 拖動釋放後正在沉降的過程
                            case ViewPager.SCROLL_STATE_SETTLING :
                                ZLog.i(TAG, "ViewPager.SCROLL_STATE_SETTLING position:"+ position);
                                break;
                            // 切換動畫全部完成結束
                            case ViewPager.SCROLL_STATE_IDLE :
                                ZLog.i(TAG, "ViewPager.SCROLL_STATE_IDLE position:"+ position);
				// 已加載過則不再加載
                                if (hasLoadedPages.contains(position)) break;
                                Fragment fragment = mPager.getFragments().get(position);
                                runCallback(position, fragment);
                                hasLoadedPages.add(position);
                                break;
                        }
                    }

                    public void runCallback(int position, Fragment fragment) {
                        ZLog.i(TAG, "runCallback");
                        DiaryList diaryList;
                        switch (position) {
                            case 0 :
                                ZLog.i(TAG, "DiaryList0");
                                diaryList = new DiaryList(
                                        getApplicationContext(),
                                        (ListView) fragment.getView().findViewById(R.id.diaryListCt)

                                );
                                ZLog.i(TAG, "DiaryList load0");
                                diaryList.load("https://m.ai9475.com/?con=meitian_app");
                                break;
                            case 1:
                                ZLog.i(TAG, "DiaryList1");
                                diaryList = new DiaryList(
                                        getApplicationContext(),
                                        (ListView) fragment.getView().findViewById(R.id.diaryListCt)

                                );
                                ZLog.i(TAG, "DiaryList load1");
                                diaryList.load("https://m.ai9475.com/?con=meitian_app&act=hot");
                                break;
                            case 2:
                                ZLog.i(TAG, "DiaryList2");
                                diaryList = new DiaryList(
                                        getApplicationContext(),
                                        (ListView) fragment.getView().findViewById(R.id.diaryListCt)

                                );
                                ZLog.i(TAG, "DiaryList load2");
                                diaryList.load("https://m.ai9475.com/?con=meitian_app");
                                break;
                        }
                    }
                }

這裏主要用到 public void onPageScrollStateChanged(int state) 頁麵滾動切換狀態變化的事件監聽

當滾動動畫完全結束 case ViewPager.SCROLL_STATE_IDLE  時再執行 Fragment 的邏輯處理,這樣動畫就會流暢了。

但經過後來的測試發現有個很簡單的解決方案,就是下麵要說到的 ViewPager 的緩存功能,其實很簡單。


三、緩存 Tabs 頁麵切換不重新加載數據

我在這地方折騰的最久,而且很多時候無從下手的感覺,網上搜索了很多文章都有說道保存 Fragment 數據和狀態,但是沒有整整提到如何來保存他的當前view狀態,不知道如何保存,當然實際上我還是沒搞懂如何僅僅緩存 Fragment 的狀態,但對於 ViewPager 緩存 Tab 對應的 Fragment 還是找到了辦法,之前花了很大功夫來自己實現,後來偶然發現他居然有個自帶的方法

        // 設置緩存多少個 Tab對應的 fragment
        mViewPager.setOffscreenPageLimit(6);

我測試時用了 6個 listView 加載圖片列表數據,切換動畫也沒有任何卡頓現象,非常流暢,就這麼簡單一句就搞定了。

配置該項後,ViewPager在切換時將不會清理不可見的 Fragment,不會觸發 Fragment 的任何事件,因此也就不會導致其重新加載。


四、ActionBar 中的 Tabs 

這個其實不用操作,在tabs數量超過一屏後,例如我現在設置6個寬度超過了屏幕則會變成可以橫向滾動的狀態,而不需要自己實現,之前不知道在這方麵查了很多資料都無果,隻知道可以用 tabhost 可以實現,但在ActionBar 裏麵卻又用不了,自己試了下方多個tab才發現原來會自動實現,無語。




還是貼上 MainActivity.class 完整代碼:

package com.ai9475.meitian.ui;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.view.Menu;
import android.widget.ListView;

import com.ai9475.meitian.AppManager;
import com.ai9475.meitian.R;
import com.ai9475.meitian.ui.fragment.DiaryListFragment;
import com.ai9475.meitian.ui.fragment.Test2Fragment;
import com.ai9475.meitian.ui.fragment.Test3Fragment;
import com.ai9475.meitian.view.DiaryList;
import com.ai9475.util.ZLog;

import java.util.ArrayList;

public class MainActivity extends BaseActivity
{
    private static final String TAG = "MainActivity";
    private MyTabsPagerAdapter mPager;
    private ViewPager mViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        ZLog.i(TAG, "start");
        // 執行父級初始化方法
        super.onCreate(savedInstanceState);
        //requestWindowFeature(Window.FEATURE_NO_TITLE);
        ZLog.i(TAG, "setContentView");
        setContentView(R.layout.activity_main);

        // 滑動頁麵視圖配置
        ZLog.i(TAG, "MyTabsPagerAdapter start");
        mPager = new MyTabsPagerAdapter(getSupportFragmentManager());
        mPager.getFragments().add(new DiaryListFragment());
        mPager.getFragments().add(new Test2Fragment());
        mPager.getFragments().add(new Test3Fragment());
        mPager.getFragments().add(new Test3Fragment());
        mPager.getFragments().add(new Test3Fragment());
        mPager.getFragments().add(new Test3Fragment());
        // 滑動分頁容器
        mViewPager = (ViewPager) findViewById(R.id.tabsViewPager);
        // 設置緩存多少個 fragment
        mViewPager.setOffscreenPageLimit(6);
        mViewPager.setAdapter(mPager);
        // 頁麵滑動事件
        mViewPager.setOnPageChangeListener(
                new ViewPager.SimpleOnPageChangeListener() {
                    private static final String TAG = "ViewPager.SimpleOnPageChangeListener";
                    private ArrayList hasLoadedPages = new ArrayList<Integer>();
                    @Override
                    public void onPageSelected(int position) {
                        ZLog.i(TAG, "onPageSelected position:"+ position);
                        getSupportActionBar().setSelectedNavigationItem(position);
                    }

                    @Override
                    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
                    {
                        ZLog.i(TAG, "onPageScrolled position: "+ position +", positionOffset:"+ positionOffset +", positionOffsetPixels:"+ positionOffsetPixels);
                    }

                    @Override
                    public void onPageScrollStateChanged(int state) {
                        ZLog.i(TAG, "onPageScrollStateChanged");
                        int position = mViewPager.getCurrentItem();

                        switch (state) {
                            // 正在拖動
                            case ViewPager.SCROLL_STATE_DRAGGING :
                                ZLog.i(TAG, "ViewPager.SCROLL_STATE_DRAGGING position:"+ position);
                                break;
                            // 拖動釋放後正在沉降的過程
                            case ViewPager.SCROLL_STATE_SETTLING :
                                ZLog.i(TAG, "ViewPager.SCROLL_STATE_SETTLING position:"+ position);
                                break;
                            // 切換動畫全部完成結束
                            case ViewPager.SCROLL_STATE_IDLE :
                                ZLog.i(TAG, "ViewPager.SCROLL_STATE_IDLE position:"+ position);
                                /*if (hasLoadedPages.contains(position)) break;
                                Fragment fragment = mPager.getFragments().get(position);
                                runCallback(position, fragment);
                                hasLoadedPages.add(position);*/
                                break;
                        }
                    }

                    public void runCallback(int position, Fragment fragment) {
                        ZLog.i(TAG, "runCallback");
                        DiaryList diaryList;
                        switch (position) {
                            case 0 :
                                ZLog.i(TAG, "DiaryList0");
                                diaryList = new DiaryList(
                                        getApplicationContext(),
                                        (ListView) fragment.getView().findViewById(R.id.diaryListCt)

                                );
                                ZLog.i(TAG, "DiaryList load0");
                                diaryList.load("https://m.ai9475.com/?con=meitian_app");
                                break;
                            case 1:
                                ZLog.i(TAG, "DiaryList1");
                                diaryList = new DiaryList(
                                        getApplicationContext(),
                                        (ListView) fragment.getView().findViewById(R.id.diaryListCt)

                                );
                                ZLog.i(TAG, "DiaryList load1");
                                diaryList.load("https://m.ai9475.com/?con=meitian_app&act=hot");
                                break;
                            case 2:
                                ZLog.i(TAG, "DiaryList2");
                                diaryList = new DiaryList(
                                        getApplicationContext(),
                                        (ListView) fragment.getView().findViewById(R.id.diaryListCt)

                                );
                                ZLog.i(TAG, "DiaryList load2");
                                diaryList.load("https://m.ai9475.com/?con=meitian_app");
                                break;
                        }
                    }
                }
        );

        ActionBar actionBar = getSupportActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        // Tab 頁麵切換
        MyTabListener listener = new MyTabListener();
        // 默認的首頁 tab
        ActionBar.Tab indexTab = actionBar.newTab()
                .setText(getString(R.string.tab_index))
                .setTabListener(listener);
        actionBar.addTab(indexTab);
        actionBar.addTab(actionBar.newTab()
                .setText(getString(R.string.tab_hot))
                .setTabListener(listener)
        );
        actionBar.addTab(actionBar.newTab()
                .setText(getString(R.string.tab_tag))
                .setTabListener(listener)
        );
        actionBar.addTab(actionBar.newTab()
                .setText(getString(R.string.tab_tag))
                .setTabListener(listener)
        );
        actionBar.addTab(actionBar.newTab()
                .setText(getString(R.string.tab_tag))
                .setTabListener(listener)
        );
        actionBar.addTab(actionBar.newTab()
                .setText(getString(R.string.tab_tag))
                .setTabListener(listener)
        );

        // 顯示首頁
        indexTab.select();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    private class MyTabListener implements ActionBar.TabListener
    {
        @Override
        public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft)
        {
            int position = tab.getPosition();
            ZLog.i(TAG, "tab selected: "+ position);
            // 數據通信
            /*Bundle bundle = new Bundle();
            Fragment fragment = mPager.getItem(tab.getPosition());
            Toast.makeText(getApplicationContext(), "position:"+ tab.getPosition(), Toast.LENGTH_SHORT).show();
            fragment.setArguments(bundle);
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            fragmentTransaction.add(R.id.fragmentContainer, fragment);
            fragmentTransaction.commit();*/
            mViewPager.setCurrentItem(position);
        }

        @Override
        public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
            ZLog.i(TAG, "tab reselected: "+ tab.getPosition());
        }

        @Override
        public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft)
        {
            ZLog.i(TAG, "tab unselected: "+ tab.getPosition());
        }
    };

    public class MyTabsPagerAdapter extends FragmentPagerAdapter
    {
        private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

        public MyTabsPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        public ArrayList<Fragment> getFragments() {
            return this.mFragments;
        }

        @Override
        public Fragment getItem(int i) {
            return this.mFragments.get(i);
        }

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




最後更新:2017-04-03 12:55:12

  上一篇:go 連載:麵向對象葵花寶典:思想、技巧與實踐(12) - “繼承” 詳解
  下一篇:go Android Studio Gradle project refresh failed No such property classpath for class