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的,不是的话会异常的。
常用方法
- 方向控制,如下,水平或者垂直
也可以直接在xml里添加orientation属性
viewPager.orientation= ViewPager2.ORIENTATION_HORIZONTAL
- 适配器
内部用的就是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;
}
}
- 页面切换动画
页面滑动的时候来处理动画的,正常屏幕肯定最多显示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
}
}
}
-
禁止滑动
setUserInputEnabled(false);默认是true,设置为false,用户就没法触摸滑动了。 -
和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
简单看下源码
知识点
- fragment的生命周期
如果你adapter用的Fragment,那么fragment可见的时候会走onResume的,不可见走OnPause - 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();
}
});
网友评论