在某些场景下ViewPager2性能低下
性能低下场景:
ViewPager2
里面每个页面渲染耗时严重,就会导致用户体验差。假设每个页面渲染需要耗费2s
,又加上ViewPager2的复用机制
,导致耗时时间会变为:2 * 3 = 6s
。这就导致了页面初始化久
,用户滑动卡顿
。
提出解决方案
造成上述问题本质
一般造成上述场景卡顿的基本原因在于:Adapter.onBindViewHolder()方法执行过于耗时。比如:设置大量的图片,将复杂数据,绑定在复杂的视图上。
解决问题的核心原理
拆分Adapter.onBindViewHolder()执行,将方法执行时间2s
拆分为几个方法,严格控制每个方法的执行时间。
具体解决方案
方案一:ViewPager2 + Fragment
在Fragment.onViewCreate()
方法执行一部分onBindViewHolder()方法,在Fragment.OnResume()
方法中又执行一个部分onBindViewHolder()方法。结合了Fragment的生命周期
,完美将OnBindViewHolder()方法的时间拆分为两段,并且OnResume()方法只有在用户滑动到此页面时
才会调用。
方案二:ViewPager2 + View
View的生命周期
并不像Fragment那样存在onCreate与onResume。所以并不能做到像Fragment那样拆分OnBindViewHolder
方法。在参考了FragmentStateAdapter
源码之后,鄙人实现了让ViewHolder
拥有Fragment的生命周期。ViewHolder
拥有三个生命周期方法:
- onAttach - 当前页面被ViewPager2加载,但此时不可见
- onDettach - 当前页面被ViewPager2移除
- onResume - 当前页面被ViewPager2加载,此时可见
结合ViewHolder
的生命周期,我们可以做类似优化。
- 在OAttach方法中,执行一部分onBindViewHolder()方法
- 在OnResume方法中,执行剩下部分的onBindViewHolder()方法
- 在onDettach方法中,回收资源
方案实现源码
方案一:
因官方已给出解决方案,各位看官可自行查找FragmentStateAdapter
在ViewPager2上使用。可以自行谷歌或者百度找示例,就不再贴使用示例。
方案二:谷歌官方并未提供此解决方案,故贴出鄙人方案源码.
框架源码:
java
package com.midea.light.device.adapter;
import android.annotation.SuppressLint;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.SimpleArrayMap;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
abstract class _DataSetChangeObserver extends RecyclerView.AdapterDataObserver {
@Override
public abstract void onChanged();
@Override
public final void onItemRangeChanged(int positionStart, int itemCount) {
onChanged();
}
@Override
public final void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
onChanged();
}
@Override
public final void onItemRangeInserted(int positionStart, int itemCount) {
onChanged();
}
@Override
public final void onItemRangeRemoved(int positionStart, int itemCount) {
onChanged();
}
@Override
public final void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
onChanged();
}
}
abstract public class ViewPager2ViewStateAdapter<T extends ViewPager2ViewStateAdapter.ViewHolder> extends RecyclerView.Adapter<T> {
ViewPager2 viewPager2;
// 记录各位置的ViewHolder
final SimpleArrayMap<Integer, T> viewHolders = new SimpleArrayMap<>();
public ViewPager2ViewStateAdapter(ViewPager2 viewPager2) {
this.viewPager2 = viewPager2;
// 设置ViewPager2的滑动监听
this.viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
// 滑动触发时,尝试更改ViewHolder状态为Resume
updateViewHolderLifecycle();
}
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
// 滑动触发时,尝试更改ViewHolder状态为Resume
updateViewHolderLifecycle();
}
});
// 设置ViewPager2数据源变化监听
registerAdapterDataObserver(new _DataSetChangeObserver() {
@Override
public void onChanged() {
// 数据集更改时,尝试更改ViewHolder状态为Resume
updateViewHolderLifecycle();
}
});
}
/**
* 尝试更新当前ViewHolder的Resume状态
*/
void updateViewHolderLifecycle() {
if (this.viewPager2.getScrollState() != ViewPager2.SCROLL_STATE_IDLE) {
return;
}
final int currentItem = this.viewPager2.getCurrentItem();
T currentViewHolder = this.viewHolders.get(currentItem);
if(currentViewHolder != null) {
holderResume(currentViewHolder, currentItem);
}
}
/**
* 更新当前ViewHolder的状态为Attach
* @param viewHolder
* @param position
*/
void holderAttach(T viewHolder, int position) {
viewHolder.state = 1;
viewHolder.holderAttach();
if(viewPager2.getCurrentItem() == position) {
holderResume(viewHolder, position);
}
}
/**
* 更新当前ViewHolder的状态为Resume
* @param viewHolder
* @param position
*/
void holderResume(T viewHolder, int position) {
if (viewHolder.state == 2) {
return;
}
if (viewHolder.state != 1) {
holderAttach(viewHolder, position);
}
viewHolder.state = 2;
viewHolder.holderResume();
}
/**
* 更新当前ViewHolder的状态为Detached
* @param viewHolder
* @param position
*/
void holderDetached(T viewHolder, int position) {
viewHolder.state = 3;
viewHolder.holderDetached();
}
@Override
public void onViewAttachedToWindow(@NonNull T holder) {
super.onViewAttachedToWindow(holder);
// ViewPager2挂载ViewHolder时,会自动调用此方法,此时ViewHolder不可见
holderAttach(holder, holder.position);
}
@Override
public void onViewDetachedFromWindow(@NonNull T holder) {
super.onViewDetachedFromWindow(holder);
// ViewPager2卸载ViewHolder时,会自动调用此方法
holderDetached(holder, holder.position);
}
@Override
public void onBindViewHolder(@NonNull T holder, @SuppressLint("RecyclerView") int position) {
holder.position = position;
viewHolders.put(position, holder);
}
/**
* 拥有生命周期的ViewHolder
* state含义:
* 1 -> Attach -> holderAttach()
* 2 -> Resume -> holderResume()
* 3 -> Detached -> holderDetached()
*/
public abstract static class ViewHolder extends RecyclerView.ViewHolder {
int position;
// 1 show 2 resume 3 hide
int state = -1;
public ViewHolder(@NonNull View itemView) {
super(itemView);
}
public abstract void holderAttach();
public abstract void holderDetached();
public abstract void holderResume();
}
}
简单使用示例:
java
private class ViewPagerAdapter extends ViewPager2ViewStateAdapter<ViewPagerAdapter.Holder> {
List<MSmartLiveData<TabRoomBean>> rooms;
public ViewPagerAdapter(ViewPager2 viewPager2) {
super(viewPager2);
}
public void setData(List<MSmartLiveData<TabRoomBean>> rooms) {
this.rooms = rooms;
notifyDataSetChanged();
}
public List<MSmartLiveData<TabRoomBean>> getData() {
return this.rooms;
}
@NonNull
@Override
public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new Holder(DeItemFamilyCardBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
}
@Override
public void onBindViewHolder(@NonNull Holder holder, int position) {
super.onBindViewHolder(holder, position);
MSmartLiveData<TabRoomBean> liveData = rooms.get(position);
//1. 给ViewHolder注入数据源
holder.initData(liveData);
}
@Override
public int getItemCount() {
return rooms == null ? 0 : rooms.size();
}
class Holder extends ViewPager2ViewStateAdapter.ViewHolder {
final DeItemFamilyCardBinding binding;
List<DeSubitemFamilyCardBinding> subItem;
MSmartLiveData<TabRoomBean> mRoom;
public Holder(@NonNull DeItemFamilyCardBinding binding) {
super(binding.getRoot());
this.binding = binding;
subItem = new ArrayList<>();
subItem.add(this.binding.sub1);
subItem.add(this.binding.sub2);
subItem.add(this.binding.sub3);
subItem.add(this.binding.sub4);
}
public DeItemFamilyCardBinding getBinding() {
return binding;
}
public void initData(MSmartLiveData<TabRoomBean> room) {
mRoom = room;
}
final void initImageLogo(ImageView imageView, boolean isPower, DeviceCardUIEntity entity) {
Glide.with(getContext())
.load((isPower ? entity.getOnLogo() : entity.getOffLogo()))
.override(40, 40)
.into(imageView);
}
private void _initData(TabRoomBean entity) {
List<MSmartLiveData<DeviceCardUIEntity>> deviceInfoEntities = new ArrayList<>();
Collection<MSmartLiveData<DeviceCardUIEntity>> _devices = loader.getDevicesInShow(entity.getRoomId());
if (CollectionUtil.isNotEmpty(_devices)) {
deviceInfoEntities.addAll(_devices);
final String familyId = familyLiveData.getOriginData().getFamilyId();
final String roomId = entity.getRoomId();
String strOrder = DeviceRepository.getInstance().getDeviceOrder(familyId, roomId);
if (!TextUtils.isEmpty(strOrder)) {
List<String> list = Arrays.asList(strOrder.split(","));
DeviceOrderUtils.sort(list, deviceInfoEntities);
}
}
int count = deviceInfoEntities.size();
binding.tvEmpty.setVisibility(count == 0 ? View.VISIBLE : View.INVISIBLE);
for (int i = 0; i < subItem.size(); i++) {
subItem.get(i).getRoot().setVisibility(count > i ? View.VISIBLE : View.INVISIBLE);
if (subItem.get(i).getRoot().getVisibility() == View.VISIBLE) {
initSubItem(deviceInfoEntities.get(i), subItem.get(i));
}
}
}
private void initSubItem(MSmartLiveData<DeviceCardUIEntity> device, DeSubitemFamilyCardBinding binding) {
DeviceCardUIEntity entity = device.getValue();
binding.tvDeviceName.setText(entity.getDeviceName());
initImageLogo(binding.iconDevice, entity.isPower(), entity);
binding.getRoot().setSelected(entity.isPower());
}
@Override
public void holderAttach() {
// 2. 执行部分视图渲染代码
binding.tvRoomName.setText(mRoom.getOriginData().getName());
binding.imageDetail.setOnClickListener(this);
}
@Override
public void holderDetached() {
// 4. 此处可执行相关资源回收代码
}
@Override
public void holderResume() {
// 3. 执行剩下部分的渲染视图代码
initData(mRoom);
binding.imageBackground.setBackgroundResource(parseRoomImage(mRoom.getOriginData().getIcon()));
}
}
}
void main() {
viewPager2 = new ViewPager2(getContext());
viewPager2.setAdapter(viewpagerAdapter = new ViewPagerAdapter(viewPager2));
}