前言
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
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
来源:https://www.cnblogs.com/guanxinjing/p/13528962.html |