1. 前言
ViewPager +Fragment 的组合比较适合用来做页面的导航,这里因为在Android 插件化开发指南——实践之仿酷狗音乐首页一文的实践中需要用来这块的知识。为了app 加载更加流畅,这里考虑使用预加载和懒加载两种机制。当然,这里对于ViewPager +Fragment 的简单实现,这里记录下: 首先定义好ViewPager 控件:
<androidx.viewpager.widget.ViewPager
android:id="@+id/fx_viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
然后定义好需要显示的三个子项的布局文件: 然后定义一个ViewPager 控件的适配器:
public class PageViewPagerAdapter<T extends View> extends PagerAdapter {
private List<T> mList;
public PageViewPagerAdapter(List<T> mList) {
this.mList = mList;
}
@Override
public int getCount() {
return this.mList.size();
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return object == view;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
container.addView(mList.get(position));
return mList.get(position);
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView(mList.get(position));
}
}
然后只需要在Fragment 中完成初始化item 到View 实例,并设置adapter ,最后设置OnPageChangeListener 监听即可。这里我的效果为:
2. ViewPager+Fragment优化
预加载和懒加载。ViewPager 控件有个特有的预加载机制,即默认情况下当前页面左右两侧的1 个页面会被加载,以方便用户滑动切换到相邻的界面时,可以更加顺畅的显示出来。这样就会导致本来加载一个页面,其实在背后会预先加载三个页面,也就是会导致内存消耗比较严重。如果页面的数据也很大的时候,可能存在极端的情况,即将内存撑爆,也就是OOM 问题。
所以在内存消耗比较低的场景中,可以使用预加载技术来提高响应时间,进而带来比较丝滑的滑动效果。在内存消耗比较高的场景中,对应的需要使用懒加载技术,来延迟资源的加载。懒加载对服务器端和客户端内存有一定的缓解压力作用,预加载则会增加服务器和和客户端压力。
2.1 预加载
在ViewPager 中,可以通过setOffscreenPageLimit(int limit) 来设置预加载页面数量,当前页面相邻的limit 个页面会被预加载进内存。不妨来看看源码:
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
从上面的代码中可以看出,预加载无法按照预想的,将limit 设置为0 来取消预加载。所以我们需要考虑其余的方式来实现取消ViewPager +Fragment 的预加载。在博客 ViewPager+Fragment取消预加载(延迟加载)一文中给出了一个解决的思路。即:
通过判断Fragment 对用于的可见性来实现,也就是在这个Fragment 对用户可见了再进行数据的加载。而再Fragment 中提供了两个方法,分别是:
boolean getUserVisibleHint()
void setUserVisibleHint(boolean isVisibleToUser)
我们只需要复写这两个方法即可,就可以判断当前的Fragment 是否可见,进而判断是否进行数据的加载。其实根据上面的分析,这里我们知道根本上这个Fragment 还是会加载,只是我们将那些实际请求数据的操作放置在了之后,其实也就是懒加载。
也即是说,取消Fragment 预加载的解决为使用懒加载。因为预加载从前面代码中我们知道,解决不了,且默认设置为1 ,也就是会预加载左右两个页面。
2.2 懒加载
比如定义如下一个懒加载的Fragment 父类:
public abstract class LazyFragment extends Fragment {
private View rootView;
private boolean isViewCreated = false;
private boolean isDatasLoaded = false;
private boolean isCurrentVisible = false;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
if(rootView == null){
rootView = inflater.inflate(getLayoutResources(), container, false);
}
isViewCreated = true;
if(getUserVisibleHint()) setUserVisibleHint(true);
return rootView;
}
protected View getRootView(){
return rootView;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isViewCreated){
Log.e("TAG", "isCurrentVisible: " + isCurrentVisible + " | isVisibleToUser: " + isVisibleToUser );
if(!isCurrentVisible && isVisibleToUser){
if(!isDatasLoaded) lazyLoadData();
isDatasLoaded = true;
isCurrentVisible = isVisibleToUser;
}else if(isCurrentVisible && !isVisibleToUser){
stopLoadData();
isCurrentVisible = isVisibleToUser;
}
}
}
protected <T extends View> T findViewById(int id) {
if(isViewCreated) return (T) rootView.findViewById(id);
return null;
}
protected abstract int getLayoutResources();
protected abstract void lazyLoadData();
protected void stopLoadData(){}
}
然后将原本传入ViewPager 的直接通过LayoutInflater 实例化的View 对象换成Fragment 对象。同时,将适配器修改为继承自FragmentPagerAdapter 的类:
public class PageViewPagerAdapter<T extends Fragment> extends FragmentPagerAdapter {
private List<T> mList;
public PageViewPagerAdapter(@NonNull FragmentManager fm, List<T> mList) {
super(fm);
this.mList = mList;
}
@NonNull
@Override
public Fragment getItem(int position) {
return mList.get(position);
}
@Override
public int getCount() {
return this.mList.size();
}
}
然后再创建ViewPager 关联的三个页面的Fragment 的时候,就需要继承自前面所定义的LazyFragment ,比如在下面的示例中,我使用Handler 发送一个延迟消息,来模拟数据的耗时加载:
public class FxPageMusicFragment extends LazyFragment {
private View rootView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
rootView = getRootView();
initViews();
return rootView;
}
private void initViews() {
}
@Override
protected int getLayoutResources() {
return R.layout.fx_viewpager_item_yy;
}
@Override
protected void lazyLoadData() {
Log.e("TAG", "lazyLoadData: 音乐加载数据");
Handler handler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (msg.what == 0){
Button button = findViewById(R.id.yy_page_loading_button);
button.setText("数据加载完毕。");
}
}
};
Message msg = new Message();
msg.what = 0;
handler.sendMessageDelayed(msg, 1000);
}
@Override
protected void stopLoadData() {
super.stopLoadData();
Log.e("TAG", "lazyLoadData: 音乐停止加载数据");
}
}
最后的效果为: 当然,对于ViewPager +Fragment 优化的懒加载处理这块,我看的bilibili 的视频:懒加载方案源码解析之一。
3. 后记
对于ViewPager +Fragment 优化的懒加载处理,主要参考了上面的那个视频。然后简单修改了一部分。从观看视频到依葫芦画瓢的这个过程,确实也说明了自己知识储备确实不够。还需要多加练习。
References
|