ViewPager2快速入门
ViewPager2是Google用来代替旧有的ViewPager而重新设计的控件,加入了一些旧版所没有的功能,并替换掉了一些我们熟知的方法。以达到使用的便捷和性能的优化。为了能让以前的项目继续正常运行,Google仍旧保留了旧有的ViewPager,两者的关系相当于ListView和RecyclerView的关系。
前期准备
ViewPager2在使用前需要在app的build.gradle中引入:
implementation 'androidx.viewpager2:viewpager2:1.0.0'
如果项目中需要用到ViewPager2和TabLayout进行联动的话还需要引入:
implementation 'com.google.android.material:material:1.2.0-alpha03'
滑动方向
ViewPager2除了常规的左右滑动外还加入了上下滑动。可以通过setOrientation方法指定滑动的方向。
public void setOrientation(@Orientation int orientation)
参数orientation的取值范围如下:
- ViewPager2.ORIENTATION_VERTICAL - 上下滑动
- ViewPager2.ORIENTATION_HORIZONTAL - 左右滑动
用RecyclerView.Adapter代替PagerAdapter
为了便于学习和记忆,谷歌直接使用RecyclerView的Adapter作为ViewPager2的适配器。这样设计好处是可以直接使用各种notifyItem开头的局部刷新方法,同时以前针对ReyclerView的Adapter的各种第三方工具类也能用了。
viewPager2.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup,viewType: Int): RecyclerView.ViewHolder {
// 在此实例化View视图
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_main_1,parent, false)
return object : RecyclerView.ViewHolder(view) {}
}
override fun getItemCount(): Int {
return listDataHor.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
// 根据集合指定位置的对象填充视图
holder.itemView.let {
it.tvName.text = listDataHor[position].name
it.tvAge.text = "${listDataHor[position].age}岁"
it.tvPhone.text = "联系电话:${listDataHor[position].phone}"
}
}
}
Pages must fill the whole ViewPager2 (use match_parent)异常的处理:
遇到这种异常无非是以下两种情况造成的。
- Item的XML布局的外层用了"wrap_content",应改为了"match_parent"
- 在onCreateViewHolder方法里实例化View时遇到inflate方法时第二个参数不要传null而是传parent,例如:
LayoutInflater.from(parent.context).inflate(R.layout.item_main_1, parent, false)
全新的FragmentStateAdapter
以前除了PageAdapte外还有FragmentPageAdapter和FragmentStatePageAdapter这两个专门用于处理Item项是Fragment的情况。现在ViewPager2中直接用FragmentStateAdapter替代之前的两个适配器类。
FragmentStateAdapter实际是继承了RecyclerView.Adapter的抽象类,调用的方法也比以前简化了许多:
// FragementStateAdapter的构造器需要传入一个FragmentActivity的实例
// 这里是在AppCompatActivity中调用的,所以直接传this
viewPager2.adapter = object : FragmentStateAdapter(this) {
override fun getItemCount(): Int {
// 返回集合的长度,Fragment的数量就是根据这个集合来创建的
return listDataFragment.size
}
override fun createFragment(position: Int): Fragment {
// 实例化Fragment
return ItemFragment.newInstance()
}
}
页面滑动响应registerOnPageChangeCallback
registerOnPageChangeCallback用于注册ViewPager2的滑动响应事件。在手势拖拽或滑到下一页时都会触发该事件,这次的registerOnPageChangeCallback需要传入的是一个OnPageChangeCallback的抽象类,可以根据需求只重写我们需要的方法,而不必像以前那样每次都要重写三个方法;
vp2Fragment.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
}
override fun onPageScrolled(position: Int,positionOffset: Float,positionOffsetPixels: Int) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
}
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
// 很多情况下在页面切换完成后在此方法进行各种操作
}
})
- onPageScrollStateChanged:当滑动状态改变时响应,通过判断state的值来确定当前的滑动状态,ViewPager2定义了三种状态的常量SCROLL_STATE_IDLE、SCROLL_STATE_DRAGGING、SCROLL_STATE_SETTLING
- onPageScrolled:滑动进行时响应。
- onPageSelected:滑动完成时响应,这个方法的使用率最高,一般我们会在滑到指定页面时对Fragment进行数据加载或更新等操作。
因为onPageChangeCallback是抽象类,所以多数情况下更多的是重写onPageSelected方法即可。
用TabLayoutMediator绑定TabLayout和ViewPager2的互动
TabLayoutMediator这个类也是新加出来的,使用前需要在build.gradle入material库才可以。TabLayoutMediator的主要是作用就是将TabLayout和ViewPager2进行绑定,然后通过回调类来更新TabLayout的状态;
TabLayoutMediator(tabLayout, viewPager2,
TabLayoutMediator.TabConfigurationStrategy { tab, position ->
// tab:当前处于选中状态的Tab对象
// position:当前Tab所处的位置
}
).attach() // 不要忘记,否则没效果
ViewPager2在用法上尽量和旧版的ViewPager保持相似,但两者的内部工作原理变化较大,加之ViewPager2出现的时间还不长,是随着androidX一起发布的,所以一些更复杂的用法还有待于以后的更新发掘。在这里写了一个简易的DEMO以供参考:
MainActivity,kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayoutMediator
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.item_main_1.view.*
import java.util.*
data class UserBean(val name: String, val age: Int, val phone: String)
class MainActivity : AppCompatActivity() {
private val listDataHor = mutableListOf<UserBean>()
private val listDataFragment = mutableListOf<UserBean>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
chkVertical.setOnClickListener {
// 设置ViewPager的滑动方向
vp2Normal.orientation = if (chkVertical.isChecked)
ViewPager2.ORIENTATION_VERTICAL
else
ViewPager2.ORIENTATION_HORIZONTAL
}
initViewPagerWithView()
initViewPagerWithFragment()
initTabViewPager()
}
private fun initViewPagerWithView() {
vp2Normal.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerView.ViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.item_main_1, parent, false)
return object : RecyclerView.ViewHolder(view) {}
}
override fun getItemCount(): Int {
return listDataHor.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.let {
it.tvName.text = listDataHor[position].name
it.tvAge.text = "${listDataHor[position].age}岁"
it.tvPhone.text = "联系电话:${listDataHor[position].phone}"
}
}
}
// 生成模拟数据
lifecycle.addObserver(object : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart() {
refreshListData(listDataHor)
vp2Normal.adapter?.notifyDataSetChanged()
}
})
}
private fun refreshListData(listData: MutableList<UserBean>) {
val arrName = arrayOf("张三", "李四", "王五", "赵六", "孙七", "周八", "吴九", "郑十")
repeat(10) {
val name = arrName[Random().nextInt(arrName.size)]
val age = Random().nextInt(10) + 20
val user = UserBean(name, age, System.currentTimeMillis().toString())
listData += user
Thread.sleep(10)
}
}
/**
* ViewPager2和Fragment的组合
*/
private fun initViewPagerWithFragment() {
vp2Fragment.adapter = object : FragmentStateAdapter(this) {
override fun getItemCount(): Int {
return listDataFragment.size
}
override fun createFragment(position: Int): Fragment {
val userInfo = listDataFragment[position].let {
"姓名:" + it.name + "\n年龄:" + it.age + "\n手机:" + it.phone
}
return ItemFragment.newInstance(userInfo)
}
}
/**
* 注册页面滑动时的回调事件
* OnPageChangeCallback是抽象类,这回终于不用每次都重写三个方法了
*/
vp2Fragment.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
Log.i("123", "onPageScrollStateChanged: $state")
}
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
Log.i("123", "onPageScrolled:$position $positionOffset $positionOffsetPixels")
}
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
// 很多情况下在页面切换完成后在此方法进行各种操作
Log.i("123", "onPageSelected:$position")
}
})
// 生成模拟数据
lifecycle.addObserver(object : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart() {
refreshListFragment(listDataFragment)
vp2Fragment.adapter?.notifyDataSetChanged()
}
})
}
/**
* 绑定TabLayout和ViewPager2的双向联动
*/
private fun initTabViewPager() {
TabLayoutMediator(tabLayout, vp2Fragment,
TabLayoutMediator.TabConfigurationStrategy { tab, position ->
tab.text = listDataFragment[position].name
}
).attach()
}
/**
* 生成模拟数据
*/
private fun refreshListFragment(listData: MutableList<UserBean>) {
val arrName = arrayOf("张三", "李四", "王五", "赵六", "孙七", "周八", "吴九", "郑十")
arrName.forEach {
val name = it
val age = Random().nextInt(10) + 20
val user = UserBean(name, age, System.currentTimeMillis().toString())
listData += user
Thread.sleep(10)
}
}
}
ItemFragment.kt
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_item.view.*
const val TAG = "ItemFragment"
class ItemFragment : Fragment() {
companion object {
fun newInstance(userInfo: String) = ItemFragment().apply {
arguments = Bundle().apply { putString("userInfo", userInfo) }
}
}
private var userInfo = ""
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.i(TAG, "${this.javaClass.simpleName} :onCreateView")
val view = inflater.inflate(R.layout.fragment_item, container, false)
userInfo = arguments?.let { it.getString("userInfo") } ?: ""
view.tvUserInfo.text = userInfo
return view
}
override fun onStart() {
super.onStart()
Log.i(TAG, "${this.javaClass.simpleName} $userInfo:onStart")
}
override fun onPause() {
super.onPause()
Log.i(TAG, "${this.javaClass.simpleName} $userInfo:onPause")
}
override fun onDestroy() {
super.onDestroy()
Log.i(TAG, "${this.javaClass.simpleName} :onDestory")
}
}
点击链接加入QQ群聊:https://jq.qq.com/?_wv=1027&k=5z4fzdT
或关注微信公众号:口袋里的安卓