1. View源码——fitSystemWindows详解
该方法在窗口的insets发生变化时,被调用。View调用该方法,以调整内容来适应窗口的变化。窗口的insets变化,包括status bar、软键盘、navigation bar等的显隐。
一般情况下我们不需要关心这个方法。但如果设置 SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN、SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 等标识开启沉浸式,默认情况下,我们的内容区域就会被status bar、软键盘等遮挡。
该方法的默认实现会根据insets值来设置view的padding,并返回true,防止该事件继续传递(即只有一个view会真正fitSystemWindows)。要开启该方法,需要执行 setFitsSystemWindows(true) ,或在XML中设置 android:fitsSystemWindows="true" 。
如果只需要为XML文件的根布局设置fitSystemWindows,该方法的默认实现就能满足。如果需要适配更加复杂的布局(比如有两个子View,一个在顶部,一个在底部,则顶部的需要根据insets设置paddingTop,底部的需要根据insets设置paddingBottom),你就需要重写该方法,自行处理insets。
需要说明的是,如果不做任何处理,所有view接收到的insets都是一样的(比如top是status bar的高度,bottom是软键盘的高度)。该方法的执行在layout之前。
WindowInsets
该类封装了几种不同的insets。 mSystemWindowInsets 对应status bar、软键盘等引起的insets。可用方法如下:
获取四个边的inset
消费掉insets,使之不再传递
生成新的WindowInsets对象
该方法会被第一个调用,如果设置了listener,则执行自定义的listener,否则执行 onApplyWindowInsets 。
默认情况下该方法会执行第一个分支,即执行上面的 fitSystemWindows 。api20以上,android建议覆写该方法,而不是已废弃的 fitSystemWindows 。
监听fitSystemWindow事件。
listener类如下:
ViewGroup:
可以看到,从根布局开始,先执行本身的 super.dispatchApplyWindowInsets 方法,然后遍历执行子View的 dispatchApplyWindowInsets 方法,如果被消费掉,则停止传递。
布局如下:
设置沉浸式:
设置软键盘适配方式:
现在布局是这个样子的:
图1标题栏被状态栏遮挡,图2页面被软键盘遮挡。
再次强调一个概念,默认情况下,设置 android:fitsSystemWindows="true" 只有一个View会生效。
为根布局设置 android:fitsSystemWindows="true" ,同时为了方便观察,给根布局设置一个灰色背景:
可以看到已经适配了软键盘,但顶部toolbar区域也显示了根布局的灰色背景,显然默认实现满足不了我们的需求。
解决方式有很多,这里介绍两种比较优雅的方式。
首先需要为Toolbar也设置 android:fitsSystemWindows="true"
达到了预期效果。
自定义根布局
自定义toolbar
两种方法实际上是等价,不过显然还是第一种方式更友好,只需要设置一个listener就能搞定,但因为api版本限制,所以很多情况下还是要使用第二种方式。
如果覆写了 fitSystemWindows(insets) 或者 onApplyWindowInsets(WindowInsets) ,覆写方法中不调用对应的super方法,则不需要设置 setFitsSystemWindows(true) 或者 android:fitsSystemWindows="true" 。
原因如下:
2. ViewPager系列文章(一)- ViewPager源码分析及加载页面原理图
1>:点击 viewPager.setAdapter进入下边源码,会调用 populate() 方法,这个方法作用是创建和销毁子条目(子item):
在populate()方法中:
创建ItemView:mAdapter.instantiateItem(this, position);
销毁ItemView:mAdapter.destroyItem(this, pos, ii.object);
所以由ViewPager的源码可以看出,ViewPager里边无论放多少个页面都不会内存溢出,它会不断的去创建和销毁view;
和 ListView、RecyclerView不一样,ListView、RecyclerView是会不断的复用view,而viewpager是不断的创建和销毁view
轮播图刚打开默认显示当前页,是第一页,默认会缓存左右两个页面,如果左边没有,只有右边有,那么右边是第0页,当前页是第一页;
如果你滑动到第1页,ViewPager会默认把 左边第0页 和 右边第2页 创建出来;
如果你滑动到第2页,ViewPager会默认把第1页和第3页创建出来,而原来的第0页就会变成需要销毁的页面;
如果想要缓存多页,可以调用setOffscreenPageLimit()方法:
setOffscreenPageLimit(1):ViewPager机制默认就是缓存1,表示左边、右边各缓存1页,加上自己,总共是3页,其余页面全部销毁;
setOffscreenPageLimit(2):表示默认给左右各缓存2页,共4页,加上自己,总共缓存5页,其余页面全部销毁;
setOffscreenPageLimit(3):表示默认给左右各缓存3页,共6页,加上自己,总共缓存7页,其余页面全部销毁;
因为 smoothScrollTo()滑动方法也调用populate(),而populate()方法维护了当前显示页面和 左右缓存的页面,就能做到无限滑动而不出问题;
A:从populate()源码中可知:先判断页面是否在缓存范围内:如果在,则addNewItem添加进来,否则在destroyItem掉;
B:ViewPager会缓存左右两边页面+1(当前显示页面),默认认为当前页面的 左右两边各有1个,用户可以手动调用setOffscreenPageLimit()方法设置数量,如果传的值小于1,就默认设置为1;
ViewPager实际示意图如下: