跳转至

Android ViewPager总结

设置缓存页数

vievPager.setOffscreenPageLimit(1)

最小值为1,不可为0; 想不预加载下一页数据,参见 懒加载 部分。

设置禁止滑动

import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;

/**
* Created by ykh on 2016/7/11.
*/
public class CustomViewPager extends ViewPager {
    private boolean isCanScroll = true;
    public CustomViewPager(Context context) {
        super(context);
    }
    public CustomViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public void setCanScroll(boolean isCanScroll) {
        this.isCanScroll = isCanScroll;
    }

    // 只重写该方法无法禁用
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return this.isCanScroll && super.onTouchEvent(event);
    }

    // 还需要重写本方法
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return this.isCanScroll && super.onInterceptTouchEvent(event);
    }
}

用法:

//允许viewpager左右滑动
viewPager.setCanScroll(true);

//禁止viewpager左右滑动
viewPager.setCanScroll(false);

不重写onInterceptTouchEvent方法会导致禁止滑动时用户可以慢慢的一点点的划出下一页的一多半页面的情况。

方法具体意义见《Android开发艺术探讨》P141。

懒加载

若不控制预加载:

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    initView();
    super.onViewCreated(view, savedInstanceState);
}

若控制预加载: package ykh.viewpagerdemo;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.app.Fragment;
import android.widget.Toast;

/**
 * Created by ykh on 2018/6/09.
 */
public abstract class BaseFragment extends Fragment {
    /**
     * Fragment当前状态是否可见
     */
    protected boolean isVisible;

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);

        if (getUserVisibleHint()) {
            isVisible = true;
            onVisible();
        } else {
            isVisible = false;
            onInvisible();
        }
    }

    /**
     * 可见
     */
    protected void onVisible() {
        onViewLoad();
    }

    /**
     * 不可见
     */
    protected void onInvisible() {
        onViewLost();
    }

    /**
     * 延迟加载
     * 子类必须重写此方法
     */
    protected abstract void onViewLoad();

    protected abstract void onViewLost();
}

Fragment继承BaseFragment,需要注意的是onViewLoad的调用顺序在onViewCreated之前,也就是说第一次数据加载的时候,可见的页面在onViewCreated方法中initView,不可见的页面在onViewLoad中initView加载数据。实例:

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;


public class ListFragment extends BaseFragment {
    private boolean is_init = false;
    private Activity activity;
    private View layout;

    private String name_str;
    private TextView name;
    private int count = 0;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        activity = this.getActivity();
        layout = activity.getLayoutInflater().inflate(R.layout.fragment_todo_list, container, false);

        Bundle bundle = this.getArguments();
        if (bundle != null) {
            name_str = bundle.getString("name", "");
        }
        return layout;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        if (isVisible) {
            initView();
        }
        super.onViewCreated(view, savedInstanceState);
    }

    public void initView() {
        if (!is_init) {
            Logger.i("initView " + name_str);
            count++;
            name = (TextView) layout.findViewById(R.id.name);
            name.setText(name_str + " Pager Loaded Times:" + count);
            // 懒加载数据的地方
            is_init = true;
        }
    }

    @Override
    protected void onViewLoad() {
        Logger.i("onViewLoad " + name_str + activity);
        if (activity != null) {
            initView();
        }
    }

    @Override
    protected void onViewLost() {
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        is_init = false;
    }
}

FragmentPagerAdapterFragmentStatePagerAdapter的区别

使用 FragmentPagerAdapter 时,ViewPager 中的所有 Fragment 实例常驻内存,当 Fragment 变得不可见时仅仅是视图结构的销毁,即调用了 onDestroyView 方法。由于 FragmentPagerAdapter 内存消耗较大,所以适合少量静态页面的场景。

使用 FragmentStatePagerAdapter 时,当 Fragment 变得不可见,不仅视图层次销毁,实例也被销毁,即调用了onDestroyViewonDestroy 方法,仅仅保存 Fragment 状态。相比而言, FragmentStatePagerAdapter 内存占用较小,所以适合大量动态页面,比如我们常见的新闻列表类应用。

Android Fragment+ViewPager 组合,一些你不可不知的注意事项

替换ViewPager中Fragment无效问题

FragmentPagerAdapter内置缓存导致替换ViewPager中Fragment无效问题

遍历FragmentPagerAdapter源代码可见instantiateItem函数,其根据getItemId(position)函数来得到itemId,再根据根据makeFragmentName(container.getId(), itemId)来获取itemId对应的name,再根据findFragmentByTag(name)来判断当前fragment是否已经存在,已存在则直接使用,不存在则调用getItem(position)函数来重新生成。

@SuppressWarnings("ReferenceEquality")
@Override
public Object instantiateItem(ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }

    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    }
    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }

    return fragment;
}

搜索得到的解决方法

  • 方法一:重写adaptergetItemPosition()方法如下:
public int getItemPosition(Object object) {
    return POSITION_NONE;
}

当调用notifyDataSetChanged()的时候。ViewPagerremove掉全部的view,然后又一次去载入。可行,可是效率低。

详见此处

  • 方法二:暴力删除fragment,方法如下:
List<Fragment> fragments = getSupportFragmentManager(0.getFragments();
for(int i=0;i<fragments.size();i++){
    getSupportFragmentManager().beginTransaction().remove(fragments.get(i));
}
  • 方法三:重写FragmentPagerAdapterinstantiateItem方法。

  • 方法四:重写getItemId如下:

public long getItemId(int position) {
    return registeredFragments.get(position).hashCode();
}

FragmentPagerAdapter里在根据getItemId(int position)来判断当前position里Fragment是否存在,如果存在,则不会创建亦不会更新,那么要让FragmentPagerAdapter的更新生效,那在getItemId(int)里根据数据返回一个唯一的数据ID,当FragmentPagerAdapter更新时,数据ID改变了,那么Fragment就会调用getItem(int)去获取新Fragment,达到更新效果(来源

  • 方法5:为每个Fragment设置tag(推荐)

摘抄如下:

My approach is to use the setTag() method for any instantiated view in the instantiateItem() method. So when you want to change the data or invalidate the view that you need, you can call the findViewWithTag() method on the ViewPager to retrieve the previously instantiated view and modify/use it as you want without having to delete/create a new view each time you want to update some value.

ViewPager PagerAdapter not updating the View

具体实现可见此博客,核心代码如下:

public class FragmentViewPagerAdapter extends FragmentPagerAdapter {
    private FragmentManager mFragmentManager;
    private List<String> mDatas;
    private List<String> tagList = new ArrayList<String>();

    public FragmentViewPagerAdapter(FragmentManager fm, List<String> datas) {
        super(fm);
        this.mFragmentManager = fm;
        this.mDatas = datas;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        tagList.add(makeFragmentName(container.getId(), getItemId(position))); //把tag存起来   
        return super.instantiateItem(container, position);
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
        tagList.remove(makeFragmentName(container.getId(), getItemId(position)));//把tag删掉
    }

    @Override
    public Fragment getItem(int position) {
        String url = mDatas.get(position);
        WebViewFragmentV4 webview = new WebViewFragmentV4(url);//本文測试的Fragment是一个WebViewFragment
        return webview;
    }

    @Override
    public int getCount() {
        if (mDatas == null) {
            return 0;
        } else {
            return mDatas.size();
        }
    }

    public void update(List<String> datas) {
        this.mDatas = datas;
        notifyDataSetChanged();//并不能起到更新Fragment内容的作用。

    }

    public void update(int position) {//这个事真正的更新Fragment的内容
        WebViewFragmentV4 fragment = (WebViewFragmentV4) mFragmentManager.findFragmentByTag(tagList.get(position));
        if (fragment == null) {
            return;
        }
        fragment.update();
    }

    private static String makeFragmentName(int viewId, long id) {
        return "android:switcher:" + viewId + ":" + id;
    }
}

最后使用的解决方案

写博客的时候遇到的具体问题场景为,一个ViewPager中有两个Fragment,需要动态的替换(而不是更新其内容)第二个Fragment为新的Fragment,但是又不愿意重新加载第一个,使用的adapter如下:

private class DynamicPagerAdapter extends FragmentPagerAdapter {
    private ArrayList<Fragment> registeredFragments;
    private List<Integer> hashList;

    private DynamicPagerAdapter(FragmentManager fm) {
        super(fm);
        this.registeredFragments = new ArrayList<>();
        hashList = new ArrayList<>();
    }

    public int getItemPosition(Object object) {
        int index = getIndexByTag(object.hashCode());
        if (index >= 0) {
            hashList.remove(index);
            return POSITION_NONE;
        } else {
            return POSITION_UNCHANGED;
        }
    }

    public long getItemId(int position) {
        return registeredFragments.get(position).hashCode();
    }

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

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

    public void add(Fragment fragment) {
        registeredFragments.add(fragment);
        hashList.add(fragment.hashCode());
        notifyDataSetChanged();
    }

    public void remove(int position) {
        registeredFragments.remove(position);
        notifyDataSetChanged();
    }

    private int getIndexByTag(int hashCode) {
        for (int i = 0; i < hashList.size(); i++) {
            if (hashList.get(i) == hashCode) {
                return i;
            }
        }
        return -1;
    }
}

主要思路:记录所有新增的FragmenthashCode,在getItemPosition时,根据Fragment判断该对象是否已经被记录,若未记录则返回POSITION_NONE让其被重新加载,否则返回POSITION_UNCHANGED以使用缓存。

ViewPager中使用相同的Fragment

页面不更新,只显示第一个Fragment

绑定控件时应使用layout查找而不是activity查找,应该为:

name = (TextView) layout.findViewById(R.id.name);

而不是:

name = (TextView) activity.findViewById(R.id.name);