美文网首页
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