flutter第三篇:布局中
13、AspectRatio、Card、CircleAvatar、Chip
如果想调整一个组件的宽高比,那么可以用AspectRatio包裹它,用其aspectRatio属性指定宽高比。
Chip是标签,看起来跟TextButton差不多,但是自带边框、边框自带圆角,边框内有文字,文字前后可放图标,点击后置图标可以触发事件。
label指定边框内的文字,值是任意Widget,一般是个Text。想修改文字的样式,需要用labelStyle属性,值是个TextStyle。要想在文字上下左右加点padding,需要用labelPadding属性。
想要在文字前面放一个图标,需要用avatar属性,值也是任意Widget。想要在文字后面放一个图标,需要用deleteIcon属性,值也是任意Widget。想自定义后置按钮颜色,需要用deleteIconColor属性。点击后置按钮时,触发onDeleted事件。长按后置按钮时,会弹出deleteButtonTooltipMessage。想要在后置按钮周围加padding,需要用padding属性。想自定义边框内背景颜色,需要用backgroundColor属性。利用visualDensity属性可以微调Chip的宽高。想自定义边框,需要用shape属性,值是个OutlinedBorder,如修改边框颜色为红色,边框粗细为2,边框圆角半径为20,则赋值为RoundedRectangleBorder(side: const BorderSide(color: Colors.red, width: 2), borderRadius: BorderRadius.circular(20))。注意,label、avatar、deleteIcon、labelPadding、padding都在边框内部,Chip的宽高是由内部撑起的。
14、ElevatedButton、TextButton、OutlinedButton、IconButton
ElevatedButton
ElevatedButton是凸起按钮,默认有灰色背景无边框,child是任意Widget,可以是图标Icon,也可以是文本Text。ElevatedButton的默认形状与胶囊的俯视图类似。默认情况下,ElevatedButton的宽高是根据子元素宽高自适应的,如果child是一个Text,则文字越多,ElevatedButton越宽,文字越大,ElevatedButton越高。默认情况下,ElevatedButton是有padding和margin的,也是有最小宽高的,这就导致了ElevatedButton的边框并不能紧紧包裹child,且ElevatedButton与其他组件有间距或者与父组件的边界有间距,通过其style属性可以调整。style属性值是个ButtonStyle实例,ButtonStyle实例可以通过ButtonStyle()创建,也可以通过调用ElevatedButton.styleFrom()静态方法创建,建议使用后者。ButtonStyle有很多实用属性,如下:
shape:用于设置外边框的形状,值是个OutlinedBorder实例。OutlinedBorder常用子类有RoundedRectangleBorder、BeveledRectangleBorder、ContinuousRectangleBorder、CircleBorder、OvalBorder、LinearBorder、StadiumBorder、StarBorder。
①RoundedRectangleBorder是默认值,是圆角矩形,可通过其side属性设置边框的颜色和宽度,borderRadius属性设置边框的弧度,不设置弧度的话,是矩形,设置的话,可以分别设置左上、左下、右上、右下4个角的弧度,如BorderRadius.only(topLeft: Radius.circular(10))设置左上角的弧度,BorderRadius.all(Radius.circular(10))设置4个角的弧度。
②BeveledRectangleBorder,斜角矩形,Beveled的意思是斜角。在不设置borderRadius时,也是矩形,设置borderRadius时,好像把矩形的直角砍一刀一样。
③ContinuousRectangleBorder,在不设置borderRadius时,也是矩形,设置borderRadius时,就是一个弧度比较小的RoundedRectangleBorder。
④CircleBorder,圆形。eccentricity大于0时,这个圆会在水平方向上拉伸,变成椭圆,值越大,拉伸的越厉害。eccentricity的意思是离心率。
⑤OvalBorder,椭圆形,eccentricity大于0时,这个椭圆会在水平方向上压缩,值越大,压缩的越厉害。eccentricity值为1,会变成圆。OvalBorder与CircleBorder正相反。
⑥LinearBorder,矩形,需要显式指定四个方向上的边框,哪个方向不指定哪个方向上就没有边框。如想有且仅有下边框,则需要设置bottom为LinearBorderEdge。LinearBorderEdge有个size属性,取值范围是0到1,默认值是1,表示这个方向上的边框的全部。如果设为0.5,则边框只会有一半,且以中间为对称轴。
⑦StadiumBorder,体育场形,Stadium的意思是体育。类似于RoundedRectangleBorder,但不能设置弧度,弧度看起来固定是个半圆。
⑧StarBorder,默认是五角星形,角的数量可以通过points调整。
padding:用于设置外边框与child的间距。当内容很短时,如果padding设置得比较小,那么可能看不出效果,因为Button默认有最小宽高,此时若想看到效果,还需调整minimumSize属性,设置最小宽度为0。当内容较长时,很容易看到效果。如child是Text("123456789"),当padding设置为EdgeInsets.zero时,可以让边框在水平方向上紧紧包裹文本,但是在竖直方向上不行。
alignment:child的位置,默认是居中。实测,只能控制在竖直方向上的位置,水平方向上的位置控制不了。如果想调整child在水平方向上的位置,有两种方法:第一种,用Align包裹child,设置Align的alignment。第二种,把child放到Row中,设置Row的mainAxisAlignment或者在此child前/后放Spacer。
minimumSize:设置Button的最小尺寸,不设置的话,最小宽度和最小高度都有默认值。最小宽度设为0的话,可以让边框在水平方向上紧紧包裹文本。最小高度设置0的话,可以让边框在竖直方向上紧紧包裹文本。综上,设置padding为0、minimumSize为0,即可让边框仅仅包裹文本。
maximumSize:设置Button的最大尺寸,设置后,即使Text文字再多,Button也不会超出这个尺寸。多出来的文字会丢。
fixedSize:设置Button的尺寸。设置后,Button固定为这个尺寸。即肉眼可见这么大。
tapTargetSize:设置Button的可点击区域大小,默认是MaterialTapTargetSize.padded。如果用Container包裹时,Button与Container有间距无论如何都去不掉,则可以尝试设置为MaterialTapTargetSize.shrinkWrap。
backgroundColor:用于设置背景色。
foregroundColor:用于设置前景色,即文本或图标颜色。
overlayColor:用于设置按钮按下时的背景色,如果想让按钮按下前后背景色不一样,则需要用这个属性。
splashFactory:设置为NoSplash.splashFactory可以去除水波纹效果。
有个问题解决不了:
①如果调大文本的大小,文本如何在竖直方向上居?
如文本是50的aaa,50的bbb
OutlinedButton
OutlineButton与ElevatedButton类似,只不是ElevatedButton是有背景无边框,而OutlinedButton是有边框无背景。OutlineButton常用的属性,除了必需的onPressed和child外,还有style,值也是个ButtonStyle实例,常用属性及效果可参考ElevatedButton。
TextButton
TextButton默认只能看见child,看不见背景、边框等,按下去,才能看到背景,才知道原来是个按钮。child是任意Widget。
TextButton常用的属性,除了必需的onPressed和child外,还有style,值也是个ButtonStyle实例,常用属性及效果可参考ElevatedButton。
IconButton
IconButton和TextButton一样,默认背景透明,只能看见icon,看不见背景、边框等,按下去,才能看到背景,才知道原来是个按钮。icon是任意Widget。IconButton和TextButton的区别是,IconButton的icon一般是个Icon,而TextButton的child一般是个Text。IconButton的背景区域默认是一个圆,大小是48×48。
IconButton常用的属性,除了必需的onPressed和icon外,还有:
iconSize,用于指定Icon的大小。
color,用于指定Icon的颜色。
highlightColor,用于指定IconButton长按时背景区域的颜色。
padding,默认是8px。通过调整padding的大小,可以调整IconButton背景区域的大小以及icon的位置。
alignment,icon在IconButton的位置,默认是Alignment.center。
style,值也是个ButtonStyle实例,常用属性及效果可参考ElevatedButton。
让IconButton中的icon居上:
Container(
height: 50,
color: Colors.blueAccent,
child: IconButton(
icon: Icon(Icons.star),
padding: EdgeInsets.zero, // 去掉内边距
alignment: Alignment.topCenter, // 对齐到顶部
onPressed: () {},
),
)
以上padding和alignment是关键。
ElevatedButton、TextButton、OutlinedButton都有一个icon()构造方法,可用于实现【在一个按钮中,左侧是图标,右侧是文本】的效果。用icon属性指定图标,用label属性指定文本。
默认大小的圆形按钮和自定义大小的圆形按钮示例:
@override
Widget build(BuildContext context) {
return ListView(
children: [
Row(
children: [
ElevatedButton(
onPressed: () {},
style: ButtonStyle(
shape: WidgetStateProperty.all(const CircleBorder(
side: BorderSide(
color: Colors.yellow,
)))),
child: const Text("圆形按钮"),
),
Container(
width: 120,
height: 120,
child: ElevatedButton(
onPressed: () {},
style: ButtonStyle(
shape: WidgetStateProperty.all(const CircleBorder(
side: BorderSide(
color: Colors.yellow,
)))),
child: const Text("自定义大小圆形按钮"),
),
),
],
),
],
);
}
15、Wrap
Wrap是一行或一列,默认是一行。这个行和Row的区别是在Row中放很多元素时,如果超过屏幕宽度,会溢出,而Wrap不会,Wrap会自动换行。Wrap和横向的ListView也不一样,横向的ListView在超出屏幕宽度时,会有滚动条,而Wrap不会有滚动条,会换行。
Wrap是不会溢出的,也是不会有滚动条的。即使换了很多行,超过了屏幕的高度,也不会溢出,也不会有滚动条,超出屏幕的就看不到了。
Wrap的alignment属性,用于设置主轴元素的对齐方式,可选值是WrapAlignment.start、end、center、spaceBetween、spaceAround、spaceEvenly,等同于Row的MainAxisAlignment。
Wrap的crossAlignment属性用于设置交叉轴元素的对齐方式,具体来说就是当一行的元素高度有不同时,元素在竖直方向的对齐方式,如一行有三个container,最左边宽高均为10,中间宽高均为20,最后面宽高均为30,那么由于默认情况下crossAlignment为WrapCrossAlignment.start,所以这三个container是顶对齐,即顶部在同一水平线上。如果crossAlignment设置为end,那么则会底对齐,即底部在同一水平线上。如果设置center,则这三个container的中心会在同一水平线上。
Wrap的spacing属性用于调整在水平方向上元素的间距,默认没有间距。Wrap的runSpacing属性用于调整行间距,默认没有间距。
如果Wrap里面有Row,则Row会单独一行。
16、StatefulWidget
略
21、自定义KeepAliveWrapper
上例中用到了KeepAliveWrapper,这是个自定义的类,用于缓存页面。当在多个tab间切换时,假如在tab1的页面浏览到了一定位置,切到其他tab,一顿乱切,最终又切回tab1,默认情况下,tab1的页面浏览的位置会重置,即tab1的页面会从头开始展示。而我们用KeepAliveWrapper包裹ListView后,在切回tab1的时候,就可以看到之前在tab1的页面浏览的位置。KeepAliveWrapper只有搭配TabBarView或者PageView时才生效,其他情况下不生效。
keep_alive_wrapper.dart代码如下:
import 'package:flutter/material.dart';
class KeepAliveWrapper extends StatefulWidget {
const KeepAliveWrapper(
{super.key, @required this.child, this.keepAlive = true});
final Widget? child;
final bool keepAlive;
@override
State<KeepAliveWrapper> createState() => _KeepAliveWrapperState();
}
class _KeepAliveWrapperState extends State<KeepAliveWrapper>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return widget.child!;
}
@override
bool get wantKeepAlive => widget.keepAlive;
@override
void didUpdateWidget(covariant KeepAliveWrapper oldWidget) {
if (oldWidget.keepAlive != widget.keepAlive) {
updateKeepAlive();
}
super.didUpdateWidget(oldWidget);
}
}
22、普通路由
23、命名路由
24、路由跳转、路由替换
25、Dialog、AlertDialog、SimpleDialog、showModalBottomSheet、showToast、showMenu
调用showDialog函数会弹框。其builder属性是个返回任意Widget函数,可返回Container,也可返回预置的一些弹框组件,如Dialog、AlertDialog、SimpleDialog。barrierColor属性如果不设置,则会有遮罩。如果设为某种颜色,如黄色,则弹框底下的整个屏幕会变成一个黄色的背景,没有任何内容。如果想实现淘宝空购物车结算的那种弹框,则需要设置barrierColor属性值为透明。默认情况下,点击弹框外面的内容,弹框会自动消失,如果不想消失,则需要设置barrierDismissible属性值为false。Dialog是一个自动居中的弹框,可以非常简单地实现一个确定、取消确认框。
showGeneralDialog函数,
AlertDialog可以用来实现提示框(样式如删除确认对话框)。
SimpleDialog可以用来实现select选择框。
showModalBottomSheet函数可以用来实现底部弹框。默认情况下,弹框高度是固定的,且点击弹框外部可以关闭弹窗。设置isDismissible属性为false,则可禁止在弹窗外部关闭。设置isScrollControlled为true,则弹窗内部内容多的话,弹窗就高,内容少的话,弹窗就低。
有一个开源的第三方包fluttertoast,也可以用来实现底部弹框。
showMenu函数可以实现弹出下拉菜单的效果。items属性是菜单集合,集合元素类型是PopupMenuItem。position属性用于设置菜单出现的位置,值可以是RelativeRect.fromLTRB()。
26、自定义Dialog
27+28、PageView、AutomaticKeepAliveClientMixin
PageView可以用来实现滚屏和轮播图。所谓滚屏,指的是像抖音推荐页那种可以一直往下滑。
PageView的children里面有多少元素,就代表有多少屏,可以用手左右滑或上下滑来切屏。scrollDirection属性决定了滚屏的方向,默认是左右滚屏,即默认展示第一屏,用手往左滑,会切到第二屏、第三屏,直到最后一屏。scrollDirection设置成Axis.vertical,则会上下滚屏,用手往上滑,会切到第二屏、第三屏,直到最后一屏。利用onPageChanged属性可配一个回调函数,入参是切到的屏的索引,以对切屏进行监听。如果想让pageView切到特定一屏,则需要利用其controller属性,赋值为一个PageController实例,调用pageController的jumpToPage()方法或者animateToPage()即可,传入要切的屏的索引,前者直接切过去,后者带动画。curve可以赋值为Curves.ease,直接在Curves后面打点可以看到很多动画效果。
利用构造函数生成的PageView实例不支持从最后一屏直接滑到第一屏,想要实现此功能,必须得用PageView的builder()命名构造器生成PageView实例,此时需要在itemBuilder函数中指定组件。itemBuilder函数第二个参数是左滑的步数,我们可以通过步数对屏数取余来拿到屏的索引,进而返回对应这个索引的屏,如返回某一张图片,返回某个page。用itemCount指定步数的最大值,如果itemCount等于屏数,那么滑到最后一屏时,再往左滑就滑不动了。如果itemCount比屏数大1,那么从最后一屏可以滑到第一屏,但是再往左滑,就滑不动了。不指定itemCount,则可以无限滑。在往左滑时,onPageChanged函数的入参index会从1开始一直递增,即使从最后一屏滑到第一屏,index也不会变成0,而是继续加1。假如我们有3屏,或者说要轮播的图片有3个,那么从第一屏滑到第二屏、从第二屏滑到第三屏时,onPageChanged函数中入参index的值依次为1、2,而从第三屏滑到第一屏时,onPageChanged函数中入参index的值为3,而不是0。也就是说,这个入参index,不是屏的索引,而是往左滑的步数,即往左滑就加1,往右滑就减1,和屏的索引是两码事。假如我们的轮播图底部有指示器,那么指示器的个数得和屏数一样。如果要让当前屏对应的指示器的颜色为红色,其他指示器的颜色为白色,那么在配置指示器的颜色属性时可以使用三目表达式,如果指示器的索引等于当前屏的索引,则为红色,否则为白色。
要想让轮播图底部有指示器,如小圆形,需要用Stack,其children中第一个元素是用Container包裹的PageView(用Container包裹PageView,主要是为了限定其高度,否则其高度就等于屏幕的高度,这明显不合适),第二个元素是一个Positioned,里面包裹一个Row,Row里面放一定数量的小圆形。为了让小圆形在水平方向上居中,需要设置Row的主轴对齐方式为居中,且Positioned的left、right属性均需要设置为0(Row在被Positioned包裹时,当设置了Positioned的top、bottom、left、right这四个属性中的任何属性时,Row的mainAxisSize都会失效,此时Row的宽度取决于Positioned的left、right、width)。为了让小圆形在竖直方向上靠近Container的底部,需要适当调整Positioned的bottom属性值。
如果想让轮播图自动滚,那么需要创建一个定时器,当到了时间后,根据当前屏的索引判断当前屏是否是最后一屏,如果是,就跳到第一屏,否则跳到下一屏。
pageView各屏的数据也可以缓存,用KeepAliveWrapper包裹各屏的组件就好了。
其实,我们可以用现成的组件来实现轮播图,flutter_swiper_view。参考文中示例即可快速实现一个轮播图。轮播图默认的指示器是圆点,如果想要变成横线,则需要给SwiperPagination的builder属性赋值为一个RectSwiperPaginationBuilder实例,通过这个实例的activeColor属性和color属性设置选中和未选中的指示器的颜色。
Swiper常用属性有:
itemCount,指定图片的个数。
autopaly,是否自动轮播,默认为false。
loop,是否循环播,即播到最后时,是否再从头播,默认是true。
layout,默认是SwiperLayout.DEFAULT,可选值还有STACK、TINDER、CUSTOM。如果值为STACK,则必须设置itemWidth,效果是多张图片堆叠在一起,在水平方向上可以看到堆叠效果,从上面抽一张,放到最下面,好像洗扑克牌一样,但是有个动效,就是在展示下一张牌之前,先展示一下上一张牌。如有三张牌,A、B、C,把A从最上面抽走,放到C下面,在展示B之前先展示C。如果值为TINDER,则必须设置itemWidth和itemHeight,效果同STACK类似,只不过堆叠时,会在竖直方向上看到堆叠效果。如果值为CUSTOM,则必须设置customLayoutOption和itemWidth。
viewportFraction,表示视口比例,默认是1,此时视口只展示1张图片,且图片不会缩放。值大于1时,视口只展示1张图片,且图片会放大。值小于1时,视口会展示三张图片,且中间图片宽度较大,左右图片宽度相等、较小,如值为0.8,则中间图片的宽度占屏幕宽度的0.8,左右各占0.1。值为0.6,则中间图片的宽度占屏幕宽度的0.6,左右各占0.2。
scale,表示缩放比例,在viewportFraction值小于1时生效,默认是1。当scale值大于1时,左边图片宽度不变,但是图片会放大,中间图片宽度会变小,右边图片宽度会变大。如viewportFraction值为0.6,scale值为2.0,则左边图片的宽度是屏幕宽度的0.2,右边图片的宽度是 屏幕宽度的0.2*2,即0.4,中间图片的宽度是1-0.2-0.4=0.4。当scale值小于1时,中间图片不变,左右两侧的图片的宽和高会缩小,此时在水平方向上图片会有间距。通过调整scale的值,即可以调整图片的间距。
更火的第三方组件是carousel_slider,功能更强大。
29、key
local key有ValueKey、UniqueKey。ValueKey的值不能相同,否则在运行时会报Duplicate keys found。UniqueKey会随机生成一个唯一值。
global key有GlobalKey。GlobalKey自动全局唯一。globalKey有currentWidget、currentState、currentContext属性。
调用globalKey的currentWidget属性可以获取对应组件,需要把返回值强转为组件类型,也就是那个我们自定义的StatelessWidget或StatefulWidget的子类类型,如var box = gk1.currentWidget as Box,这个用处比较小。
调用globalKey的currentState属性可以获取对应组件的状态,需要把返回值强转为组件状态类型,也就是那个我们自定义的StatefulWidget的子类衍生的_xxxState,如var boxState = gk1.currentState as _BoxState,拿到状态的实例后,就可以访问_xxxState的属性和方法了。
调用globalKey的currentContext属性,可以有很多用途:
①用于Scrollable的区域跳转。如上述讲解。
②获取对应组件的宽高、位置。获取宽高时,既可以用gk1.currentContext?.size拿到Size实例,进而获取其宽高,又可以通过gk1.currentContext?.findRenderObject()拿到RenderObject实例,然后强转为RenderBox类型,然后调用其localToGlobal(Offset.zero)方法获取Offset实例,进而获取其宽高。通过Offset实例还可以获取其位置。注意,获取宽高、位置时,需要在组件加载好之后才行,否则gk1.currentContext?.size、gk1.currentContext?.findRenderObject()均会返回null。比如,在initState()方法或onInit()方法或onReady()方法中调用,就会返回null。可以在监听中调用,如对滚动条的监听。
var renderBox = gk1.currentContext?.findRenderObject() as RenderBox;
print(renderBox.size.width);
print(renderBox.size.height);
print(renderBox.localToGlobal(Offset.zero).dx);
print(renderBox.localToGlobal(Offset.zero).dy);
renderBox.size.width获取组件的宽,renderBox.size.height获取组件的高,renderBox.localToGlobal(Offset.zero).dx获取组件与屏幕左侧的间距,renderBox.localToGlobal(Offset.zero).dy获取组件与屏幕顶部的间距。
30、AnimatedList
利用AnimatedList可以实现在删除列表元素时有动画效果。
AnimatedList是一个StatefulWidget,它对应的State类型为AnimatedListState,添加和删除元素的方法位于AnimatedListState中:
void insertItem(int index, {Duration duration = _kDuration})
void removeItem(int index, AnimatedRemovedItemBuilder builder, {Duration duration = _kDuration})
如在评论列表中删除某条评论。假设评论列表是List<String> list = ["a","b","c"]。列表组件就用AnimatedList,itemBuilder函数返回一个能展示文本(list[index])的组件,如ListTile。key赋值为一个预定义的GlobalKey<AnimatedListState>(),如k1。initialItemCount值是list.length。
添加元素时,需要调用k1.currentState.insertItem(),第一个参数是新添加元素在列表中的索引,添加后会把原来该索引处及之后的元素都往后移。insertItem要伴随着list.insert(index)。
删除某元素时,需要调用k1.currentState.removeItem(),第一个参数是要删除的元素在列表中的索引,第二个参数是AnimatedRemovedItemBuilder,是个函数,Widget Function(BuildContext context, Animation<double> animation),此函数需要返回一个动画组件,如xxxTransition,如FadeTransition(动画效果是浅入浅出)、ScaleTransition(动画效果是缩放)、SlideTransition(添加元素的动画效果是从右往左出,删除元素的动画效果是从左往右收)。以返回ScaleTransition为例,ScaleTransition的scale属性值即为AnimatedRemovedItemBuilder的第二个入参animation,child属性值即为要删除的元素,元素用元素的key.currentWidget指定。removeItem要伴随着list.removeAt(index)。
MediaQuery.sizeOf(context),可以获得设备屏幕的宽高。
MediaQuery.orientationOf(context),可以看设备是横屏还是竖屏。比如红米手机,正常情况下返回竖屏,旋转屏幕后,返回横屏。
ConstrainedBox用于对子组件添加额外的约束,用于组件没有constraints属性的情况,就好像Padding用于组件没有padding属性的情况。
OverflowBox、SizedOverflowBox:用于允许子组件超出父组件边界的情况。
LimitedBox用于指定最大宽高。
FittedBox:用于自定义子组件适配父组件的规则。
Flexible是Expanded的父类。
用RotationTransition可以创建一个旋转的组件。Rotation的意思是旋转。想把一个组件旋转,用RotationTransition包裹这个组件,设置其turns属性指定偏转角度即可,如指定为AlwaysStoppedAnimation(15/360),即表示顺时针旋转15°。
安全区域SafeArea,安全区域是屏幕除顶部状态栏和底部功能栏的区域,
@override Widget build(BuildContext context) { return SafeArea( child: Container( color: Colors.red, child: const Text("我是安全的文本"), ), ); }
如果想设置安全区域之外区域的背景色(即屏幕顶部和底部),则需要在main函数中调用SystemChrome.setSystemUIOverlayStyle(),入参是一个SystemUIOverlayStyle实例,创建这个实例时,可以指定状态栏区域的背景色、系统导航栏区域的背景色,一般设置为透明即可,系统导航栏区域背景色就跟body背景色一样了,如下:
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
systemNavigationBarColor: Colors.transparent,
));
通过MediaQuery.of(context)获得的是MediaQueryData,MediaQueryData有三个常用属性:
1、viewInsets。viewInsets是被系统UI(主要是键盘)遮挡的屏幕区域,值是个EdgeInsets。正常情况下,四个方向上的值都是0。当键盘弹出时,bottom属性值是键盘的高度。可以调用MediaQuery.viewInsetsOf(context)直接获得viewInsets。
2、viewPadding。viewPadding是刘海区域、状态栏。可以调用MediaQuery.viewPaddingOf(context)直接获得viewPadding。
3、padding。等于viewPadding-viewInsets。可以调用MediaQuery.paddingOf(context)直接获得padding。
浙公网安备 33010602011771号