TabLayout+ViewPager的使用

標籤(空格分隔): 安卓UIjava


1、TabLayout的基本使用方式

TabLayout這個控件展現的效果很簡單,就是一個水平的佈局用來展現不一樣的Tab,每一個Tab對應的View通常會結合其餘控件一塊兒來使用;android

一、TabLayout經常使用方法

TabLayout經常使用的方法就是新增Tab和Tab切換監聽: addTab(@NonNull Tab tab)同類的方法:Add a tab to this layout. newTab():Create and return a new {@link Tab} removeTab(Tab tab):Remove a tab from the layout. setTabMode(@Mode int mode) :Set the behavior mode for the Tabs in this layout. setSelectedTabIndicatorColor(@ColorInt int color):Sets the tab indicator's color for the currently selected tab. setSelectedTabIndicatorHeight(int height):Sets the tab indicator's height for the currently selected tab. setScrollPosition(int position, float positionOffset, boolean updateSelectedText):Set the scroll position of the tabs. addOnTabSelectedListener(@NonNull OnTabSelectedListener listener):Add a {@link TabLayout.OnTabSelectedListener} that will be invoked when tab selection changes. TabLayout還提供了其餘比較多的UI定製方法,能夠直接參考源碼;app

二、TabLayout+ViewPager使用

TabLayout控件只是提供了展現不一樣Tab的佈局,通常場景下,須要根據切換Tab,展現其餘效果,使用比較多的就是TabLayout+ViewPager;關於ViewPager的使用見下文ViewPager介紹,目前只介紹是TabLayout+ViewPager如何結合使用; TabLayout提供了以下方法: setupWithViewPager(@Nullable ViewPager viewPager)、setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh)、setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh, boolean implicitSetup) ,這幾個方法雖然參數不一樣,可是做用是差很少的,Google官方解釋:less

/** * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}. * * <p>This method will link the given ViewPager and this TabLayout together so that * changes in one are automatically reflected in the other. This includes scroll state changes * and clicks. The tabs displayed in this layout will be populated * from the ViewPager adapter's page titles.</p> * * <p>If {@code autoRefresh} is {@code true}, any changes in the {@link PagerAdapter} will * trigger this layout to re-populate itself from the adapter's titles.</p> * * <p>If the given ViewPager is non-null, it needs to already have a * {@link PagerAdapter} set.</p> 複製代碼

從上面解釋能夠看到,經過上述的方法,就能夠將TabLayout和ViewPager關聯起來,任何一個控件的改變都會影響另外一個;ide

2、ViewPager的使用方法

ViewPager這個控件設置的初衷就是實現相似廣告左右滑動的時候切換頁面的效果,所以這個控件也相似於ListView同樣,須要經過Adapater來指定Page的數據源;Google控件無處不在用到適配器模式,想一想適配器模式的應用效果 官方文檔介紹以下:函數

/** * Layout manager that allows the user to flip left and right * through pages of data. You supply an implementation of a * {@link PagerAdapter} to generate the pages that the view shows. * * <p>ViewPager is most often used in conjunction with {@link android.app.Fragment}, * which is a convenient way to supply and manage the lifecycle of each page. * There are standard adapters implemented for using fragments with the ViewPager, * which cover the most common use cases. These are * {@link android.support.v4.app.FragmentPagerAdapter} and * {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these * classes have simple code showing how to build a full user interface * with them. */
public class ViewPager extends ViewGroup {
    ......
}
複製代碼

從上面的官方解釋,總結起來有如下幾點:佈局

1.ViewPager類直接繼承了ViewGroup類,全部它是一個容器類,能夠在其中添加其餘的view類。 2.ViewPager類須要一個PagerAdapter適配器類給它提供數據。 3.ViewPager常常和Fragment一塊兒使用,而且提供了專門的FragmentPagerAdapter和FragmentStatePagerAdapter類供Fragment中的ViewPager使用。性能

從上面的表述能夠看到,ViewPager的數據是經過PagerAdapter來提供;動畫

2.一、PagerAdapter的使用

首先將官方文檔中對PagerAdapter的主要介紹截取出來:ui

/** * Base class providing the adapter to populate pages inside of * a {@link ViewPager}. You will most likely want to use a more * specific implementation of this, such as * {@link android.support.v4.app.FragmentPagerAdapter} or * {@link android.support.v4.app.FragmentStatePagerAdapter}. * <p>When you implement a PagerAdapter, you must override the following methods * at minimum:</p> * <ul> * <li>{@link #instantiateItem(ViewGroup, int)}</li> * <li>{@link #destroyItem(ViewGroup, int, Object)}</li> * <li>{@link #getCount()}</li> * <li>{@link #isViewFromObject(View, Object)}</li> * </ul> * <p>PagerAdapter supports data set changes. Data set changes must occur on the * main thread and must end with a call to {@link #notifyDataSetChanged()} similar * to AdapterView adapters derived from {@link android.widget.BaseAdapter}. A data * set change may involve pages being added, removed, or changing position. The * ViewPager will keep the current page active provided the adapter implements * the method {@link #getItemPosition(Object)}.</p> */
public abstract class PagerAdapter {
    ......
}
複製代碼

以上就能夠將PagerAdapter概述爲:

1.大多數狀況下,咱們根據具體狀況實現PagerAdapter而且更加具體的適配器; 2.當你實現一個PagerAdapter時,你至少須要重寫下面的幾個方法:instantiateItem、destroyItem、getCount、isViewFromObject; 3.ViewPager使用回調機制來顯示一個更新步驟,而不是直接使用視圖回收機制。 4.PagerAdapter支持數據集的改變。數據集的改變必須放在主線程中,而且在結束時調用notifyDataSetChanged()方法,這與經過BaseAdapter適配的AdapterView相似。

上面傳達除了使用PagerAdapter最重要的訊息,根據具體的狀況咱們實現符合咱們需求的PagerAdapter,須要重寫四個方法;同時大部分場景下使用已有的FragmentPagerAdapter和FragmentStatePagerAdapter可以知足需求;PagerAdapter能夠和ListView對應的Adapter對比,二者的實質是不同,只不過用於不一樣的控件,展現的UI效果也就不同了;

一、簡單實現PagerAdapter

實現一個最簡單的PagerAdapter代碼以下:

public class AdapterViewpager extends PagerAdapter {
    private List<View> mViewList;

    public AdapterViewpager(List<View> mViewList) {
        this.mViewList = mViewList;
    }

    @Override
    public int getCount() {//必須實現
        return mViewList.size();
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {//必須實現
        return view == object;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {//必須實現,實例化
        container.addView(mViewList.get(position));
        return mViewList.get(position);
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {//必須實現,銷燬
        container.removeView(mViewList.get(position));
    }
}
複製代碼

關注一下四個函數要實現的功能:

/** * Create the page for the given position. The adapter is responsible * for adding the view to the container given here, although it only * must ensure this is done by the time it returns from * {@link #finishUpdate(ViewGroup)}. * @param container The containing View in which the page will be shown. * @param position The page position to be instantiated. * @return Returns an Object representing the new page. This does not * need to be a View, but can be some other container of the page. */
public Object instantiateItem(ViewGroup container, int position) {
    ......
}
複製代碼

instantiateItem最重要的做用就是將某個位置對應的Page添加到container,相似RecyclerView.adpter的onCreateView;

/** * Determines whether a page View is associated with a specific key object * as returned by {@link #instantiateItem(ViewGroup, int)}. This method is * required for a PagerAdapter to function properly. * @param view Page View to check for association with <code>object</code> * @param object Object to check for association with <code>view</code> * @return true if <code>view</code> is associated with the key object <code>object</code> */
public abstract boolean isViewFromObject(View view, Object object);
複製代碼

isViewFromObject就是返回是否某個Page View和instantiateItem返回的對象對應; destroyItem、getCount也參考如上實現很是容易理解;

二、FragmentPagerAdapter

簡單的實現FragmentPagerAdapter代碼以下:

public class AdapterFragment extends FragmentPagerAdapter {
    private List<Fragment> mFragments;

    public AdapterFragment(FragmentManager fm, List<Fragment> mFragments) {
        super(fm);
        this.mFragments = mFragments;
    }

    @Override
    public Fragment getItem(int position) {//必須實現
        return mFragments.get(position);
    }

    @Override
    public int getCount() {//必須實現
        return mFragments.size();
    }

    @Override
    public CharSequence getPageTitle(int position) {//選擇性實現
        return mFragments.get(position).getClass().getSimpleName();
    }
}
複製代碼

要使用FragmentPagerAdapter很簡單,做如上實現便可,FragmentPagerAdapter是PagerAdapter的子類,看一下官方文檔:

/** * <p>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 * {@link FragmentStatePagerAdapter}. * <p>Subclasses only need to implement {@link #getItem(int)} * and {@link #getCount()} to have a working adapter. */
public abstract class FragmentPagerAdapter extends PagerAdapter {
    ......
}
複製代碼

從上面主要的簡介能夠看到,FragmentPagerAdapter對應的全部pager fragments會一直被持有放在內存,及時對應的Fragment不可見的時候;所以,FragmentPagerAdapter的使用必定要注意,若是FragmentPagerAdapter對應的pages數量比較大,不要使用FragmentPagerAdapter,可能引起內存問題; FragmentPagerAdapter最重要就是實現:

/** * Return the Fragment associated with a specified position. */
public abstract Fragment getItem(int position);
複製代碼

三、FragmentStatePagerAdapter

要實現FragmentStatePagerAdapter和實現FragmentPagerAdapter其實是同樣的,可是正如前面說到的內存問題,FragmentStatePagerAdapter內部實現了對內存的處理,具體代碼參考: FragmentStatePagerAdapter的destroyItem:

@Override
public void destroyItem:(ViewGroup container, int position, Object object) {
    Fragment fragment = (Fragment) object;

    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
            + " v=" + ((Fragment)object).getView());
    while (mSavedState.size() <= position) {
        mSavedState.add(null);
    }
    mSavedState.set(position, fragment.isAdded()
            ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
    mFragments.set(position, null);

    mCurTransaction.remove(fragment);
}
複製代碼

FragmentPagerAdapter的destroyItem:

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
            + " v=" + ((Fragment)object).getView());
    mCurTransaction.detach((Fragment)object);
}
複製代碼

差異就在mCurTransaction.remove(fragment)mCurTransaction.detach((Fragment)object),參考FragmentTransaction對應的方法就能夠理解;更多具體的實現能夠參考源碼實現;從上面的對比能夠看到,FragmentStatePagerAdapter用於當pages比較多或者Fragment內存佔用比較多的狀況,而FragmentPagerAdapter用於佔用內存不大,可是使用頻率很高的狀況,性能會更高;所以要根據具體狀況使用; 那FragmentPagerAdapter佔用的pages內存何時會釋放呢?參考FragmentTransaction和源碼須要進一步分析;

2.二、ViewPager支持更多的展現

Android Design的控件都支持更多的UI展現,ViewPager的效果是左右滑動實現頁面翻轉,固然也支持過場動畫,具體實現參考ViewPager.PageTransformer接口;

/** * Apply a property transformation to the given page. * * @param page Apply the transformation to this page * @param position Position of page relative to the current front-and-center *position of the pager. 0 is front and center. 1 is one full *page position to the right, and -1 is one page position to the left. */
void transformPage(View page, float position);
複製代碼

一、提高性能的setOffscreenPageLimit

ViewPager提供了一個setOffscreenPageLimit方法,官方解釋以下:

/** * Set the number of pages that should be retained to either side of the * current page in the view hierarchy in an idle state. Pages beyond this * limit will be recreated from the adapter when needed. * * <p>This is offered as an optimization. If you know in advance the number * of pages you will need to support or have lazy-loading mechanisms in place * on your pages, tweaking this setting can have benefits in perceived smoothness * of paging animations and interaction. If you have a small number of pages (3-4) * that you can keep active all at once, less time will be spent in layout for * newly created view subtrees as the user pages back and forth.</p> * * <p>You should keep this limit low, especially if your pages have complex layouts. * This setting defaults to 1.</p> * * @param limit How many pages will be kept offscreen in an idle state. */
public void setOffscreenPageLimit(int limit) {
    if (limit < DEFAULT_OFFSCREEN_PAGES) {
        Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                + DEFAULT_OFFSCREEN_PAGES);
        limit = DEFAULT_OFFSCREEN_PAGES;
    }
    if (limit != mOffscreenPageLimit) {
        mOffscreenPageLimit = limit;
        populate();
    }
}
複製代碼

翻譯過來幾個重點:

1.當設置了限制數量m,當滑動的時候,超過m的page纔會每次都要從新建立; 2.實際上這個函數的做用就是會保存相應數量的pages,由於viewpager可能存在頻繁的左右滑動,所以若是老是重複繪製UI,性能和用戶體驗上可能會有影響; 3.仍是綜合考慮性能和內存的影響,謹慎使用這個方法;

關於這個方法更多的介紹參考**populate(int newCurrentItem)**方法,能夠清楚這個方法最終如何影響ViewPager的使用。

2.三、基於Java範形實現ViewPager

基於Java的範性來實現ViewPager: PagerAdapter:

public class QuickPageAdapter<T extends View> extends PagerAdapter {
  private List<T> mList;

  public QuickPageAdapter(List<T> mList) {
      this.mList = mList;
  }

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

  @Override
  public boolean isViewFromObject(View view, Object object) {
      return object == view;
  }

  @Override
  public Object instantiateItem(ViewGroup container, int position) {
      container.addView(mList.get(position));
      return mList.get(position);
  }

  @Override
  public void destroyItem(ViewGroup container, int position, Object object) {
      container.removeView(mList.get(position));
  }
複製代碼

FragmentPagerAdapter:

public class QuickFragmentPageAdapter<T extends Fragment> extends FragmentPagerAdapter {
  private List<T> mList;
  private String[] mStrings;

  /** * @param fm * @param list * @param titles PageTitles */
  public QuickFragmentPageAdapter(FragmentManager fm, List<T> list, String[] titles) {
      super(fm);
      mList = list;
      mStrings = titles;
  }

  @Override
  public Fragment getItem(int position) {
      return mList.get(position);
  }

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

  @Override
  public CharSequence getPageTitle(int position) {
      return mStrings == null ? super.getPageTitle(position) : mStrings[position];
  }
}
複製代碼
相關文章
相關標籤/搜索