Flutter 2020/2/18 来到app登录界面

🔥邀你共赏登录界面样式🔥

🔥这样的流程图你见过吗🔥

在这里插入图片描述在这里插入图片描述

💋约会可能发生的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

Plant Web Server 绘制流程图

@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秒后重复发) .
如果倒计时完成 , 按钮文本变成 (重新发送) .

Plant Web Server 绘制流程图

@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 命令加载依赖
flutter_inappwebview
自定义网页加载

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 完美消除警告线提示
SingleChildScrollView

🔥 三方登录标题 🔥

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、微信、微博登录图片资源配置文件
登录QQ、WX、WB

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(),
              ],
            ),
          ),
        ));
  }
}

SafeArea

🔥 运行案例的环境 🔥

Android Studio版本
Android Studio版本
编译时JDK版本
编译时JDK版本gradle版本
gradle、kotlin_version

🔥 登录界面案例下载 🔥

  • 11
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

️ 邪神

你自己看着办,你喜欢打赏我就赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值