Day8-Fragment懶加載+ViewPager+TabLayout

Tips:
如何設(shè)置 xml 的屬性預(yù)覽可見, 運行不可見
方法: xml 根布局添加xmlns:tool="http://schemas.android.com/tools", 子控件命名空間設(shè)置tool:text等, 只在Design視圖能看見, 運行后不可見 Link

Fragment

ViewPager打開第 1 個fragment時會打開第 2 個 fragment 并執(zhí)行第 2 個的生命周期從 onCreateViewonResume(點擊第 2 個時會打開第 3 個的...), 為避免在 onCreateView 里進行太多操作, 我們考慮只在 fragment可見 && 第一次加載 時 進行數(shù)據(jù)加載

  • Fragment 搭配 ViewPager 時會多在生命周期奪走一個方法setUserVisibleHint(), 我們可手動調(diào)用getUserVisibleHint看回調(diào)
  • setUserVisibleHint()用在單個 Fragment 時不會被調(diào)用
  • setUserVisibleHint() 調(diào)用在onAttach之前

如何懶加載(兩種)

  1. Fragment.setUserVisibleHint
  2. ViewPager.addOnPageChangeListener里的onPageSelected(int position)

第一種.setUserVisibleHint

我們需要做的判斷

  • fragment可見, 我們可以根據(jù)setUserVisibleHint時判斷isVisibleToUser
  • 是否加載過View, 通過自己創(chuàng)建標識位
  • 是否加載過數(shù)據(jù), 通過自己創(chuàng)建標識位

寫法

  • 先寫 BaseFragment
public abstract class BaseFragment extends Fragment {

    protected Activity mActivity;
    protected View mRootView;
    private Unbinder unbinder;

    /**
     * 說明:在此處保存全局的Context
     *
     * @param context 上下文
     */
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mActivity = (Activity) context;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mRootView = inflater.inflate(getLayoutId(), container, false);
        unbinder = ButterKnife.bind(this, mRootView);
        init();
        return mRootView;
    }

    /**
     * @return 返回該Fragment的layout id
     */
    protected abstract int getLayoutId();

    /**
     * 說明:創(chuàng)建視圖時的初始化操作均寫在該方法
     */
    protected abstract void init();

    /**
     * 獲取控件對象
     *
     * @param id 控件id
     * @return 控件對象
     */
    public View findViewById(int id) {
        if (getContentView() != null) {
            return getContentView().findViewById(id);
        } else {
            return null;
        }
    }

    /**
     * 說明:返回當前View
     *
     * @return view
     */
    protected View getContentView() {
        return mRootView;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        unbinder.unbind();
    }

}
  • 再寫 lazyFragment
/**
 * 懶加載Fragment
 *
 * 可以加載數(shù)據(jù)的條件:
 * 1.視圖已經(jīng)初始化
 * 2.視圖對用戶可見
 */

public abstract class LazyLoadFragment extends BaseFragment {

    public boolean isInit = false;//視圖是否已經(jīng)初始化
    public boolean isLoad = false;//視圖是否已經(jīng)加載過

    /**
     * 初始化
     */
    @Override
    protected void init() {
        isInit = true;
        isCanLoadData();
    }

    /**
     * 判斷是否可以加載數(shù)據(jù), 如果可以便進行數(shù)據(jù)的加載
     */
    private void isCanLoadData() {
        if (!isInit) {//未初始化
            return;
        }

        if (getUserVisibleHint()) {//用戶可見
            lazyLoad();
            isLoad = true;
        } else {
            if (isLoad) {//用戶不可見, 且已經(jīng)加載過
                stopLoad();
            }
        }
    }

    /**
     * 當視圖初始化并且對可見時加載數(shù)據(jù)
     */
    public abstract void lazyLoad();

    /**
     * 當該視圖對用戶不可見并且已經(jīng)加載過數(shù)據(jù)的時候, 如果需要在切換到其他頁面時停止加載數(shù)據(jù), 通過覆寫此方法實現(xiàn)
     */
    public void stopLoad() {
    }

    /**
     * 說明:當前視圖可見性發(fā)生變化時調(diào)用該方法
     *
     * @param isVisibleToUser 當前視圖是否可見
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        isCanLoadData();
    }

    /**
     * 視圖銷毀時將Fragment是否初始化的狀態(tài)變?yōu)閒alse
     */
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        isInit = false;
        isLoad = false;
    }

}

第二種, 通過onPageSelected判斷

缺點: 第 1 頁第 1 次進入不調(diào)用 onPageSelected

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

    }

    @Override
    public void onPageSelected(int position) {
       // TODO
       fragment.initData();
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
});

作者:最最最最醉人
鏈接:http://www.reibang.com/p/843088e1ad40
來源:簡書
著作權(quán)歸作者所有毡泻。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán), 非商業(yè)轉(zhuǎn)載請注明出處漾橙。

PagerAdapter

分為三種

(1)PagerAdapter 數(shù)據(jù)源:List<View> 適合輪播圖,圖片查看等
 (2)FragmentPagerAdapter 數(shù)據(jù)源:List<Fragment>
 (3)FragmentStatePagerAdapter 數(shù)據(jù)源:List<Fragment>

FragmentPagerAdapter 和 FragmentStatePagerAdapter區(qū)別

  • FragmentPagerAdapter: fragmentTransaction()調(diào)用depatch(), 生命周期走到onDestroyView(), 適合主界面, 頁面少, 只回收view, 數(shù)據(jù)保留
  • FragmentStatePagerAdapter: fragmentTransaction()調(diào)用remove(), 生命周期走到onDetach(), 適合多頁面, 數(shù)據(jù)多, view和data都回收, 但也會調(diào)onSaveInstance方法, 可存在bundle.

使用FragmentStatePagerAdapter打開多個Fragment:

  1. 第一次進入頁面

     0: onAttach
     0: onCreate
     1: onAttach
     1: onCreate
     0: onCreateView
     0: onViewCreated
     0: onActivityCreated
     0: onStart
     0: onResume
     1: onCreateView
     1: onViewCreated
     1: onActivityCreated
     1: onStart
     1: onResume
    
  2. 從第0頁到第1頁

    2: onAttach
    2: onCreate
    2: onCreateView
    2: onViewCreated
    2: onStart
    2: onResume
    
  3. 從第1頁到第2頁, 會把第0頁殺到 onDetach

    3: onAttach
    3: onCreate
    0: onPause
    0: onStop
    0: onDestroyView
    0: onDestroy
    0: onDetach
    3: onCreateView
    3: onViewCreated
    3: onActivityCreated
    3: onStart
    3: onResume
    
  4. 從第1頁到第0頁

    2: onPause
    2: onStop
    2: onDestroyView
    2: onDestroy
    2: onDetach
    
  5. FragmentPagerAdapter則是讓不可見的頁面其生命周期走到onDestroyView

    3: onAttach
    3: onCreate
    0: onPause
    0: onStop
    0: onDestroyView
    3: onCreateView
    3: onViewCreated
    3: onActivityCreated
    3: onStart
    3: onResume
    

adapter到底是外部還是內(nèi)部的

  • adapter別持有Activity的context, 否則可能內(nèi)存泄漏OOM
  • 盡量 內(nèi)部, 按照谷歌的示例

tab關(guān)聯(lián)viewpager

  • tabLayout.setupWithViewPager(viewpager); 傳入viewpager

  • setupWithViewPager方法會在populateFromPagerAdapter清空tab, 再調(diào)用getPageTitle, 所以正確設(shè)置tab的方式是getPageTitle里拼CharSequence Link

    //...省略一部分代碼
    //處理從方法中傳入的viewpager
    if (viewPager != null) {
        mViewPager = viewPager;
    
        //①以下這部分代碼是處理一些滑動的關(guān)聯(lián)關(guān)系忆家。例如滑動監(jiān)聽
        // Add our custom OnPageChangeListener to the ViewPager
        if (mPageChangeListener == null) {
            mPageChangeListener = new TabLayoutOnPageChangeListener(this);
        }
        mPageChangeListener.reset();
        viewPager.addOnPageChangeListener(mPageChangeListener);
        // Now we'll add a tab selected listener to set ViewPager's current item
        mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
        addOnTabSelectedListener(mCurrentVpSelectedListener);
    
        //②以下這部分代碼就可以解釋為什么我們的tab設(shè)置的內(nèi)容會不見了
        final PagerAdapter adapter = viewPager.getAdapter();
        if (adapter != null) {
            // Now we'll populate ourselves from the pager adapter, adding an observer if
            // autoRefresh is enabled
            //將外部設(shè)置給viewpager的adapter取出來通過setPagerAdapter重新設(shè)置
            setPagerAdapter(adapter, autoRefresh);
        }
        ...
        }
        ...
    }
    void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) {
        if (mPagerAdapter != null && mPagerAdapterObserver != null) {
            // If we already have a PagerAdapter, unregister our observer
            mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver);
        }
        //注冊observer
        mPagerAdapter = adapter;
        if (addObserver && adapter != null) {
            // Register our observer on the new adapter
            if (mPagerAdapterObserver == null) {
                mPagerAdapterObserver = new PagerAdapterObserver();
            }
            adapter.registerDataSetObserver(mPagerAdapterObserver);
        }
        // Finally make sure we reflect the new adapter
        //關(guān)鍵代碼
        populateFromPagerAdapter();
    }
    
    void populateFromPagerAdapter() {
        //移除所有的tab
        //我們之前設(shè)置的tab就是在這里被移除的
        removeAllTabs();
        if (mPagerAdapter != null) {
            final int adapterCount = mPagerAdapter.getCount();
            for (int i = 0; i < adapterCount; i++) {
               //重新添加tab, 而setText得內(nèi)容是在PagerAdapter中g(shù)etPageTitle獲取的, 因此問題的答案就已經(jīng)很明顯了勋磕。 addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false);
            }
            // Make sure we reflect the currently set ViewPager item
            if (mViewPager != null && adapterCount > 0) {
                final int curItem = mViewPager.getCurrentItem();
                if (curItem != getSelectedTabPosition() && curItem < getTabCount()) {
                    selectTab(getTabAt(curItem));
                }
            }
        }
    }
    
    作者:liaoweijian
    鏈接:http://www.reibang.com/p/d83f10c1a765
    來源:簡書
    著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán), 非商業(yè)轉(zhuǎn)載請注明出處。
    

tab添加標題

  • adapter的getPageTitle(int position)返回每個pager對應(yīng)的標題名

tab添加圖片

  • adapter的getPageTitle(int position) 的 CharSequence 看不見圖片;
    解決: 在 style 里把 textAllCaps 置為 false
    <style name="HomeTabLayout" parent = "Widget.Design.TabLayout">
         <item name="tabTextAppearance">@style/HomeTabTextAppearance</item>
    
     </style>
    
     <style name="HomeTabTextAppearance" parent="TextAppearance.Design.Tab">
         <!--顯示tab圖標-->
         <item name="textAllCaps">false</item>
    
     </style>
    
    style可以設(shè)置在styles里, 也可以單獨在xml里添加style

自定義tabItem

在activity中添加, 防止將context傳給外部Adapter, setupWithViewPager先執(zhí)行再for循環(huán)添加view, 簡單的按鈕切換用selector

@Override
protected void onCreate(Bundle savedInstanceState) {
    tabLayout.setupWithViewPager(viewpager);
    for (int i = 0; i < tabLayout.getTabCount(); i++){
        tabLayout.getTabAt(i).setCustomView(getTabView(i));
    }
}

public View getTabView(int position){
   View view = LayoutInflater.from(this).inflate(R.layout.tab_home_bottom, null);
   ImageView ivHomeTab = (ImageView) view.findViewById(R.id.iv_home_tab);
   ivHomeTab.setImageResource(tabIcons[position]);
   return view;
}

setTabMode

  • TabLayout.MODE_SCROLLABLE 跟著viewpager滾動
  • TabLayout.MODE_FIXED 固定的Tab, tabGravity.GRAVITY_FILL需要和 TabLayout.MODE_FIXED 一起使用才有效

下劃線Indicator

  • style里通過tabIndicatorColor設(shè)置顏色, tabIndicatorHeight設(shè)置寬高

處理橫豎切換(待驗證)

@Override
   public void onSaveInstanceState(Bundle outState) {
       super.onSaveInstanceState(outState);
       outState.putInt(POSITION,tabLayout.getSelectedTabPosition());
   }

   @Override
   protected void onRestoreInstanceState(Bundle savedInstanceState) {
       super.onRestoreInstanceState(savedInstanceState);
       viewPager.setCurrentItem(savedInstanceState.getInt(POSITION));
   }

ViewPager

保存多少個Pager 用 ViewPager.setOffscreenPageLimit(num)

  • 保留 2 num + 1 個頁面不被銷毀, 即設(shè)置1時保留左右兩邊的
  • 默認是1, 除非修改源碼

Viewpager + PagerAdapter + TabLayout

ViewPager viewpager = (ViewPager) findViewById(R.id.viewpager);
        viewpager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int position) {
                Fragment fragment = null;
                switch (position) {
                    case 0:
                        fragment = new FragmentZero();
                        break;
                    case 1:
                        fragment = new FragmentOne();
                        break;
                    case 2:
                        fragment = new FragmentTwo();
                        break;
                    case 3:
                        fragment = new FragmentThree();
                        break;
                    case 4:
                        fragment = new FragmentFour();
                        break;
                    case 5:
                        fragment = new FragmentFive();
                        break;

                }
                return fragment;

            }

            @Override
            public int getCount() {
                return 6;
            }

            @Override
            public CharSequence getPageTitle(int position) {

                switch (position){
                    case 0:
                        return "0";
                    case 1:
                        return "1";
                    case 2:
                        return "2";
                    case 3:
                        return "3";
                    case 4:
                        return "4";
                    default:
                        return "0";

                }
            }
        });

TabLayout tabLayout = (TabLayout) findViewById(R.id.tablayout);
tabLayout.setupWithViewPager(viewpager);

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市炒刁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌誊稚,老刑警劉巖翔始,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罗心,死亡現(xiàn)場離奇詭異,居然都是意外死亡城瞎,警方通過查閱死者的電腦和手機渤闷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脖镀,“玉大人飒箭,你說我怎么就攤上這事⊙鸦遥” “怎么了弦蹂?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長强窖。 經(jīng)常有香客問我凸椿,道長,這世上最難降的妖魔是什么翅溺? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任脑漫,我火速辦了婚禮,結(jié)果婚禮上咙崎,老公的妹妹穿的比我還像新娘优幸。我一直安慰自己,他們只是感情好褪猛,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布网杆。 她就那樣靜靜地躺著,像睡著了一般伊滋。 火紅的嫁衣襯著肌膚如雪跛璧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天新啼,我揣著相機與錄音,去河邊找鬼刹碾。 笑死燥撞,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的迷帜。 我是一名探鬼主播物舒,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼戏锹!你這毒婦竟也來了冠胯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤锦针,失蹤者是張志新(化名)和其女友劉穎荠察,沒想到半個月后置蜀,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡悉盆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年盯荤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焕盟。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡秋秤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出脚翘,到底是詐尸還是另有隱情灼卢,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布来农,位于F島的核電站鞋真,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏备图。R本人自食惡果不足惜灿巧,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望揽涮。 院中可真熱鬧抠藕,春花似錦、人聲如沸蒋困。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雪标。三九已至零院,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間村刨,已是汗流浹背告抄。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嵌牺,地道東北人打洼。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像逆粹,于是被迫代替她去往敵國和親募疮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容