ViewPager2 应用说明书
零、Demo
我的项目源码
演示 apk
如果对你有用,心愿能给个 star,谢谢。
一、性能
官网对于应用 ViewPager2 创立滑动视图的阐明:
Swipe views allow you to navigate between sibling screens, such as tabs, with a horizontal finger gesture, or swipe. This navigation pattern is also referred to as horizontal paging. This topic teaches you how to create a tab layout with swipe views for switching between tabs, along with how to show a title strip instead of tabs.
粗心是说,应用 ViewPager2 能够实现 Views 或页面的程度方向(罕用程度,垂直也反对)的滑动。也能够联合 Tab 组件应用。
二、根本应用
2.1 依赖援用
implementation "androidx.viewpager2:viewpager2:1.0.0"
2.2 版本阐明
1.0.0 版本是 2019 年 11 月 20 日 更新的。
1.1.0-beta01 测试版本是 2021 年 8 月 4 日 ,最近才更新的。
具体的更新内容,和最新版本的信息,能够在这个链接查到。
2.3 根本应用
ViewPager2 应用形式简略。学习须要把握以下几个因素:XML 申明、定义 Adapter 、设置滑动监听。
2.3.1 XML 布局中应用
<androidx.viewpager2.widget.ViewPager2 xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" />
2.3.2 罕用 Adapter 类型
ViewPager2.java 局部源码:
private void initialize(Context context, AttributeSet attrs) { mAccessibilityProvider = sFeatureEnhancedA11yEnabled ? new PageAwareAccessibilityProvider() : new BasicAccessibilityProvider(); mRecyclerView = new RecyclerViewImpl(context); mRecyclerView.setId(ViewCompat.generateViewId()); mRecyclerView.setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS); mLayoutManager = new LinearLayoutManagerImpl(context); mRecyclerView.setLayoutManager(mLayoutManager); mRecyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING); setOrientation(context, attrs); mRecyclerView.setLayoutParams( new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); mRecyclerView.addOnChildAttachStateChangeListener(enforceChildFillListener()); ...}
通过 ViewPager2 的源码,能够看到它外部保护了一个 RecyclerView,来实现列表视图的显示和滑动管制。
所以,RecyclerView.Adapter 能够间接用于 ViewPager2 ,这个应该是大家在应用 RecyclerView 组件时常常用到的。
另外,ViewPager2 的依赖包中,还提供了 FragmentStateAdapter ,继承自RecyclerView.Adapter。次要用于 ViewPager2 和 Fragment 的联合应用。下文中会介绍如何应用。
<img src="https://image.autismbug.cn/2021-08-18-151043.png" alt="image-20210818231024635" style="zoom: 50%;" />
2.3.3 滑动事件监听
void registerOnPageChangeCallback(@NonNull OnPageChangeCallback callback)
通过该函数,注册 Page 变动的事件监听。
OnPageChangeCallback 类的源码如下:
public abstract static class OnPageChangeCallback { /** * This method will be invoked when the current page is scrolled, either as part * of a programmatically initiated smooth scroll or a user initiated touch scroll. * * * @param position Position index of the first page currently being displayed. * Page position+1 will be visible if positionOffset is nonzero. * @param positionOffset Value from [0, 1) indicating the offset from the page at position. * @param positionOffsetPixels Value in pixels indicating the offset from position. */ public void onPageScrolled(int position, float positionOffset, @Px int positionOffsetPixels) { } /** * This method will be invoked when a new page becomes selected. Animation is not * necessarily complete. * * @param position Position index of the new selected page. */ public void onPageSelected(int position) { } /** * Called when the scroll state changes. Useful for discovering when the user begins * dragging, when a fake drag is started, when the pager is automatically settling to the * current page, or when it is fully stopped/idle. {@code state} can be one of {@link * #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}. */ public void onPageScrollStateChanged(@ScrollState int state) { } }
有三个回调办法:
- onPageScrolled ,返回 Page 滑动地位偏移或像素的变动。
- onPageSelected ,滑动后的 page position。
- onPageScrollStateChanged,滑动状态的变动。
从一个 Page 滑动到另一个 Page ,Callback 的回调状况:
2021-08-18 23:28:14.046 onPageSelected() called with: position = 02021-08-18 23:28:14.047 onPageScrolled() called with: position = 0, positionOffset = 0.0, positionOffsetPixels = 02021-08-18 23:28:37.333 onPageScrollStateChanged() called with: state = 12021-08-18 23:28:37.350 onPageScrolled() called with: position = 0, positionOffset = 0.016666668, positionOffsetPixels = 18......2021-08-18 23:28:37.427 onPageScrolled() called with: position = 0, positionOffset = 0.20277777, positionOffsetPixels = 2192021-08-18 23:28:37.431 onPageScrollStateChanged() called with: state = 22021-08-18 23:28:37.450 onPageSelected() called with: position = 12021-08-18 23:28:37.451 onPageScrolled() called with: position = 0, positionOffset = 0.28611112, positionOffsetPixels = 309......2021-08-18 23:28:37.717 onPageScrolled() called with: position = 0, positionOffset = 0.99814814, positionOffsetPixels = 10782021-08-18 23:28:37.734 onPageScrolled() called with: position = 1, positionOffset = 0.0, positionOffsetPixels = 02021-08-18 23:28:37.734 onPageScrollStateChanged() called with: state = 0
onPageScrollStateChanged 滑动状态 state:SCROLL_STATE_IDLE = 0SCROLL_STATE_DRAGGING = 1SCROLL_STATE_SETTLING = 2
察看日志,回调的特色:
- 首次加载,会回调 onPageSelected 和 onPageScrolled 办法,position 为 0。
- 向右滑动一页时,首先触发 onPageScrollStateChanged 回调,state 为 SCROLL_STATE_DRAGGING 。而后 onPageScrolled 屡次回调,能够看到地位偏移量的变动。在 onPageScrolled 屡次回调两头,会回调 onPageScrollStateChanged 办法,state 变为 SCROLL_STATE_SETTLING 地位固定。而后回调 onPageSelected ,position 为滑动后的地位。
- 最初一次 onPageScrolled 回调,position 变为 1。之后回调 onPageScrollStateChanged ,state 变为 SCROLL_STATE_IDLE 。
三、应用形式
3.1 + View
3.1.1 成果演示
<img src="https://image.autismbug.cn/2021-08-19-054524.gif" alt="views" style="zoom:50%;" />
3.1.2 Adapter 代码实现
首先定义一个 item xml 布局。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"> <TextView android:id="@+id/tv_text" android:background="@color/black" android:layout_width="match_parent" android:layout_height="280dp" android:gravity="center" android:textColor="#ffffff" android:textSize="22sp" /></LinearLayout>
自定义 ViewAdapter。
import android.graphics.Colorimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport android.widget.TextViewimport androidx.recyclerview.widget.RecyclerViewclass ViewAdapter : RecyclerView.Adapter<ViewAdapter.PagerViewHolder>() { var data: List<Int> = ArrayList() class PagerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { private val mTextView: TextView = itemView.findViewById(R.id.tv_text) private val colors = arrayOf("#CCFF99", "#41F1E5", "#8D41F1", "#FF99CC") fun bindData(i: Int) { mTextView.text = i.toString() mTextView.setBackgroundColor(Color.parseColor(colors[i])) } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagerViewHolder { return PagerViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_page, parent, false)) } override fun onBindViewHolder(holder: PagerViewHolder, position: Int) { holder.bindData(position) } override fun getItemCount(): Int { return data.size }}
3.1.3 ViewPager2 配置
在 Activity 或 Fragment 中的布局文件,间接应用 ViewPager2 标签。
而后在代码中,配置 Adapter 便实现了,实现 3.1.1 中的成果。
val viewAdapter = ViewAdapter() viewAdapter.data = listOf(1, 2, 3, 4) viewPager2 = findViewById(R.id.view_pager) viewPager2.apply { adapter = viewAdapter }
3.1.4 应用场景
Banner ,轮播广告图。能够配合定时器,进行定时滚动展现。
3.2 + Fragment
3.2.1 成果展现
整体成果,看上去与应用 RecyclerView.Aadapter + Views 的模式差不多。
前者在于部分控件,和 Fragment 配合,更多是整页的滑动。
这个例子中,减少了一些动画成果,及一屏多页的成果,在下文中会具体阐明。
<img src="https://image.autismbug.cn/2021-08-19-054516.gif" alt="fragment" style="zoom:50%;" />
3.2.2 Adapter 代码实现
ViewPager2 和 Fragment 配合应用, Adapter 前文也提到过,ViewPager2 的依赖包中,特地提供了 FragmentStateAdapter 。
应用起来比拟不便,只用重写两个办法:getItemCount 和 createFragment。示例如下:
TestFragment 是用 AndroidStudio 生成的模板 BlankFragment ,并将两个入参,别离设置为页面的文本内容及背景色彩。
import androidx.fragment.app.Fragmentimport androidx.fragment.app.FragmentActivityimport androidx.viewpager2.adapter.FragmentStateAdapterclass FragmentPagerAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) { override fun getItemCount(): Int { return 4 } private val colors = arrayOf("#CCFF99", "#41F1E5", "#8D41F1", "#FF99CC") override fun createFragment(position: Int): Fragment { return when (position) { PAGE_MESSAGE -> TestFragment.newInstance("音讯$position", colors[0]) PAGE_CONTACT -> TestFragment.newInstance("通讯录$position", colors[1]) PAGE_SETTING -> TestFragment.newInstance("设置$position", colors[2]) PAGE_MINE -> TestFragment.newInstance("我$position", colors[3]) else -> TestFragment.newInstance("$position", colors[0]) } } companion object { const val PAGE_MESSAGE = 0 const val PAGE_CONTACT = 1 const val PAGE_SETTING = 2 const val PAGE_MINE = 3 }}
3.2.3 ViewPager2 配置
应用形式简略, 间接将 Adapter 实例化赋值给 ViewPager2 对象的 adapter 。
3.2.4 场景
页面间的切换,反对手势滑动。反对过渡的动画。
还能够联合 Tab 组件,进行组件间的联动。
3.3 + TabLayout
3.3.1 成果展现
<img src="https://image.autismbug.cn/2021-08-19-070802.gif" alt="tablayout" style="zoom:50%;" />
3.3.2 代码实现
TabLayout 个别放在页面的顶部地位。在页面布局的 xml 中,用 com.google.android.material.tabs.TabLayout 标签申明。
TabLayout 组件是 material 包中的组件,Android Studio 新建我的项目,会主动引入这个依赖。
如果是老版本的我的项目,想要引入 TabLayout 组件,能够引入以下依赖,具体版本以官网为准。
'com.google.android.material:material:1.3.0'
XML 中申明:
<com.google.android.material.tabs.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="wrap_content" />
代码中,通过 TabLayoutMediator 将 TabLayout 和 ViewPager2 进行绑定。
val tabLayout = findViewById<TabLayout>(R.id.tab_layout) TabLayoutMediator(tabLayout, viewPager2) { tab, position -> //设置标签名称 tab.text = "OBJECT ${(position + 1)}" }.attach()
这样就实现了,标签导航和页面滑动的联动。
3.3.3 应用场景
个别能够用于相似新闻或者电商 ,各种类目页面间的切换。
3.4 + BottomNavigationView
3.4.1 成果展现
<img src="https://image.autismbug.cn/2021-08-19-070755.gif" alt="bottomNavigation" style="zoom:50%;" />
3.4.2 代码实现
监听 BottomNavigationView 的 setOnNavigationItemSelectedListener ,扭转 ViewPager2 的 item。
监听 ViewPager2 的 onPageSelected ,扭转 BottomNavigationView 的 item。
留神两组件的数量和地位要对应。
// 页面适配器 val fragmentPagerAdapter = FragmentPagerAdapter(this) val viewPager2 = findViewById<ViewPager2>(R.id.viewPager2) viewPager2.apply { adapter = fragmentPagerAdapter // 不提前加载 offscreenPageLimit = fragmentPagerAdapter.itemCount // 单动画成果 setPageTransformer(ScaleInTransformer()) } // 底部菜单 val bottomNavigation = findViewById<BottomNavigationView>(R.id.bottomNavigation) // 设置监听 bottomNavigation.setOnNavigationItemSelectedListener { menuItem -> when (menuItem.itemId) { //设置 viewPager2 item 地位 R.id.menu_messages -> { viewPager2.setCurrentItem(0, true) // 如返回 false ,bottomNavigation 被点击的 item ,不会被置为选中状态 true } R.id.menu_contacts -> { viewPager2.setCurrentItem(1, true) true } R.id.menu_setting -> { viewPager2.setCurrentItem(2, true) true } R.id.menu_mine -> { viewPager2.setCurrentItem(3, true) true } else -> throw IllegalArgumentException("未设置的 menu position,请查看参数") } } // 监听页面变动 viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { // 设置 bottomNavigation item 状态 bottomNavigation.menu.getItem(position).isChecked = true } })
3.4.3 应用场景
带底部导航的多页面导航 APP 。
3.5 其它
3.5.1 预加载
ViewPager2 的 offscreenPageLimit 属性。
设置为 ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT ,不进行预加载和缓存。
3.5.2 过渡成果
反对单成果和多成果设置,都是通过 setPageTransformer 函数。
单成果
间接传入 ViewPager2.PageTransformer 的子类,即能够设置对应的成果。
ViewPager2 包中提供了 MarginPageTransformer 页面边距成果,能够间接在我的项目中应用。
也能够自定义实现 PageTransformer 。
组合成果
ViewPager2 包中的 CompositePageTransformer 类,该类中,保护了 List<PageTransformer>。
能够设置多种 PageTransformer 组合成果。
上面提供几种成果的展现和代码,能够学习下自定义实现本人的成果:
1. 深度变动成果
<img src="https://image.autismbug.cn/2021-08-19-070748.gif" alt="depth" style="zoom:50%;" />
import android.view.Viewimport androidx.viewpager2.widget.ViewPager2private const val MIN_SCALE = 0.75fclass DepthPageTransformer : ViewPager2.PageTransformer { override fun transformPage(view: View, position: Float) { view.apply { val pageWidth = width when { position < -1 -> { // [-Infinity,-1) // This page is way off-screen to the left. alpha = 0f } position <= 0 -> { // [-1,0] // Use the default slide transition when moving to the left page alpha = 1f translationX = 0f translationZ = 0f scaleX = 1f scaleY = 1f } position <= 1 -> { // (0,1] // Fade the page out. alpha = 1 - position // Counteract the default slide transition translationX = pageWidth * -position // Move it behind the left page translationZ = -1f // Scale the page down (between MIN_SCALE and 1) val scaleFactor = (MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position))) scaleX = scaleFactor scaleY = scaleFactor } else -> { // (1,+Infinity] // This page is way off-screen to the right. alpha = 0f } } } }}
2. 比例放大进入成果
<img src="https://image.autismbug.cn/2021-08-19-064422.gif" alt="scarle" style="zoom:50%;" />
import android.view.Viewimport androidx.viewpager2.widget.ViewPager2import java.lang.Math.absclass ScaleInTransformer : ViewPager2.PageTransformer { private val mMinScale = DEFAULT_MIN_SCALE override fun transformPage(view: View, position: Float) { view.elevation = -abs(position) val pageWidth = view.width val pageHeight = view.height view.pivotY = (pageHeight / 2).toFloat() view.pivotX = (pageWidth / 2).toFloat() if (position < -1) { view.scaleX = mMinScale view.scaleY = mMinScale view.pivotX = pageWidth.toFloat() } else if (position <= 1) { if (position < 0) { val scaleFactor = (1 + position) * (1 - mMinScale) + mMinScale view.scaleX = scaleFactor view.scaleY = scaleFactor view.pivotX = pageWidth * (DEFAULT_CENTER + DEFAULT_CENTER * -position) } else { val scaleFactor = (1 - position) * (1 - mMinScale) + mMinScale view.scaleX = scaleFactor view.scaleY = scaleFactor view.pivotX = pageWidth * ((1 - position) * DEFAULT_CENTER) } } else { view.pivotX = 0f view.scaleX = mMinScale view.scaleY = mMinScale } } companion object { const val DEFAULT_MIN_SCALE = 0.85f const val DEFAULT_CENTER = 0.5f }}
3.缩放进入退出成果
<img src="https://image.autismbug.cn/2021-08-19-070727.gif" alt="zoom" style="zoom:50%;" />
private const val MIN_SCALE = 0.85fprivate const val MIN_ALPHA = 0.5fclass ZoomOutPageTransformer : ViewPager2.PageTransformer { override fun transformPage(view: View, position: Float) { view.apply { val pageWidth = width val pageHeight = height when { position < -1 -> { // [-Infinity,-1) // This page is way off-screen to the left. alpha = 0f } position <= 1 -> { // [-1,1] // Modify the default slide transition to shrink the page as well val scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position)) val vertMargin = pageHeight * (1 - scaleFactor) / 2 val horzMargin = pageWidth * (1 - scaleFactor) / 2 translationX = if (position < 0) { horzMargin - vertMargin / 2 } else { horzMargin + vertMargin / 2 } // Scale the page down (between MIN_SCALE and 1) scaleX = scaleFactor scaleY = scaleFactor // Fade the page relative to its size. alpha = (MIN_ALPHA + (((scaleFactor - MIN_SCALE) / (1 - MIN_SCALE)) * (1 - MIN_ALPHA))) } else -> { // (1,+Infinity] // This page is way off-screen to the right. alpha = 0f } } } }}
4. PageTransformer 页面边距成果
<img src="https://image.autismbug.cn/2021-08-19-064507.gif" alt="margin" style="zoom:50%;" />
3.5.3 禁止手动滑动
viewPager2.isUserInputEnabled = true or false
3.5.4 模仿滑动
viewPager2.beginFakeDrag() if (viewPager2.fakeDragBy(-300f)) { viewPager2.endFakeDrag() }
3.5.5 滑动方向
viewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL or ViewPager2.ORIENTATION_HORIZONTAL
四、延长扩大
- 如何自定义 PageTransformer
- ViewPager2 中 Fragment 的生命周期变动
- 更多应用场景扩大
五、参考资料
《还在用 ViewPager?是时候替换成 ViewPager2 了!》
《Google 官网文档》
《应用 ViewPager2 创立蕴含标签的滑动视图》
感激!
本文由博客一文多发平台 OpenWrite 公布!