前言
ViewPage2是ViewPage的取代者,解决了一些解决了其前辈ViewPage的大部分难题,包括从右到左的布局支持,垂直方向,可修改的Fragment集合等。从易用性上来说ViewPage2的确会比ViewPage更简单,并且它实际上是使用RecyclerView实现的。源码里可以很容易看到,另外它的适配器也是RecyclerView.Adapter。
特别注意
在developer官网上,已经说明了 ViewPager2在导航键翻页(TV模式),根本没实现。所有,如果你的是TV应用,原则上不建议使用ViewPager2开发(TV应用用它浪费时间),而是使用ViewPager。
这下面说需要完善,都2022年了,居然还没完善。 本着钻研的精神(钻牛角尖),本博客最下面给了一个不太稳定的方法实现焦点的触控。
依赖
dependencies { implementation "androidx.viewpager2:viewpager2:1.0.0" }
使用RecyclerView.Adapter
使用场景一般是首页图片轮播,或者引导页面
xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".viewpage2.ViewPager2Activity"> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/imagePager" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" /> </androidx.constraintlayout.widget.ConstraintLayout>
Adapter
class ViewPager2Adapter1() : RecyclerView.Adapter<ViewPager2Adapter1.ViewHolder>() { val mImageList = mutableListOf<Int>() fun refreshData(list :MutableList<Int>?){ list?.let { mImageList.addAll(list) } notifyDataSetChanged() } class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val imageView = itemView.findViewById<ImageView>(R.id.image) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewPager2Adapter1.ViewHolder { return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_view_pager_demo_1, parent, false)) } override fun onBindViewHolder(holder: ViewPager2Adapter1.ViewHolder, position: Int) { holder.imageView.setImageResource(mImageList[position]) } override fun getItemCount(): Int = mImageList.size }
activity
class ViewPager2Activity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_view_pager2) val adapter = ViewPager2Adapter1() val imagePager = findViewById<ViewPager2>(R.id.imagePager) imagePager.adapter = adapter adapter.refreshData(mutableListOf(R.drawable.ic_landscape_1, R.drawable.ic_landscape_2, R.drawable.ic_landscape_3, R.drawable.ic_landscape_4)) } }
效果图
使用FragmentStateAdapter
使用场景一般是主页tab分页,每一页都是Fragment
class MessageAdapter(fragments: List<Fragment>, fragment: Fragment) : FragmentStateAdapter(fragment) { private var mFragments :List<Fragment> = fragments override fun getItemCount() = mFragments.size override fun createFragment(position: Int) = mFragments[position] }
上面的代码是在Fragment中使用,如果需要在activity中使用就使用另一个构造方法
public FragmentStateAdapter(@NonNull FragmentActivity fragmentActivity)
然后是添加到ViewPager2里
val fragmentList = listOf(AlertFragment.newInstance(), FamilyFragment.newInstance(), NoticeFragment.newInstance()) val adapter = MessageAdapter(fragmentList, this) mBinding.viewPager.adapter = adapter
与TabLayout配合使用
TabLayout tabLayout = view.findViewById(R.id.tab_layout); new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> tab.setText("OBJECT " + (position + 1)) ).attach();
但是请注意!在添加数据的时候不需要在newTab了,参考如下代码
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val fragmentList = listOf(AlertFragment.newInstance(), FamilyFragment.newInstance(), NoticeFragment.newInstance()) val adapter = MessageAdapter(fragmentList, this) mBinding.viewPager.adapter = adapter val tabIconArray = intArrayOf(R.drawable.message_ic_alert_3, R.drawable.message_ic_family_3, R.drawable.message_ic_notice_3) TabLayoutMediator(mBinding.tabLayout, mBinding.viewPager) { tab, position -> }.attach() for ((index, itemData)in tabIconArray.withIndex()) { //这里不需要newTab(),因为上面的TabLayoutMediator已经创建好了tab val tab: TabLayout.Tab? = mBinding.tabLayout.getTabAt(index) val binding = MessageItemMessageBinding.inflate(layoutInflater, mBinding.tabLayout, false) binding.icon.setImageResource(itemData) tab?.customView = binding.root mItemRedDotArray.add(binding.redDot) } initListener() }
禁用左右滑动切换
viewPager.setUserInputEnabled(false)
设置方向
横
android:orientation="horizontal"
竖
android:orientation="vertical"
设置滑动方向
从左到右
android:layoutDirection="ltr"
从右到左
android:layoutDirection="rtl"
翻页结果监听
imagePager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageScrollStateChanged(state: Int) { super.onPageScrollStateChanged(state) /** 表示ViewPager2处于空闲、稳定状态。当前页面完全可见,并且没有动画正在进行中。 SCROLL_STATE_IDLE = 0 表示ViewPager2当前正在被用户拖动,或者通过虚假拖动功能以编程方式进行拖动。 SCROLL_STATE_DRAGGING = 1 表示ViewPager2正在稳定到最终位置。 SCROLL_STATE_SETTLING = 2 */ Log.e("zh", "滚动状态变化 : state = $state") } override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { super.onPageScrolled(position, positionOffset, positionOffsetPixels) /** position – 当前显示的第一页的位置索引。如果 positionOffset 不为零,则页面位置+1 将可见。 positionOffset – 表示在位置处与页面的偏移量, 取值范围[0,1] positionOffsetPixels – 以像素为单位的值,指示与位置的偏移量。 */ Log.e("zh", "正在翻页滚动 : position = $position positionOffset = $positionOffset positionOffsetPixels = $positionOffsetPixels") } override fun onPageSelected(position: Int) { super.onPageSelected(position) Log.e("zh", "当前页面 : position = $position") } })
翻页动画
ViewPager2的翻页动画稍微一点点复杂,需要一些耐心理解.
首先动画的实现是依靠 ViewPager2.PageTransformer 接口回调实现的, 在下面的代码中有注释说明,
imagePager.setPageTransformer(object : ViewPager2.PageTransformer { override fun transformPage(page: View, position: Float) { if (position < -1) { // 范围 -无穷数 到 -1 // 此页面在左侧屏幕外,在这个位置的view已经看不到了,一般不需要处理,只要给初始默认值就行 Log.e("zh", "此页面在左侧屏幕外: $position") } else if (position <= 1) { //范围-1 到 1 /* * 在这个位置的view正在在屏幕中显示,是需要实现动画效果的view * 这里其实有2个view会交替回调, 分别是position小于0的左边的view 与 position大于0的右边的view */ if (position < 0) { //viewpager左边item的显示 Log.e("zh", "viewpager左边item的显示 位置 = $position view的内存地址 = ${page}") } else { //viewpager右边item的显示 Log.e("zh", "viewpager右边item的显示 位置 = $position view的内存地址 = ${page}") } } else { // 范围 1 到 +无穷数 // 此页面在右侧屏幕外,在这个位置的view已经看不到了,一般不需要处理,只要给初始默认值就行 Log.e("zh", "此页面在右侧屏幕外: $position") } } })
如果你看了上面的注释依然不太明白,我们可以用图片解释一下
图片1
图片2
理解这张图片很重要! 这张图片中的效果出现的时候,就是position 正在返回 -1 到 1的值.
这正是我们需要实现动画效果(透明度,放大缩小,旋转抛出等等效果)的时候
举例一个淡入淡出动画效果
在上面说明了动画的取值范围后,这里举例一个最容易实现的动画,淡入淡出动画.
imagePager.setPageTransformer(object : ViewPager2.PageTransformer { override fun transformPage(page: View, position: Float) { if (position < -1) { page.alpha = 0f } else if (position <= 1) { //范围-1 到 1 /* * 求绝对值,作为动画的变量值 */ val animationValue: Float = Math.abs(position) if (position < 0) { //viewpager左边item的显示 page.alpha = 1 - animationValue } else { //viewpager右边item的显示 page.alpha = 1 - animationValue } } else {
page.alpha = 0f } } })
效果图
举一反三实现旋转与缩小放大动画效果
代码
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_view_pager2) val adapter = ViewPager2Adapter1() val imagePager = findViewById<ViewPager2>(R.id.imagePager) imagePager.adapter = adapter adapter.refreshData(mutableListOf(R.drawable.ic_landscape_1, R.drawable.ic_landscape_2, R.drawable.ic_landscape_3, R.drawable.ic_landscape_4)) imagePager.setPageTransformer(object : ViewPager2.PageTransformer { override fun transformPage(page: View, position: Float) { if (position < -1) { page.alpha = 0f } else if (position <= 1) { //范围-1 到 1 /* * 求绝对值,作为动画的变量值 */ val animationValue: Float = Math.abs(position) if (position < 0) { //viewpager左边item的显示 page.alpha = 1 - animationValue setRotate(page, animationValue) setScale(page, animationValue) } else { //viewpager右边item的显示 page.alpha = 1 - animationValue setRotate(page, animationValue) setScale(page, animationValue) } } else { page.alpha = 0f } } }) } /** * 旋转效果 */ fun setRotate(view: View, value: Float) { view.rotationX = 0.5f view.rotationY = 0.5f view.rotation = 360 * value } /** * 缩放效果 */ fun setScale(view: View, value: Float) { view.scaleX = 1 - value view.scaleY = 1 - value }
效果图
看看google给的例子
例子1
public class DepthPageTransformer implements PageTransformer { private static float MIN_SCALE = 0.75f; @SuppressLint("NewApi") @Override public void transformPage(View view, float position) { int pageWidth = view.getWidth(); if (position < -1) { // [-Infinity,-1)//This page is way off-screen to the left. view.setAlpha(0); } else if (position <= 0) { // [-1,0]Use //the default slide transition when moving to the left page view.setAlpha(1); view.setTranslationX(0); view.setScaleX(1); view.setScaleY(1); } else if (position <= 1) { // (0,1]// Fade the page out. view.setAlpha(1 - position); // Counteract the default slide transition view.setTranslationX(pageWidth * -position); // Scale the page down (between MIN_SCALE and 1) float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position)); view.setScaleX(scaleFactor); view.setScaleY(scaleFactor); } else { // (1,+Infinity] // This page is way off-screen to the right. view.setAlpha(0); } } }
效果图
例子2
public class ZoomOutPageTransformer implements PageTransformer { private static float MIN_SCALE = 0.85f; private static float MIN_ALPHA = 0.5f; @Override public void transformPage(View view, float position) { int pageWidth = view.getWidth(); int pageHeight = view.getHeight(); if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. view.setAlpha(0); } else if (position <= 1) { // [-1,1] // Modify the default slide transition to // shrink the page as well float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position)); float vertMargin = pageHeight * (1 - scaleFactor) / 2; float horzMargin = pageWidth * (1 - scaleFactor) / 2; if (position < 0) { view.setTranslationX(horzMargin - vertMargin / 2); } else { view.setTranslationX(-horzMargin + vertMargin / 2); } // Scale the page down (between MIN_SCALE and 1) view.setScaleX(scaleFactor); view.setScaleY(scaleFactor); // Fade the page relative to its size. view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * (1 - MIN_ALPHA)); } else { // (1,+Infinity] // This page is way off-screen to the right. view.setAlpha(0); } } }
效果图
背景图片与ViewPager2 左右滑动的联动动画实现
xml
<ImageView android:id="@+id/bgImage" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleX="1.1" android:scaleY="1.1" android:background="@mipmap/launcher_ic_bg" /> <androidx.viewpager.widget.ViewPager android:id="@+id/viewPage" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="parent" app:layout_constraintVertical_bias="1.0" tools:layout_editor_absoluteX="0dp" />
java
mBinding.viewPage.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { var lastPosition = 0 var animationValue = 50f lateinit var objectAnimator : ObjectAnimator override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { mDotsAdapter.setSelectItem(position) if (lastPosition == position){ return } if (lastPosition < position){ //右 animationValue = animationValue -50f objectAnimator = ObjectAnimator.ofFloat(mBinding.bgImage,"translationX", animationValue) } else{ //左 animationValue = animationValue + 50f objectAnimator = ObjectAnimator.ofFloat(mBinding.bgImage,"translationX", animationValue) } objectAnimator.duration = 500 objectAnimator.start() lastPosition = position } override fun onPageSelected(position: Int) { } override fun onPageScrollStateChanged(state: Int) { } })
TV应用焦点与ViewPager2的兼容处理
在developer官网上,已经说明了 ViewPager2在导航键翻页(TV模式),根本没实现。所有,如果你的是TV应用,原则上不建议使用ViewPager2开发,而是使用ViewPager。 如果你非得杆上ViewPager2,下面这个代码也提供一个实现思路。但是十分不稳定
TV应用特别是支持触控又支持遥控器的TV设备,如何让焦点翻页? 特别是如何只使用遥控器方向键的情况下翻页呢?
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(mBinding.root) //注册全局焦点监听 mBinding.root.getViewTreeObserver().addOnGlobalFocusChangeListener(focusChangeListener) } override fun onDestroy() { super.onDestroy() mBinding.root.viewTreeObserver.removeOnGlobalFocusChangeListener(focusChangeListener) } private val focusChangeListener: ViewTreeObserver.OnGlobalFocusChangeListener = object : ViewTreeObserver.OnGlobalFocusChangeListener { override fun onGlobalFocusChanged(oldFocus: View?, newFocus: View?) { Log.e("zh", "newFocus: ${newFocus} ") if (newFocus != null) { /** * viewPage里的当前焦点view的第3个父类其实是RecyclerView,这里需要准确找到它。 * 当前你也可以使用另一种方法,那就是继承ViewPage,然后让mRecyclerView 全局变量暴露到外包 */ mBinding.viewPage.findFocus()?.parent?.parent?.parent?.let { recyclerView -> if (recyclerView is RecyclerView) { /** * 重点方法:findContainingViewHolder * 根据当前焦点view找到当前的ViewHolder */ val viewHolder = recyclerView.findContainingViewHolder(mBinding.viewPage.findFocus()) if (viewHolder != null) { if (viewHolder.layoutPosition != RecyclerView.NO_POSITION) { //使其翻页 mBinding.viewPage.currentItem = viewHolder.layoutPosition } } } } } } }
ViewPager2的已知bug
因为ViewPager与SwipeRefreshLayout冲突导致RecyclerView或者其他列表布局的item无法点击的问题
End
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/13528962.html