🔥邀你共赏登录界面样式🔥
|
🔥这样的流程图你见过吗🔥
💋约会可能发生的5件小事💋
一起去电影院看电影
一起穿情侣装逛街
一起去迪士尼乐园(世界上有6个迪士尼乐园,要和同一个人去6次哦)
一起去游泳
一起唱歌并且录下来
🔥Flutter 2022 产品路线图🔥
🔥 直奔主题创建 Module🔥
通过Android Studio开发者工具直接创建
通过命令创建
flutter create -t module flutter_fire_utils
添加加载图片、打印日志、导航…等工具类
🔥创建budligapp工程🔥
通过命令创建
flutter create budligapp
编辑 pubspec.yaml 引入 flutter_fire_utils module
🔥工程组件化加载assets图片🔥
const _isRunAlone = bool.fromEnvironment('IS_RUN_ALONE', defaultValue: false);
///加载图片
Widget imageAsset(assetName, {imgWidth, imgHeight, fitType}) {
return Image.asset(
_isRunAlone
? 'assets/images/$assetName.png'
: 'packages/flutter_fire_utils/assets/images/$assetName.png',
width: imgWidth,
height: imgHeight,
fit: fitType,
);
}
🔥 BackWidget布局🔥
import 'package:flutter/material.dart';
import 'package:flutter_fire_utils/utils/imgload_utils.dart';
import 'package:flutter_fire_utils/utils/log_util.dart';
///🔥🔥🔥点击关闭登录
class BackWidget extends StatelessWidget {
const BackWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
margin: const EdgeInsets.only(top: 44.0, bottom: 8.0),
color: Colors.transparent,
width: 64.0,
height: 48.0,
child: GestureDetector(
child: imageAsset('login_close'),
onTap: () {
logV('LoginPage _BackWidget build login_close');
添加BackWidget子布局到LoginPage并运行main.dart 文件
import 'package:flutter/material.dart';
import 'package:flutter_budligapp/view/login/agree_widget.dart';
import 'package:flutter_budligapp/view/login/phone_num_widget.dart';
import 'package:flutter_budligapp/view/login/send_code_widget.dart';
import 'package:flutter_budligapp/view/login/title_widget.dart';
import 'back_widget.dart';
class LoginPage extends StatefulWidget {
const LoginPage({
Key? key,
}) : super(key: key);
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: null,
backgroundColor: Colors.blue.withOpacity(0.2),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const <Widget>[
BackWidget(),
//TitleWidget(),
//PhoneNumWidget(),
//SendCodeWidget(),
//AgreeWidget(),
],
),
);
}
}
🔥 TitleWidget🔥
class TitleWidget extends StatelessWidget {
const TitleWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return const Padding(
padding: EdgeInsets.only(left: 20.0, right: 20.0),
child: Text(
'🔥🔥🔥🔥🔥',
style: TextStyle(
fontSize: 28.0,
fontWeight: FontWeight.bold,
color: Color(0xffFFFFFF)),
),
);
}
}
🔥 PhoneNumWidget 🔥
自定义编辑框 cus_text_field.dart
@startmindmap
* 编辑框功能
* 颜色
* 编辑框默认文本颜色
* 编辑框输入文本颜色
* 编辑框光标颜色
* 文本
* 编辑框默认提示文本
* 编辑框前面标题
* 边框
* 编辑框输入文本大小
* 编辑框文本位置(左边|右边|中间)
* 编辑框输入文本格式(文本|数字|邮箱...)
* 编辑框输入文本行数(1行还是多行)
* 编辑框输入文本长度(即输入字符长度)
* 编辑框一键删除图片
* 编辑框输入完成CallBack
@endmindmap
创建图片资源文件夹 , 复制1.0x、2.0x、3.0x多倍图到资源文件夹下
pubspec.xml 文件下添加图片资源的依赖
import 'package:flutter/material.dart';
import 'package:flutter_fire_utils/utils/imgload_utils.dart';
import 'package:flutter_fire_utils/utils/log_util.dart';
///图标类型+黑色图标+白色图标
enum TYPE { fieldDelWhite, fieldDelBlack }
///自定义编辑
class CusTextField extends StatefulWidget {
final InputValueCallBack? _inputValueCallBack;
///编辑框输入颜色值
final int inputColorValue;
///默认文本的颜色值
final int hintColorValue;
///编辑框默认提示文本
final hintText;
///边框
final border;
///标题
final Widget? labelText;
///编辑框输入文本大小
final inputFontSize;
///文本位置(左边|右边|中间)
final TextAlign? textAlign;
final keyboardType;
//文本行数
final int? maxLine;
///一键删除图片
final fieldDel;
///光标颜色
final cursorColor;
final crossAxisAlignmentType;
///编辑框文本长度
final int? maxLength;
final inputFontWeight, hintFontWeight;
const CusTextField(this._inputValueCallBack,
{this.inputColorValue = 0xffFFFFFF,
this.hintColorValue = 0xffFFFFFF,
this.hintText = '',
this.border,
this.labelText,
this.inputFontSize,
this.textAlign = TextAlign.right,
this.maxLine = 1,
this.keyboardType,
this.fieldDel,
this.cursorColor = 0xff000000,
this.crossAxisAlignmentType = CrossAxisAlignment.center,
this.maxLength,
this.inputFontWeight = FontWeight.bold,
this.hintFontWeight = FontWeight.bold});
@override
_CusTextFieldState createState() => _CusTextFieldState();
}
class _CusTextFieldState extends State<CusTextField> {
//定义一个controller
late TextEditingController _controller;
bool _isShoDel = false;
///是否获取焦点
bool _isFocus = false;
final FocusNode _focusNode = FocusNode();
var _fieldDel = 'field_del_white';
@override
void initState() {
// TODO: implement initState
super.initState();
switch (widget.fieldDel) {
case TYPE.fieldDelWhite:
_fieldDel = 'field_del_white';
break;
case TYPE.fieldDelBlack:
_fieldDel = 'field_del_black';
break;
}
_controller = TextEditingController();
_controller.addListener(() {
_inputContro(_controller.text, false);
});
_focusNode.addListener(() {
logV('输入框是否获取焦点: ${_focusNode.hasFocus}');
setState(() {
_isFocus = _focusNode.hasFocus;
});
});
}
void _inputContro(v, bool isInput) {
///编辑框输入文本长度
int _valueLength = '$v'.length;
logV('输入框输入监听 文本长度: $_valueLength');
///编辑框输入文本大于0
_isShoDel = (_valueLength > 0);
///编辑框文本输入文本存在值时或者等于为空时刷新编辑框
if (_valueLength <= 1 && isInput) {
setState(() {});
logV('CusTextField_刷新编辑框');
}
///粘贴
if (!isInput) {
setState(() {});
}
_inputValue(v);
}
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: widget.crossAxisAlignmentType,
children: [
widget.labelText ?? Container(),
Expanded(
flex: 1,
child: TextField(
textAlign: (widget.textAlign)!,
maxLines: widget.maxLine!,
maxLength: widget.maxLength,
focusNode: _focusNode,
///光标颜色
cursorColor: Color(widget.cursorColor),
///编辑框首次不自动获取焦点
autofocus: false,
keyboardType: widget.keyboardType ?? TextInputType.number,
style: TextStyle(
fontSize: widget.inputFontSize ?? 0.0,
fontWeight: widget.inputFontWeight,
///文本输入或文本为空时的颜色值
color:
Color(_isShoDel ? (widget.inputColorValue) : 0xffFFFFFF)),
decoration: InputDecoration(
///默认文本
hintText: '${widget.hintText ?? ''}',
hintStyle: TextStyle(
color: Color(widget.hintColorValue),
fontWeight: widget.hintFontWeight,
),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
///边框
border: widget.border ?? InputBorder.none,
),
onChanged: (v) {
_inputContro(v, true);
},
controller: _controller, //设置controller
),
),
Offstage(
offstage: !(_isShoDel && _isFocus),
child: GestureDetector(
onTap: () {
_controller.clear();
_inputContro('', false);
},
child: Container(
alignment: Alignment.center,
width: 40.0,
height: 40.0,
color: Colors.transparent,
child: imageAsset(_fieldDel, imgWidth: 20.0, imgHeight: 20.0),
),
),
),
],
);
}
void _inputValue(v) {
String _curV = '$v'.replaceAll(' ', '');
logV('编辑框输入的值:$_curV');
///编辑框输入值
widget._inputValueCallBack!(_curV);
}
}
///编辑框输入值
typedef void InputValueCallBack(var inputValue);
class PhoneNumWidget extends StatelessWidget {
const PhoneNumWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
margin: const EdgeInsets.only(left: 20.0, right: 20.0, top: 20.0),
padding: const EdgeInsets.only(
left: 20.0,
right: 4.0,
),
child: CusTextField((v) {},
hintText: '请输入手机号码',
keyboardType: TextInputType.number,
hintColorValue: 0x80ffffff,
textAlign: TextAlign.start,
inputFontSize: 16.0,
fieldDel: TYPE.fieldDelWhite,
cursorColor: 0xffFFFFFF,
labelText: Container()),
decoration: BoxDecoration(
border: Border.all(color: const Color(0xffFFFFFF).withOpacity(0.3)),
borderRadius: const BorderRadius.all(Radius.circular(28.0))));
}
}
🔥 SendCodeWidget 🔥
用户点击倒计时 , 如果倒计时开始那么就不会再次发送验证码 , 无任何响应 .
如果第首次发送验证码 , 触发倒计时成功刷新按钮文本(N秒后重复发) .
如果倒计时完成 , 按钮文本变成 (重新发送) .
@startuml
!theme vibrant
:<size:20>用户点击;
while (<size:20>发送验证码)
: <size:20>开始发送验证码;
: <size:20>倒计时结束;
backward : <size:20>重新发送;
endwhile (<size:20>已经开始倒计时)
: <size:20>等待倒计时结束;
@enduml
倒计时工具类 count_down_time.dart
//定义变量
import 'dart:async';
import 'package:flutter/cupertino.dart';
Timer? _timer;
//倒计时数值
var _countdownTime = 0;
///开启倒计时
bool _startCountDownTime = false;
startCountdown(State state, ICouDowTimCallBack? callBack) {
if (!state.mounted) return;
///已经开始倒计时,不能重复倒计时
if (_startCountDownTime) return;
_startCountDownTime = true;
///倒计时总时间60秒
_countdownTime = 60;
///倒计时回调
callBack!(_countdownTime, _startCountDownTime);
///倒计时周期1秒钟
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
///倒计时时间等于0时,停止倒计时
if (_countdownTime < 1) {
_startCountDownTime = false;
timer.cancel();
} else {
///倒计时每次--
_countdownTime -= 1;
}
///倒计时回调
callBack(_countdownTime, _startCountDownTime);
});
}
///关闭倒计时
timerCancel() {
if (_timer != null) {
_timer!.cancel();
_timer = null;
_startCountDownTime = false;
_countdownTime = 0;
}
}
///倒计时
typedef void ICouDowTimCallBack(var _couDowTim, bool _staCouDowTim);
I/flutter (32303): AppInfo v 倒计时:60s后重发
I/flutter (32303): AppInfo v 倒计时:59s后重发
I/flutter (32303): AppInfo v 倒计时:58s后重发
I/flutter (32303): AppInfo v 倒计时:57s后重发
I/flutter (32303): AppInfo v 倒计时:56s后重发
I/flutter (32303): AppInfo v 倒计时:55s后重发
I/flutter (32303): AppInfo v 倒计时:54s后重发
I/flutter (32303): AppInfo v 倒计时:53s后重发
I/flutter (32303): AppInfo v 倒计时:52s后重发
I/flutter (32303): AppInfo v 倒计时:51s后重发
I/flutter (32303): AppInfo v 倒计时:50s后重发
I/flutter (32303): AppInfo v 倒计时:49s后重发
I/flutter (32303): AppInfo v 倒计时:48s后重发
I/flutter (32303): AppInfo v 倒计时:47s后重发
I/flutter (32303): AppInfo v 倒计时:46s后重发
I/flutter (32303): AppInfo v 倒计时:45s后重发
I/flutter (32303): AppInfo v 倒计时:44s后重发
I/flutter (32303): AppInfo v 倒计时:43s后重发
I/flutter (32303): AppInfo v 倒计时:42s后重发
I/flutter (32303): AppInfo v 倒计时:41s后重发
I/flutter (32303): AppInfo v 倒计时:40s后重发
I/flutter (32303): AppInfo v 倒计时:39s后重发
I/flutter (32303): AppInfo v 倒计时:38s后重发
I/flutter (32303): AppInfo v 倒计时:37s后重发
I/flutter (32303): AppInfo v 倒计时:36s后重发
I/flutter (32303): AppInfo v 倒计时:35s后重发
I/flutter (32303): AppInfo v 倒计时:34s后重发
I/flutter (32303): AppInfo v 倒计时:33s后重发
I/flutter (32303): AppInfo v 倒计时:32s后重发
I/flutter (32303): AppInfo v 倒计时:31s后重发
I/flutter (32303): AppInfo v 倒计时:30s后重发
I/flutter (32303): AppInfo v 倒计时:29s后重发
I/flutter (32303): AppInfo v 倒计时:28s后重发
I/flutter (32303): AppInfo v 倒计时:27s后重发
I/flutter (32303): AppInfo v 倒计时:26s后重发
I/flutter (32303): AppInfo v 倒计时:25s后重发
I/flutter (32303): AppInfo v 倒计时:24s后重发
I/flutter (32303): AppInfo v 倒计时:23s后重发
I/flutter (32303): AppInfo v 倒计时:22s后重发
I/flutter (32303): AppInfo v 倒计时:21s后重发
I/flutter (32303): AppInfo v 倒计时:20s后重发
I/flutter (32303): AppInfo v 倒计时:19s后重发
I/flutter (32303): AppInfo v 倒计时:18s后重发
I/flutter (32303): AppInfo v 倒计时:17s后重发
I/flutter (32303): AppInfo v 倒计时:16s后重发
I/flutter (32303): AppInfo v 倒计时:15s后重发
I/flutter (32303): AppInfo v 倒计时:14s后重发
I/flutter (32303): AppInfo v 倒计时:13s后重发
I/flutter (32303): AppInfo v 倒计时:12s后重发
I/flutter (32303): AppInfo v 倒计时:11s后重发
I/flutter (32303): AppInfo v 倒计时:10s后重发
I/flutter (32303): AppInfo v 倒计时:9s后重发
I/flutter (32303): AppInfo v 倒计时:8s后重发
I/flutter (32303): AppInfo v 倒计时:7s后重发
I/flutter (32303): AppInfo v 倒计时:6s后重发
I/flutter (32303): AppInfo v 倒计时:5s后重发
I/flutter (32303): AppInfo v 倒计时:4s后重发
I/flutter (32303): AppInfo v 倒计时:3s后重发
I/flutter (32303): AppInfo v 倒计时:2s后重发
I/flutter (32303): AppInfo v 倒计时:1s后重发
I/flutter (32303): AppInfo v 倒计时:0s后重发
I/flutter (32303): AppInfo v 倒计时:重新发送
🔥 AgreeWidget 🔥
创建图片资源文件并在 pubspec.yaml 里面添加依赖配置
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_fire_utils/utils/imgload_utils.dart';
///🔥🔥🔥选择或取消协议🔥用户协议🔥隐私政策
class AgreeWidget extends StatefulWidget {
const AgreeWidget({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _AgreeWidgetState();
}
}
class _AgreeWidgetState extends State<AgreeWidget> {
///🔥🔥🔥用户协议🔥隐私政策🔥💍
TapGestureRecognizer? _userAgreeTapGesRec, _priAgreeTapGesRec;
///选择或取消协议
bool _checkAgree = false;
@override
void initState() {
// TODO: implement initState
super.initState();
_userAgreeTapGesRec = TapGestureRecognizer();
_priAgreeTapGesRec = TapGestureRecognizer();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return RichText(
//文字居中
textAlign: TextAlign.center,
//文字区域
text: TextSpan(
children: [
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: GestureDetector(
onTap: () {
setState(() {
_checkAgree = !_checkAgree;
});
},
child: Container(
color: Colors.transparent,
padding: const EdgeInsets.only(
left: 20.0, top: 16.0, bottom: 16.0),
child: imageAsset(
_checkAgree ? 'login_check_agree' : 'login_uncheck_agree',
imgWidth: 20.0,
imgHeight: 20.0),
),
)),
const TextSpan(
text: "\t我已阅读并同意\t",
style: TextStyle(color: Color(0xffDFDFE3), fontSize: 12.0),
),
TextSpan(
text: "\t用户协议\t",
style: const TextStyle(color: Colors.white, fontSize: 12.0),
//点击事件
recognizer: _userAgreeTapGesRec!
..onTap = () {
},
),
const TextSpan(
text: "与",
style: TextStyle(color: Colors.grey),
),
TextSpan(
text: "\t隐私协议\t",
style: const TextStyle(color: Colors.white, fontSize: 12.0),
//点击事件
recognizer: _priAgreeTapGesRec!
..onTap = () {
},
),
],
),
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_userAgreeTapGesRec!.dispose();
_priAgreeTapGesRec!.dispose();
}
}
🔥 协议加载 🔥
文件 pubspec.yaml 添加 flutter_inappwebview 插件依赖 并执行 flutter pub get 命令加载依赖
自定义网页加载
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
class FluWebView extends StatefulWidget {
final String? title;
final String? url;
const FluWebView(this.title, this.url);
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _FluWebViewState();
}
}
class _FluWebViewState extends State<FluWebView> {
///网页加载进度值
double _loadProValue = 0.0;
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(title: Text(widget.title!),),
body: Column(
mainAxisSize: MainAxisSize.max,
children: [
SizedBox(
child: LinearProgressIndicator(
backgroundColor: const Color(0xFF1E88E5).withOpacity(0.2),
valueColor: const AlwaysStoppedAnimation(Color(0xFF1E88E5)),
value: _loadProValue,
),
height: 10.0,
width: MediaQuery.of(context).size.width,
),
Expanded(
child: InAppWebView(
initialUrlRequest: URLRequest(url: Uri.parse(widget.url!)),
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions()),
onWebViewCreated: (InAppWebViewController controller) {},
onLoadStart: (InAppWebViewController controller, Uri? url) {},
onLoadStop:
(InAppWebViewController? controller, Uri? url) async {},
onProgressChanged:
(InAppWebViewController controller, int progress) {
setState(() {
_loadProValue = progress / 100;
});
},
),
flex: 1,
)
],
),
);
}
}
查看用户协议
查看隐私政策
🔥创建登录按钮🔥
class LoginSubmitWidget extends StatelessWidget {
const LoginSubmitWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return GestureDetector(
onTap: () {
},
child: Container(
alignment: Alignment.center,
margin: const EdgeInsets.only(
top: 48.0, left: 20.0, right: 20.0, bottom: 182.0),
height: 56.0,
width: MediaQuery.of(context).size.width,
child: const Text(
'登录',
style: TextStyle(color: Color(0xffFFFFFF), fontSize: 18.0),
),
decoration: const BoxDecoration(
color: Color(0xff00D778),
borderRadius: BorderRadius.all(Radius.circular(28.0))),
),
);
}
}
🔥 屏幕旋转产生警告线警告线 🔥
错误提示代码
The overflowing RenderFlex has an orientation of Axis.vertical.
The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and
black striped pattern. This is usually caused by the contents being too big for the RenderFlex.
Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the
RenderFlex to fit within the available space instead of being sized to their natural size.
This is considered an error condition because it indicates that there is content that cannot be
seen. If the content is legitimately bigger than the available space, consider clipping it with a
ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex,
like a ListView.
The specific RenderFlex in question is: RenderFlex#d4782 relayoutBoundary=up1 OVERFLOWING:
needs compositing
creator: Column ← _BodyBuilder ← MediaQuery ← LayoutId-[<_ScaffoldSlot.body>] ←
CustomMultiChildLayout ← AnimatedBuilder ← DefaultTextStyle ← AnimatedDefaultTextStyle ←
_InkFeatures-[GlobalKey#37d67 ink renderer] ← NotificationListener ←
PhysicalModel ← AnimatedPhysicalModel ← ⋯
parentData: offset=Offset(0.0, 0.0); id=_ScaffoldSlot.body (can use size)
constraints: BoxConstraints(0.0<=w<=774.5, 0.0<=h<=392.7)
size: Size(774.5, 392.7)
direction: vertical
mainAxisAlignment: start
mainAxisSize: max
crossAxisAlignment: start
textDirection: ltr
verticalDirection: down
◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤
因为界面上被渲染得视图超过了指定的高度,那么我们需要在指定渲染的高度范围内进行滚动
OK 完美消除警告线提示
🔥 三方登录标题 🔥
import 'package:flutter/cupertino.dart';
///🔥🔥🔥三方登录标题
class ThreeLoginLabelWidget extends StatelessWidget {
const ThreeLoginLabelWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return SizedBox(
width: MediaQuery.of(context).size.width,
child: const Text(
'- 其他登录方式 -',
textAlign: TextAlign.center,
style: TextStyle(
color: Color(0xffDFDFE3),
fontSize: 10.0,
),
),
);
}
}
🔥 ThreeLoginWidget 🔥
三方登录QQ、微信、微博布局
pubspec.yaml 文件里面加入QQ、微信、微博登录图片资源配置文件
import 'package:flutter/material.dart';
import 'package:flutter_fire_utils/utils/imgload_utils.dart';
///🔥🔥🔥三方登录:微信、QQ、微博
class ThreeLoginWidget extends StatelessWidget {
const ThreeLoginWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return SizedBox(
width: MediaQuery.of(context).size.width,
height: 124.0,
child: Stack(
children: [
Positioned(
left: 0.0,
right: 0.0,
top: 10.0,
child: Center(
child: GestureDetector(
onTap: () {},
child: Container(
color: Colors.transparent,
padding: const EdgeInsets.all(10.0),
child: imageAsset('login_wx',
imgWidth: 40.0, imgHeight: 40.0),
),
),
),
),
Positioned(
right: 60.0,
top: 10.0,
child: GestureDetector(
onTap: () {},
child: Container(
color: Colors.transparent,
padding: const EdgeInsets.all(10.0),
child:
imageAsset('login_qq', imgWidth: 40.0, imgHeight: 40.0),
),
),
),
Positioned(
left: 60.0,
top: 10.0,
child: GestureDetector(
onTap: () {},
child: Container(
color: Colors.transparent,
padding: const EdgeInsets.all(10.0),
child:
imageAsset('login_wb', imgWidth: 40.0, imgHeight: 40.0),
),
),
),
],
),
);
}
}
🔥 刘海屏的不适配 🔥
使用 SafeArea 包裹布局
class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: null,
backgroundColor: Colors.blue.withOpacity(0.2),
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: const <Widget>[
BackWidget(),
TitleWidget(),
PhoneNumWidget(),
SendCodeWidget(),
AgreeWidget(),
LoginSubmitWidget(),
ThreeLoginLabelWidget(),
ThreeLoginWidget(),
],
),
),
));
}
}
🔥 运行案例的环境 🔥
Android Studio版本
编译时JDK版本
gradle版本