ViewPager2使用和探究

1.ViewPager2简单介绍

    ViewPage2是Jetpack中的其中一个组件,可以实现滑动切换页面的效果,通常可以搭配其他组件实现banner切换、以及类似于抖音短视频上下滑动切换播放的效果。
ViewPager2是基于RecyclerView实现的,自然继承了RecyclerView的众多优点,并且针对ViewPager存在的问题做了优化。

  • 支持垂直方向的滑动且实现极其简单。
  • 完全支持RecyclerView的相关配置功能。
  • 支持多个PageTransformer。
  • 支持DiffUtil,局部数据刷新和Item动画。
  • 支持模拟用户滑动与禁止用户操作。

ViewPager 与 ViewPager2 部分对比

ViewPagerViewPager 2
PagerAdapterRecyclerView.Adapter
FragmentStatePagerAdapterFragmentStateAdapter
addPageChangeListenerregisterOnPageChangeCallback
从右到左 (RTL) 的布局支持
垂直方向支持
停用用户输入的功能(setUserInputEnabled、isUserInputEnabled)

常见API

//刷新Viewpager 同样支持recyclerView的局部刷新
notifyDataSetChanged()
 
setUserInputEnabled(false);//禁止手动滑动
 
setCurrentItem(0, false);//跳转到指定页面,false不带滚动动画
 
setCurrentItem(0);//跳转到指定页面,带滚动动画
 
addItemDecoration()//设置分割线 同RecyclerView
 
setOffscreenPageLimit();//设置预加载数量
 
setOrientation();//设置方向
 
fakeDragBy(offsetPx)//代码模拟用户滑动页面。支持通过编程方式滚动。
 
setPageTransformer()//设置滚动动画,参数可传 CompositePageTransformer,PageTransformer

依赖引入

    目前最新版本还是1.0.0,而且使用ViewPager2项目必须迁移到Androidx。
implementation ‘androidx.viewpager2:viewpager2:1.0.0’
implementation ‘androidx.recyclerview:recyclerview:1.2.1’ // ViewPager 2 需要使用 RecycleView 的 adapter

2.常见使用

2.1.简单水平及横向滑动

    这种功能的实现和Recyclerview一模一样,都是需要定义一个适配器,并且在适配器中编写Viewholder并填充数据,水平和竖直方向的变化只需要在布局文件中修改viewpager2的属性即可如下:

布局文件

//主布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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=".LineActivity">


    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/vp1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" />
</LinearLayout>
//viewpager2组件中的item布局,这里其实可以完全把viewpager2理解为一个recyclerview
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/line1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

适配器

package com.example.myapplication

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class LineAdapter(val data: List<Int>) : RecyclerView.Adapter<LineAdapter.LineViewHolder>() {
    inner class LineViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val linerLayout = view.findViewById<LinearLayout>(R.id.line1)
        val textView = view.findViewById<TextView>(R.id.text1)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LineViewHolder {
        return LineViewHolder(
            LayoutInflater.from(parent.context).inflate(R.layout.item_line, parent, false)
        )
    }

    override fun onBindViewHolder(holder: LineViewHolder, position: Int) {
        holder.linerLayout.setBackgroundColor(data[position])
        holder.textView.text = "这是第${position}个View"
    }

    override fun getItemCount(): Int {
        return data.size
    }
}

主体代码

package com.example.myapplication

import android.os.Bundle
import com.example.myapplication.databinding.ActivityLineBinding

class LineActivity : BaseActivity() {
    override val bind by getBind<ActivityLineBinding>()
    private lateinit var backgrounds: ArrayList<Int>
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (!::backgrounds.isInitialized) {
            backgrounds = ArrayList<Int>()
            backgrounds.add(android.R.color.holo_blue_bright);
            backgrounds.add(android.R.color.holo_red_dark);
            backgrounds.add(android.R.color.holo_green_dark);
            backgrounds.add(android.R.color.holo_orange_light);
            backgrounds.add(android.R.color.holo_purple);
        }
        bind.vp1.adapter = LineAdapter(backgrounds)
    }
}

2.2.搭配RadioGroup

布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".RadioActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/vp_rg"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/rg_vp" />

    <RadioGroup
        android:id="@+id/rg_vp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_gravity="bottom"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/rb_home"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@null"
            android:checked="true"
            android:drawableTop="@drawable/ic_launcher_foreground"
            android:drawablePadding="5dp"
            android:gravity="center"
            android:paddingTop="5dp"
            android:paddingBottom="5dp"
            android:text="111"
            android:textSize="16sp" />

        <RadioButton
            android:id="@+id/rb_msg"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@null"
            android:drawableTop="@drawable/ic_launcher_background"
            android:drawablePadding="5dp"
            android:gravity="center"
            android:paddingTop="5dp"
            android:paddingBottom="5dp"
            android:text="222"
            android:textSize="16sp" />

        <RadioButton
            android:id="@+id/rg_my"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@null"
            android:drawableTop="@drawable/ic_launcher_foreground"
            android:drawablePadding="5dp"
            android:gravity="center"
            android:paddingTop="5dp"
            android:paddingBottom="5dp"
            android:text="333"
            android:textSize="16sp" />
    </RadioGroup>
</RelativeLayout>

适配器

fragment相关需要继承FragmentStateAdapter并重写里面的方法

package com.example.myapplication

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter

class RadioAdapter(fragmentActivity: FragmentActivity, val data: List<Fragment>) :
    FragmentStateAdapter(fragmentActivity) {
    override fun getItemCount(): Int {
        return data.size
    }

    override fun createFragment(position: Int): Fragment {
        return data[position]
    }
}

主体代码

在这个里面可以通过registerOnPageChangeCallback来注册监听逻辑

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.RadioGroup
import androidx.viewpager2.widget.ViewPager2
import com.example.myapplication.databinding.ActivityLineBinding
import com.example.myapplication.databinding.ActivityRadioBinding

class RadioActivity : BaseActivity() {
    override val bind by getBind<ActivityRadioBinding>()
    private lateinit var frglist: ArrayList<BlankFragment>
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (!::frglist.isInitialized) {
            frglist = ArrayList<BlankFragment>()
            frglist.add(BlankFragment("111"))
            frglist.add(BlankFragment("222"))
            frglist.add(BlankFragment("333"))

        }
        bind.vpRg.adapter = RadioAdapter(this, frglist)
        bind.vpRg.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                Log.d("RadioActivity", "onPageSelected: ${position}")
                when (position) {
                    0 -> bind.rbHome.isChecked = true
                    1 -> bind.rbMsg.isChecked = true
                    2 -> bind.rgMy.isChecked = true
                }
            }
        })


        bind.rgVp.setOnCheckedChangeListener(object : RadioGroup.OnCheckedChangeListener {
            override fun onCheckedChanged(p0: RadioGroup?, p1: Int) {
                Log.d("RadioActivity", "onCheckedChanged: ${p1}")
                when (p1) {
                    R.id.rb_home -> bind.vpRg.currentItem = 0
                    R.id.rb_msg -> bind.vpRg.currentItem = 1
                    R.id.rg_my -> bind.vpRg.currentItem = 2
                }
            }

        })
    }
}

2.3.搭配Tablayout来使用

布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".TabLayoutActivity">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/mTabLayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="0.1">

        <com.google.android.material.tabs.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Monday" />

        <com.google.android.material.tabs.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Tuesday" />

        <com.google.android.material.tabs.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Wednesday" />
    </com.google.android.material.tabs.TabLayout>

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/myViewPager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

    </androidx.viewpager2.widget.ViewPager2>


</LinearLayout>

适配器

    和2.2用的一样

主体代码

重要的其实就是使用TabLayoutMediator绑定到一起就可以了。

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.myapplication.databinding.ActivityRadioBinding
import com.example.myapplication.databinding.ActivityTabLayoutBinding
import com.google.android.material.tabs.TabLayoutMediator

class TabLayoutActivity : BaseActivity() {
    override val bind by getBind<ActivityTabLayoutBinding>()
    private lateinit var frglist: ArrayList<BlankFragment>
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initPage()
    }

    fun initPage() {
        if (!::frglist.isInitialized) {
            frglist = ArrayList()
            frglist.add(BlankFragment("111"))
            frglist.add(BlankFragment("222"))
            frglist.add(BlankFragment("333"))
        }
        bind.myViewPager.adapter = RadioAdapter(this, frglist)
        TabLayoutMediator(
            bind.mTabLayout,
            bind.myViewPager,
            TabLayoutMediator.TabConfigurationStrategy { tab, position ->
                when (position) {
                    0 -> tab.text = "aaa"
                    1 -> tab.text = "bbb"
                    2 -> tab.text = "ccc"
                }
            }).attach()
    }
}

3.PageTransformer动画效果

    setPageTransformer是ViewPager中就提供的方法,用于设置页面切换动画效果。相较于ViewPager在ViewPager2中setPageTransformer的功能要更加的强大,除了可以设置页面切换动画,还可以用来设置页面边距而且支持同时设置多个PageTransformer。

MarginPageTransformer

    ViewPager2中取消了在ViewPager中的setPageMargin()方法,改为通过提供的MarginPageTransformer来设置页面间距。

bind.vp1.setPageTransformer(MarginPageTransformer(20))

CompositePageTransformer与自定义Transformer实现放缩换页
ViewPager2支持设置多个Transformer就是通过CompositePageTransformer实现的。CompositePageTransformer类中通过List来维护多个Transformer。
val compositePageTransformer = CompositePageTransformer()
compositePageTransformer.addTransformer(MarginPageTransformer(20))
compositePageTransformer.addTransformer(MyTransformer())
bind.vp1.setPageTransformer(compositePageTransformer)

    MyTransformer是自定义的变换类,自定义Transformer的实现也很简单跟ViewPager中的实现基本一致,只不过需要继承实现的接口变成ViewPager2.PageTransformer,同时实现transformPage方法即可,transformPage方法有两个参数:第一个是发生改变的页面对象,第二个是偏移量。
    View page 发生改变的页面对象。
    float position 页面偏移量,取值范围(-1,1)当前页面显示位置值为0,页面向左滑动时,当前页面从屏幕位置向左移动时取值从0变为-1,完全不可见时取值趋向-1,右侧页面向左移动到当前屏幕位置取值从1变为0,页面右滑时同理。

自定义实现放大缩小变化

inner class MyTransformer() : ViewPager2.PageTransformer {
    val DEFAULT_MIN_SCALE = 0.85f
    val DEFAULT_CENTER = 0.5f

    private val mMinScale = DEFAULT_MIN_SCALE
    override fun transformPage(page: View, position: Float) {
        val pageWidth = page.width
        val pageHeight = page.height
        //动画锚点设置为View中心
        //动画锚点设置为View中心
        page.pivotX = (pageWidth / 2).toFloat()
        page.pivotY = (pageHeight / 2).toFloat()
        if (position < -1) {
            //屏幕左侧不可见时
            page.scaleX = mMinScale
            page.scaleY = mMinScale
            page.pivotY = (pageWidth / 2).toFloat()
        } else if (position <= 1) {
            if (position < 0) {
                //屏幕左侧
                //(0,-1)
                val scaleFactor: Float = (1 + position) * (1 - mMinScale) + mMinScale
                page.scaleX = scaleFactor
                page.scaleY = scaleFactor
                page.pivotX = pageWidth.toFloat()
            } else {
                //屏幕右侧
                //(1,0)
                val scaleFactor: Float = (1 - position) * (1 - mMinScale) + mMinScale
                page.scaleX = scaleFactor
                page.scaleY = scaleFactor
                page.pivotX = pageWidth * ((1 - position) * DEFAULT_CENTER)
            }
        } else {
            //屏幕右侧不可见
            page.pivotX = 0f
            page.scaleY = mMinScale
            page.scaleY = mMinScale
        }
    }
}

一屏多页效果

    使用setOffscreenPageLimit来设置ViewPager2至少预加载左右各一个页面,否则可能存在左右页面未初始化,导致不显示问题。

viewPager2.setOffscreenPageLimit(1);

//一屏多页
        val recyclerView: View = bind.vp1.getChildAt(0)
        if (recyclerView is RecyclerView) {
            recyclerView.setPadding(100, 0, 100, 0)
            (recyclerView as RecyclerView).clipToPadding = false
        }

4.fragemnt懒加载

    在使用ViewPager显示Fragment时,最麻烦的可能就是ViewPager的预加载问题了,最常用的解决方案有以下几种:

  1. 通过重写ViewPager来解除setOffscreenPageLimit(int)必须大于等于1的限制。
  • ViewPager提供了setOffscreenPageLimit(int)可以用来控制左右预加载页面的数量,但是限制了至少预加载一页。
  • 可以通过拷贝ViewPager源码,重写setOffscreenPageLimit方法来解除限制,从而实现ViewPager不进行预加载。
  • 此方法只能以某一版本的ViewPager源码为基础进行修改,适配性很差。且需要重写ViewPager部分逻辑。
  • 不推荐使用。
  1. 通过Fragment的setUserVisibleHint(boolean)判断Fragment可见性,从而实现懒加载。
  1. adnroidx之后可以使用Lifecycle实现更简洁的懒加载实现方式。

    ViewPager2也是通过上面提到的第三条实现方式来实现懒加载的。ViewPager2需要使用FragmentStateAdapter,不会调用Fragment的setUserVisibleHint(在Android X中已经被废弃),所以不能依靠setUserVisibleHint 来判断Fragment是否可见。
    FragmentStateAdapter 会自动销毁不再用的Fragment(打log发现销毁倒数第三个),如果需要 首次加载后不再进行接口请求,则需要设置ViewPager的offscreenPageLimit

/**

 * Created by Yangxy on 2020-01-13
 * description --
 */
abstract class LazyFragment : Fragment() {

    private var isFirstLoad = true


    override fun onResume() {
        super.onResume()
        if (isFirstLoad) {
            isFirstLoad = false
            lazyLoad()
        }
    }

    abstract fun lazyLoad()
}
//设置offscreenPageLimit 为列表总数
vp_node.offscreenPageLimit = nodeList.size

参考文献:懒加载

5.Viewpager2缓存分析

详解Android ViewPager2中的缓存和复用机制

  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ViewPager和ViewPager2都是Android中的视图控件,用于实现滑动切换不同页面的功能。 ViewPager是Android SDK中的一个类,它可以在同一个Activity中展示多个Fragment,通过左右滑动来切换不同的Fragment。ViewPager可以实现无限循环滑动,但是它的性能不够好,存在一些问题,比如在嵌套使用时会出现滑动冲突等。 ViewPager2ViewPager的升级版,它是在AndroidX库中的一个类,它解决了ViewPager存在的一些问题,比如滑动冲突、性能问题等。ViewPager2支持嵌套滑动,可以实现更加灵活的布局,同时还支持横向和纵向滑动。因此,ViewPager2是更加推荐使用的视图控件。 ### 回答2: ViewPager 和 ViewPager2 都是 Android 平台上的视图容器,它们都用于实现左右滑动切换多个视图的效果。不过,它们也有一些不同的特点。 ViewPager 是 Android 系统自带的视图容器,它主要用于在同一个 Activity 中切换多个 Fragment。ViewPager 会将多个 Fragment 放置在同一个视图中,通过滑动切换 Fragment 来实现左右滑动的效果。ViewPager 比较易用、稳定,使用起来也比较简单,但是在一些功能上有一定的局限性。 ViewPager2ViewPager 的升级版,它是在 AndroidX 中新增加的一个控件。相较于 ViewPager,ViewPager2 有一些更加高级和灵活的功能。首先,ViewPager2 支持 RecyclerView.Adapter,这样用户可以通过 RecyclerView.Adapter 来实现 ViewPager2 中的数据管理,这大大提高了数据操作的灵活性。其次,ViewPager2 支持垂直滑动的效果,这使得用户可以通过上下滑动切换多个视图。此外,ViewPager2 还支持滑块(PageTransformer)和视图预加载(OffscreenPageLimit)等高级功能,让用户可以更加方便地自定义 ViewPager2 的效果和行为。 总的来说,如果只是想要简单实现左右滑动切换多个 Fragment 的效果,可以使用 ViewPager。如果需要更加高级、灵活的功能,或者需要在 ViewPager 中嵌套 RecyclerView 或其他视图控件,则可以选择 ViewPager2。同时,最好在使用 ViewPager2 时,将所有 Fragment 替换为 RecyclerView,这样能够充分利用 ViewPager2 的强大功能。 ### 回答3: ViewPager和ViewPager2Android平台上常用的 View容器 组件。它们最主要的作用是管理多个子view的滑动显示,类似于滑动的页面。 ViewPager从Android API Level 11就被引入,它支持从左往右滑动查看多个子视图,以轻松实现流畅的“屏幕滑动”效果,常见的使用场景包括相册、图库、图片轮播图等。在使用ViewPager时,开发者需要自己实现适配器,根据需要返回子View。且ViewPager中每个页面的宽度是相等的,无法进行自由的布局。 而ViewPager2是新增的一个组件,它是AndroidX中的一部分,于2019年发布。ViewPager2相对于ViewPager的最大改进就在于支持不同宽度的页面。除了滑动方向以外,ViewPager2还支持从RecyclerView使用适配器,从而不仅仅可以使用View,还可以使用任何RecyclerView的特性和布局(如GridLayoutManager等)。另一个重要的改进是支持了多层嵌套,并且同步了更多的触摸事件,增强了原生的滑动手势支持。 总之,ViewPager2ViewPager的升级版,它具有更多灵活的布局和更好的性能。开发者可以根据自己的需求选择使用ViewPager或ViewPager2,相信在未来的Android开发中,ViewPager2会成为首选。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值