针对ViewPager2一种通用的性能优化手段

在某些场景下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拥有三个生命周期方法:

  1. onAttach - 当前页面被ViewPager2加载,但此时不可见
  2. onDettach - 当前页面被ViewPager2移除
  3. onResume - 当前页面被ViewPager2加载,此时可见

结合ViewHolder的生命周期,我们可以做类似优化。

  1. 在OAttach方法中,执行一部分onBindViewHolder()方法
  2. 在OnResume方法中,执行剩下部分的onBindViewHolder()方法
  3. 在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));
}
相关推荐
北极无雪20 分钟前
Spring源码学习(拓展篇):SpringMVC中的异常处理
java·开发语言·数据库·学习·spring·servlet
VXbishe27 分钟前
(附源码)基于springboot的“我来找房”微信小程序的设计与实现-计算机毕设 23157
java·python·微信小程序·node.js·c#·php·课程设计
YONG823_API1 小时前
电商平台数据批量获取自动抓取的实现方法分享(API)
java·大数据·开发语言·数据库·爬虫·网络爬虫
扬子鳄0081 小时前
java注解的处理器
java
Amagi.1 小时前
Spring中Bean的作用域
java·后端·spring
2402_857589361 小时前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
繁依Fanyi1 小时前
旅游心动盲盒:开启个性化旅行新体验
java·服务器·python·算法·eclipse·tomcat·旅游
J老熊2 小时前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
蜜桃小阿雯2 小时前
JAVA开源项目 旅游管理系统 计算机毕业设计
java·开发语言·jvm·spring cloud·开源·intellij-idea·旅游
CoderJia程序员甲2 小时前
重学SpringBoot3-集成Redis(四)之Redisson
java·spring boot·redis·缓存