ViewPager最佳實踐

鋸齒流沙發表於2017-12-26

現在網上關於ViewPager的文章已經琳琅滿目了,很多都是關於ViewPager和Fragment的,大概大家都覺得他們才是最佳拍檔。 但是本文是關於:ViewPager+View+TabLayout+SwipeRefreshLayout的最佳實踐,而且還加入了ViewPager切換的動畫效果。

大家如果想看專案實踐執行的視訊,可以切換到ViewPager最佳實踐

下面我來帶大家實踐一下,首先看下需要的檔案。

專案檔案
上面就是我們需要的專案檔案。

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.lwj.viewpager.ViewPagerActivity">
    <android.support.design.widget.TabLayout
        android:id="@+id/toolbar_tab"
        android:layout_width="match_parent"
        android:layout_height="44dp"
        android:layout_gravity="bottom"
        android:background="#ffffff"
        android:fillViewport="true"
        android:paddingLeft="5dp"
        android:visibility="invisible"
        app:tabIndicatorColor="#96a8d0"
        app:tabIndicatorHeight="3.0dp"
        app:tabPaddingEnd="0dp"
        app:tabPaddingStart="0dp"
        app:tabPaddingTop="0dp"
        app:tabSelectedTextColor="#96a8d0"
        app:tabTextColor="#666666">
    </android.support.design.widget.TabLayout>

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

</LinearLayout>
複製程式碼

主介面的佈局很簡單,一個TabLayout,一個ViewPager。你可以在這裡設定TabLayout的顯示樣式。 需要說明一點,TabLayout是在android.support.design包下的,所以專案中要新增這個包,在Dependencies中新增就可以了。

新增包

public class ViewPagerActivity extends BaseActivity {

    private ViewPager mViewPager;
    private TabLayout mTabLayout;
    private TabItemView mSelectBarItem;//記錄選擇中的tablayout的item
    private List<View> mViews = new ArrayList<>();//viewpager每個頁面的view
    private MyViewPagerAdapter mViewPagerAdapter;
    private String[] mIsLoads;//0:未載入,1:已載入
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_pager);
        initView();
        getPageTitle();
    }

    /**
     * 初始化View
     */
    private void initView() {
        mViewPager = (ViewPager)this.findViewById(R.id.my_viewpager);
        mTabLayout = (TabLayout)this.findViewById(R.id.toolbar_tab);
        mTabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
        mViewPagerAdapter = new MyViewPagerAdapter(mViews);
        mViewPager.setAdapter(mViewPagerAdapter);
        //設定viewpager的滑動監聽器
        mViewPager.addOnPageChangeListener(mPagerChangeListener);
        //設定viewpager切換動畫
        mViewPager.setPageTransformer(true, new ZoomOutPageTransformer());
    }

    /**
     * 獲取頁面的title
     */
    public void getPageTitle() {
        List<String> titles = new ArrayList<>();
        for (int i = 0; i < 15; i++) {
            String title = "Title "+i;
            titles.add(title);
        }
        initViewPages(titles);
    }

    /**
     * 根據多少個title建立多少個view
     * @param titles
     * 這裡必須建立完成各個頁面的view並且改變viewpager才能初始化tablayout的item,否則出錯
     */
    private void initViewPages(List<String> titles) {
        if(titles != null && titles.size() > 0)
        {
            mIsLoads = new String[titles.size()];
            for(int i = 0; i < titles.size(); i++)
            {
                ListPage page = new ListPage(ViewPagerActivity.this);
                mIsLoads[i] = "0";
                mViews.add(page);
            }
            mViewPagerAdapter.notifyDataSetChanged();
            addData(0);
        }
        initTab(titles);

    }

    /**
     * 初始化各個頁面的資料
     * @param position
     */
    private void addData(int position) {
        if(mIsLoads != null && mIsLoads[position].equals("0"))
        {
            mIsLoads[position] = "1";
            if(mViews != null && mViews.size() > 0)
            {
                if (mViews.get(position) != null){
                    ((ListPage)mViews.get(position)).firstLoadData();
                }
            }
        }
    }

    /**
     * 初始化tablayout的item
     * 有多少個title就建立多少個item
     * @param titles
     */
    private void initTab(List<String> titles)
    {
        mTabLayout.setVisibility(View.VISIBLE);
        ColorStateList color = mTabLayout.getTabTextColors();
        if(titles != null && titles.size() > 0)
        {
            mTabLayout.setupWithViewPager(mViewPager);
            for(int i = 0; i < titles.size(); i++)
            {
                String title = titles.get(i);
                TabLayout.Tab item = mTabLayout.getTabAt(i);
                TabItemView tabItem = new TabItemView(ViewPagerActivity.this);
                tabItem.setText(title);
                if(i == 0)
                {
                    mSelectBarItem = tabItem;
                    tabItem.setTextColor(0xff809fd8);
                }
                else
                {
                    tabItem.setTextColor(color);
                }
                item.setCustomView(tabItem);
            }
        }
    }

    /**
     * ViewPager監聽器
     */
    private ViewPager.OnPageChangeListener mPagerChangeListener = new ViewPager.OnPageChangeListener()
    {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
        {

        }

        @Override
        public void onPageSelected(final int position)
        {
            //改變tablayout選中item的顏色
            mSelectBarItem.setTextColor(mTabLayout.getTabTextColors());
            //滑動到頁面才能初始化資料,不需要預載入
            addData(position);
        }

        @Override
        public void onPageScrollStateChanged(int state)
        {

        }
    };
}
複製程式碼

這是activity,主要做的操作就是初始化ViewPager、TabLayout和資料,還有viewpager的設定。具體的程式碼已經註釋清楚了。

public class TabItemView extends FrameLayout {
    private TextView mText;

    public TabItemView(@NonNull Context context) {
        super(context);
        init(context);
    }

    public TabItemView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public TabItemView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        View v = LayoutInflater.from(context).inflate(R.layout.tab_text, null);
        addView(v);
        mText = (TextView) v.findViewById(R.id.tv_toolbar);
    }

    /**
     * 設定選中的顏色
     * @param color
     */
    public void setTextColor(ColorStateList color)
    {
        mText.setTextColor(color);
    }

    /**
     * 設定選中的顏色
     * @param color
     */
    public void setTextColor(int color)
    {
        mText.setTextColor(color);
    }

    /**
     * 設定item的文字
     * @param text
     */
    public void setText(String text)
    {
        mText.setText(text);
    }
}
複製程式碼

設定TabLayout的item,你可以在裡面設定item的樣式。

<?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:orientation="vertical">
    <TextView
        android:id="@+id/tv_toolbar"
        android:gravity="center"
        android:paddingLeft="20dp"
        android:paddingTop="2dp"
        android:paddingRight="20dp"
        android:paddingBottom="2dp"
        android:textColor="@color/text_color_selector"
        android:textSize="15dp"
        android:layout_centerInParent="true"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"/>

</LinearLayout>
複製程式碼

這是TabLayout的item佈局檔案,你可以在這裡編輯你想要的佈局。

public class MyViewPagerAdapter extends PagerAdapter {

    private List<View> mViews = null;

    public MyViewPagerAdapter(List<View> datas)
    {
        this.mViews = datas;
    }

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

    /**
     * 判斷是否使用快取, 如果true, 使用快取
     * view 就是拖動的物件
     * object 進來的物件
     */
    @Override
    public boolean isViewFromObject(View view, Object object)
    {
        return view == object;
    }

    /**
     * 銷燬物件
     * position  就是被銷燬的物件的索引
     */
    @Override
    public void destroyItem(ViewGroup container, int position, Object object)
    {
        container.removeView(mViews.get(position));

    }

    /**
     * 載入item
     * position 被載入的item的索引
     */
    @Override
    public Object instantiateItem(ViewGroup container, int position)
    {
        container.addView(mViews.get(position));
        return mViews.get(position);
    }
}
複製程式碼

這是viewpager的adapter,具體的方法使用在檔案中已經註釋說明了。


接下來我們看viewpager頁面所需要的view部分。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ListView
            android:id="@+id/list"
            android:divider="@null"
            android:dividerHeight="0dp"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

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

</LinearLayout>
複製程式碼

佈局真的很簡單,一個是google的下拉重新整理,一個是用來顯示列表的ListView。

public class ListPage extends FrameLayout {
    private LinearLayout layout;
    private SwipeRefreshLayout mSwipeRefreshLayout;
    private ListView mListView;
    private Handler mH = new Handler();
    private ListAdapter mAdapter;
    private List<String> datas = new ArrayList<>();
    private String[] lists = new String[]{"好聽麼","你是誰",
            "你從哪裡來","要到哪裡去","android開發","javaweb開發","ios開發","php開發","哈哈",
            "聽風","下雨了","嘩啦啦","風呼呼","牛魔王","孫悟空","齊天大聖","豬八戒","無能為力","沙僧","唐僧"};


    public ListPage(@NonNull Context context) {
        super(context);
        initialize(context);
    }

    public ListPage(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initialize(context);
    }

    public ListPage(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initialize(context);
    }

    private void initialize(Context context) {
        initView(context);
        initListener();

    }

    /**
     * 初始化view
     * @param context
     */
    private void initView(Context context) {
        int MP = LayoutParams.MATCH_PARENT;
        LayoutParams fl = new LayoutParams(MP,MP);
        layout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.list_layout,null);
        addView(layout,fl);

        mSwipeRefreshLayout = (SwipeRefreshLayout)layout.findViewById(R.id.swipe);
        mListView = (ListView)layout.findViewById(R.id.list);

        mAdapter = new ListAdapter(context,datas);
        mListView.setAdapter(mAdapter);
    }

    /**
     * 第一次進來載入資料
     */
    public void firstLoadData(){
        setData(getData());
    }

    /**
     * 獲取資料
     */
    public List<String> getData(){
        Random r = new Random();
        List<String> mDatas = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            String data = lists[r.nextInt(20)];
            mDatas.add(data);
        }
        return mDatas;
    }

    private void initListener() {
        /**
         * 下拉重新整理
         */
        mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {

            @Override
            public void onRefresh() {
                getNetData();
            }
        });
    }

    /**
     * 下拉重新整理獲取資料
     */
    public void getNetData() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                final List<String> strs = getData();
                mSwipeRefreshLayout.isRefreshing();
//                mSwipeRefreshLayout.setEnabled(false);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mH.post(new Runnable() {
                    @Override
                    public void run() {
                        setData(strs);
                    }
                });
            }
        }).start();
    }

    /**
     * 設定資料
     * @param data
     */
    public void setData(List<String> data) {
        mSwipeRefreshLayout.setRefreshing(false);
        if (data != null && data.size() > 0){
            if (datas != null && datas.size() > 0){
                datas.clear();
            }
            datas.addAll(data);
            mAdapter.notifyDataSetChanged();
        }
    }

}
複製程式碼

這裡主要是實現ListView列表和SwipeRefreshLayout的下拉重新整理。

public class ListAdapter extends BaseAdapter {
    private List<String> datas;
    private Context mContext;

    public ListAdapter(Context mContext,List<String> datas) {
        this.datas = datas;
        this.mContext = mContext;
    }

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

    @Override
    public Object getItem(int i) {
        return datas == null ? null : datas.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup viewGroup) {
        if (convertView == null && !(convertView instanceof ItemLayout)){
            convertView = new ItemLayout(mContext);
        }
        ((ItemLayout)convertView).setData(datas.get(position));
        return convertView;
    }

    public static class ItemLayout extends LinearLayout{
        public String tagStr;
        private Button mButton;

        public ItemLayout(Context context) {
            super(context);
            initView(context);
        }

        private void initView(Context context) {
            int MP = LinearLayout.LayoutParams.MATCH_PARENT;
            int WC = LinearLayout.LayoutParams.WRAP_CONTENT;
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(MP, WC);
            LinearLayout layout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.list_item_layout,null);
            addView(layout,params);
            mButton = (Button)layout.findViewById(R.id.text);
        }
        public void setData(String str){
            if (str == null || str == tagStr){
                return;
            }else{
                tagStr = str;
                mButton.setText(str);
            }
        }

    }
}
複製程式碼

這裡大家應該很熟悉了,這是ListView的adapter,這裡直接顯示列表,沒有做過多的需求。

<?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="wrap_content"
    android:orientation="vertical">
    <Button
        android:id="@+id/text"
        android:layout_margin="5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>
複製程式碼

這是adapter的item。


最後我們看看實現viewpager頁面切換的動畫,這裡用的是google官網上的。

public class ZoomOutPageTransformer implements ViewPager.PageTransformer {

    private static final float MIN_SCALE = 0.9f;
    private static final float MIN_ALPHA = 0.5f;

    @Override
    public void transformPage(View page, float position) {
        int pageWidth = page.getWidth();
        int pageHeight = page.getHeight();

        if (position < -1)
        { // [-Infinity,-1)
            // This page is way off-screen to the left.
            page.setAlpha(0);

        } else if (position <= 1) //a頁滑動至b頁 ; a頁從 0.0 -1 ;b頁從1 ~ 0.0
        { // [-1,1]
            // Modify the default slide transition to shrink the page as well
            float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
            float vertMargin = pageHeight * (1 - scaleFactor) / 2;
            float horzMargin = pageWidth * (1 - scaleFactor) / 2;
            if (position < 0)
            {
                page.setTranslationX(horzMargin - vertMargin / 2);
            } else
            {
                page.setTranslationX(-horzMargin + vertMargin / 2);
            }

            // Scale the page down (between MIN_SCALE and 1)
            page.setScaleX(scaleFactor);
            page.setScaleY(scaleFactor);

            // Fade the page relative to its size.
            page.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE)
                    / (1 - MIN_SCALE) * (1 - MIN_ALPHA));

        } else
        { // (1,+Infinity]
            // This page is way off-screen to the right.
            page.setAlpha(0);
        }
    }
}
複製程式碼

mViewPager.setPageTransformer(true, new ZoomOutPageTransformer());這樣簡簡單單的一句話就可以實現viewpager的頁面切換動畫了。 關於更多的ViewPager切換動畫,大家可以參考鴻洋大神的文章 Android 實現個性的ViewPager切換動畫 實戰PageTransformer(相容Android3.0以下)


最後提供一個簡單封裝的ViewPager,可以設定不允許ViewPager左右滑動

public class CustomViewPager extends ViewPager {

    //控制Viewpager是否可以左右滑動,預設不能滑動
    private boolean isCanScroll = true;

    public CustomViewPager(Context context) {
        super(context);
    }

    public CustomViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setScanScroll(boolean isCanScroll) {
        this.isCanScroll = isCanScroll;
    }

    @Override
    public void scrollTo(int x, int y) {
        super.scrollTo(x, y);
    }

    @Override
    public boolean onTouchEvent(MotionEvent arg0) {
        // TODO Auto-generated method stub
        if (isCanScroll) {
            return false;
        } else {
            return super.onTouchEvent(arg0);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (isCanScroll) {
            return false;
        } else {
            return super.onInterceptTouchEvent(ev);
        }
    }

    @Override
    public void setCurrentItem(int item, boolean smoothScroll) {
        // TODO Auto-generated method stub
        super.setCurrentItem(item, smoothScroll);
    }

    @Override
    public void setCurrentItem(int item) {
        // TODO Auto-generated method stub
        super.setCurrentItem(item);
    }
}
複製程式碼