美文网首页
ViewPager2学习

ViewPager2学习

作者: 有点健忘 | 来源:发表于2019-09-23 11:12 被阅读0次

demo地址https://github.com/googlesamples/android-viewpager2
先运行下demo体验下,可以横向,竖向滑动的viewpager。还有页面切换的动画

使用

这个是androidx的包

implementation 'androidx.viewpager2:viewpager2:1.0.0-alpha03'

后来发现材料库自带这个库,所以有了下边的,上边的可以不加了

implementation 'com.google.android.material:material:1.1.0-alpha10'

注意

这个ViewPager2里的view,布局要求必须是math_parent的,不是的话会异常的。

常用方法

  1. 方向控制,如下,水平或者垂直
    也可以直接在xml里添加orientation属性
viewPager.orientation= ViewPager2.ORIENTATION_HORIZONTAL
  1. 适配器
    内部用的就是recyclerView,所以adapter也是
    public void setAdapter(@Nullable Adapter adapter) {
        mRecyclerView.setAdapter(adapter);
    }

另外还可以是fragment,当然其实里边还是用的recyclerView的adapter

        vp2.adapter=object :FragmentStateAdapter(this){
            override fun getItem(position: Int): Fragment {
                return FragmentTempTest()
            }

            override fun getItemCount(): Int {
                return 5
            }
        }
简单看下FragmentStateAdapter的代码
public abstract class FragmentStateAdapter extends
        RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {

    public FragmentStateAdapter(@NonNull FragmentActivity fragmentActivity) {
        this(fragmentActivity.getSupportFragmentManager(), fragmentActivity.getLifecycle());
    }

    public FragmentStateAdapter(@NonNull Fragment fragment) {
        this(fragment.getChildFragmentManager(), fragment.getLifecycle());
    }
  //3种构造方法,最终其实都是走到这里拉
    public FragmentStateAdapter(@NonNull FragmentManager fragmentManager,
            @NonNull Lifecycle lifecycle) {
        mFragmentManager = fragmentManager;
        mLifecycle = lifecycle;
        super.setHasStableIds(true);
    }

//FragmentViewHolder里其实就是new里一个FrameLayout作为容器,呆会把fragment的view填进来而已
    public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return FragmentViewHolder.create(parent);
    }

public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {


//其他都是在判断是否应该添加移除view,下边这个方法就是把对应的fragment的view弄进来
 placeFragmentInViewHolder(holder);
}

看下上边的ViewHolder,就是new了一个FrameLayout

public final class FragmentViewHolder extends ViewHolder {
    private FragmentViewHolder(@NonNull FrameLayout container) {
        super(container);
    }

    @NonNull static FragmentViewHolder create(@NonNull ViewGroup parent) {
        FrameLayout container = new FrameLayout(parent.getContext());
        container.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT));
        container.setId(ViewCompat.generateViewId());
        container.setSaveEnabled(false);
        return new FragmentViewHolder(container);
    }

    @NonNull FrameLayout getContainer() {
        return (FrameLayout) itemView;
    }
}
  1. 页面切换动画
    页面滑动的时候来处理动画的,正常屏幕肯定最多显示2个了。这里拿到view,以及偏移量可以做一些动画操作
    左边的position是负的,右边的是正的,取值-1到1.正中间是0
vp2.setPageTransformer(anim2)

    public interface 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(@NonNull View page, float position);
    }

看一下demo里的代码,有3种动画,旋转,平移,缩放

    private val mAnimator = ViewPager2.PageTransformer { page, position ->
        val absPos = Math.abs(position)
        page.apply {
            rotation = if (rotateCheckBox.isChecked) position * 360 else 0f
            translationY = if (translateY) absPos * 500f else 0f
            translationX = if (translateX) absPos * 350f else 0f
            if (scaleCheckBox.isChecked) {
                val scale = if (absPos > 1) 0F else 1 - absPos
                scaleX = scale
                scaleY = scale
            } else {
                scaleX = 1f
                scaleY = 1f
            }
        }
    }
  1. 禁止滑动
    setUserInputEnabled(false);默认是true,设置为false,用户就没法触摸滑动了。

  2. 和tablayout咋关联
    我们知道以前的viewpager,直接调用attach方法就行了。
    现在这个看下

        TabLayoutMediator(tabLayout, viewPager) { tab, position ->
            tab.text = cards[position].toString()
        }.attach()

构造方法如下
也没啥,最后多个回调,就是处理tab的显示内容的。

    public TabLayoutMediator(@NonNull TabLayout tabLayout, @NonNull ViewPager2 viewPager,
            @NonNull OnConfigureTabCallback onConfigureTabCallback) {
        this(tabLayout, viewPager, true, onConfigureTabCallback);
    }

这个已经集成到material库里了

源码解析

public final class ViewPager2 extends ViewGroup {
    public ViewPager2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initialize(context, attrs);
    }
//可以看到构造方法里都调用了initialize方法
//简单贴下有用的代码
private void initialize(Context context, AttributeSet attrs) {

mRecyclerView = new RecyclerViewImpl(context);
        mLayoutManager = new LinearLayoutManagerImpl(context);
        mRecyclerView.setLayoutManager(mLayoutManager);

       //这个方法就是读取xml里的orientation参数来修改垂直还是水平滚动
        setOrientation(context, attrs);
mRecyclerView.setLayoutParams(
                new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

//PagerSnapHelper学过的应该知道这个作用就是让rv一次滑动一个item
mPagerSnapHelper = new PagerSnapHelperImpl();
        mPagerSnapHelper.attachToRecyclerView(mRecyclerView);

//省略其他一些监听

//把这个rv添加到容器里。
attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
}

RecyclerViewImpl
主要就是触摸事件加了个条件,就是上边分类4里的setUserInputEnabled这个变量,根据这个来阻止触摸事件

  private class RecyclerViewImpl extends RecyclerView {
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            return isUserInputEnabled() && super.onTouchEvent(event);
        }

        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            return isUserInputEnabled() && super.onInterceptTouchEvent(ev);
        }
    }
继续看下vp2里的代码

为啥adapter里的view都必须是match,看下代码
attache child的时候会判断layoutparams的,不是match就直接exception了

    private RecyclerView.OnChildAttachStateChangeListener enforceChildFillListener() {
        return new RecyclerView.OnChildAttachStateChangeListener() {
            @Override
            public void onChildViewAttachedToWindow(@NonNull View view) {
                RecyclerView.LayoutParams layoutParams =
                        (RecyclerView.LayoutParams) view.getLayoutParams();
                if (layoutParams.width != LayoutParams.MATCH_PARENT
                        || layoutParams.height != LayoutParams.MATCH_PARENT) {
                    throw new IllegalStateException(
                            "Pages must fill the whole ViewPager2 (use match_parent)");
                }
            }

            @Override
            public void onChildViewDetachedFromWindow(@NonNull View view) {
                // nothing
            }
        };
    }

setAdapter 就是把adapter给了rv

    public void setAdapter(@Nullable @SuppressWarnings("rawtypes") Adapter adapter) {
        final Adapter<?> currentAdapter = mRecyclerView.getAdapter();
        mAccessibilityProvider.onDetachAdapter(currentAdapter);
        unregisterCurrentItemDataSetTracker(currentAdapter);
        mRecyclerView.setAdapter(adapter);
        mCurrentItem = 0;
        restorePendingState();
        mAccessibilityProvider.onAttachAdapter(adapter);
        registerCurrentItemDataSetTracker(adapter);
    }

public void setCurrentItem(int item, boolean smoothScroll)
页面手动切换,可以看到最终调用的还是rv的scroll方法

        if (!smoothScroll) {
            mRecyclerView.scrollToPosition(item);
            return;
        }

        // For smooth scroll, pre-jump to nearby item for long jumps.
        if (Math.abs(item - previousItem) > 3) {
            mRecyclerView.scrollToPosition(item > previousItem ? item - 3 : item + 3);
            // TODO(b/114361680): call smoothScrollToPosition synchronously (blocked by b/114019007)
            mRecyclerView.post(new SmoothScrollToPosition(item, mRecyclerView));
        } else {
            mRecyclerView.smoothScrollToPosition(item);
        }

缓存页面数,看下参数最小是1

    public void setOffscreenPageLimit(@OffscreenPageLimit int limit) {
        if (limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT) {
            throw new IllegalArgumentException(
                    "Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0");
        }
        mOffscreenPageLimit = limit;
        // Trigger layout so prefetch happens through getExtraLayoutSize()
        mRecyclerView.requestLayout();
    }

TabLayoutMediator

简单看下源码

知识点

  1. fragment的生命周期
    如果你adapter用的Fragment,那么fragment可见的时候会走onResume的,不可见走OnPause
  2. issue
    如果你给vp2设置了多个fragment以后,想删除其中一个,比如 ABC你想删除B,结果显示的还是AB
    解决办法
    确保那个getItemId 返回的值能代表你那个fragment.
        vp2.setAdapter(new FragmentStateAdapter(this) {
            @NonNull
            @Override
            public Fragment createFragment(int position) {
                return fragments.get(position);
            }

            @Override
            public int getItemCount() {
                return fragments.size();
            }

            @Override
            public long getItemId(int position) {
                return fragments.get(position).getTrafficType().ordinal();
            }
        });

相关文章

网友评论

      本文标题:ViewPager2学习

      本文链接:https://www.haomeiwen.com/subject/vssfiqtx.html