2 Star 1 Fork 0

GGB / flutter_faster_study

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

flutter_faster_study

准备写一些比较潮流的UI,于是看了B站博主的视频,感觉不错,开始写

项目运行:点击单独的main文件,右击 执行,后期等写多了,会做统一的页面跳转,暂时先这样处理; 在写的过程中,可能在文件命名上有自己的很多风格,多多包涵;

目录结构介绍

assets 静态文件目录

Flutter 重新创建指定语言的android/ios目录

  1. 移除android目录,重新创建指定语言的android目录

进入工程目录,删除android目录

rm -rf android

重新创建java语言的android目录

flutter create -a java .

重新创建kotlin语言的android目录

flutter create -a kotlin .
  1. 移除ios目录,重新创建指定语言的ios目录

进入工程目录,删除ios

rm -rf ios

重新创建指定swift语言的ios目录

flutter create -i swift .

重新创建指定objective-c 语言的ios目录

flutter create -i objc .

生成二维码和查看二维码

使用到的库 glassmorphism: ^1.0.4 sleek_circular_slider: ^1.2.0+web

生成二维码demo

BarcodeWidget(
  data: controller.text,
  barcode: Barcode.qrCode(),
  color: Colors.white,
  width: 200,
  height: 200,
),

查看二维码:

Future<void> scanQRCode() async {
  try {
    final qrCode = await FlutterBarcodeScanner.scanBarcode(
      '#ff6666',
      '取消',
      true,
      ScanMode.QR,
    );
    if (!mounted) return; // 未加载成功,直接返回
    setState(() {
      this.qrCode = qrCode;
    });
  } on PlatformException {
    qrCode = '获取信息失败';
  }
}

iso需要做配置:具体参考 https://share.weiyun.com/qKsTTMmM

GestureDetector 监听滑动

GestureDetector 有监听上下滑动和左右滑动的功能 这里主要演示左右滑动的交互

GestureDetector(
  onHorizontalDragEnd: swiperFunction,...)


void swiperFunction(DragEndDetails details) {
    final selectedIndex = ExerciseType.values.indexOf(selectedType);
    final hasNext = selectedIndex < ExerciseType.values.length - 1;
    final hasPrevious = selectedIndex > 0;
    setState(() {
      if (details.primaryVelocity < 0 && hasNext) {
        final nextType = ExerciseType.values[selectedIndex + 1];
        selectedType = nextType;
      }
      if (details.primaryVelocity > 0 && hasPrevious) {
        final previousType = ExerciseType.values[selectedIndex - 1];
        selectedType = previousType;
      }
    });
  }

枚举使用与菜单

定义枚举 并使用 用于做菜单比较合适

enum ExerciseType { low, mid, hard }
String getExerciseName(ExerciseType type) {
  switch (type) {
    case ExerciseType.hard:
      return "剧烈";
      break;
    case ExerciseType.mid:
      return "适中";
      break;
    case ExerciseType.low:
      return "热身";
      break;

    default:
      return "全部";
      break;
  }
}

Flutter获取Widget的大小和位置信息

获取的原理,通过globalKey绑定在一个weidget上,在渲染完成之后获取绘制的信息;在从回执信息的获取宽高信息和位置信息

// 定义key 绑定key
final keyText = GlobalKey();
Size size;
Offset position;




// 获取方法
// 该方法保证了在(build函数渲染完之后)页面渲染完成之后执行
void calculateSizeAndPosition() =>
WidgetsBinding.instance.addPostFrameCallback((_) {
  final RenderBox box =
      keyText.currentContext.findRenderObject(); // 包含widget的绘制信息
  setState(() {
    position = box.localToGlobal(Offset.zero); // 获取绝对信息 从左上角
    size = box.size;
  });
});


// 调用
calculateSizeAndPosition();

也可以在init方法中调用
void initState() {
  super.initState();
  calculateSizeAndPosition();
}


// 展示
Text('宽度:${size.width.toInt()}'),
Text('高度:${size.height.toInt()}'),
Text('x坐标:${position.dx.toInt()}'),
Text('y坐标:${position.dy.toInt()}'),

指纹识别和刷脸识别

插件地址:https://pub.flutter-io.cn/packages/local_auth

添加android 权限

<user-permission android:name="android.permission.USE_FINGERPRINT"></user-permission>

修改kotlin下的MainActivity.kt

package com.example.flutter_faster_study

import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.embedding.engine.flutterEngine
import androidx.annotation.MonNull;
class MainActivity: FlutterFragmentActivity() {
  override fun configureFlutterEngine(flutterEngine: FlutterEngine){
    GeneratedPluginRegistrant.registerWith(flutterEngine);
  }
}

ios 需要在info.plist 中配置相应的内容

<key>NSFaceIDUsageDescription</key>
<string>Why is my app authenticating using face id?</string>

判断是否支持生物识别

判断指纹识别是否正确

判断支持生物识别的那些类型

import 'package:flutter/services.dart';
import 'package:local_auth/local_auth.dart';

class LocalAuthApi {
  static final _auth = LocalAuthentication();
  // 获取生物识别功能
  static Future<bool> authenticate() async {
    // 判断设备是否支持生物识别功能
    final isAvailable = await hasBiometrics();
    if (!isAvailable) return false;
    try {
      return await _auth.authenticateWithBiometrics(
        localizedReason: "请进行指纹识别",
        useErrorDialogs: true,
        stickyAuth: true,
      );
    } on PlatformException catch (e) {
      return false;
    }
  }
// 调用这个方法  判断设备是否支持生物识别功能
  static Future<bool> hasBiometrics() async {
    try {
      return await _auth.canCheckBiometrics;
    } on PlatformException catch (e) {
      return false;
    }
  }

  // 获取设备支持生物识别列表
  static Future<List<BiometricType>> getBiometrics() async {
    try {
      return await _auth.getAvailableBiometrics();
    } on PlatformException catch (e) {
      return <BiometricType>[];
    }
  }
}

功能引导

插件 https://pub.flutter-io.cn/packages/showcaseview

第一步: 在需要的widget中进行包裹

ShowCaseWidget(
  builder: Builder(builder: (context) => HomeScreen()),
  autoPlay: false,
  autoPlayDelay: Duration(seconds: 3),
  autoPlayLockEnable: false,
),

第二步 在需要的页面 添加globalKey

GlobalKey _one = GlobalKey();
GlobalKey _two = GlobalKey();
GlobalKey _three = GlobalKey();
GlobalKey _four = GlobalKey();

第三步 封装

import 'package:flutter/material.dart';
import 'package:showcaseview/showcaseview.dart';

class CustomShowcaseWidget extends StatelessWidget {
  const CustomShowcaseWidget(
      {Key key,
      @required this.child,
      @required this.desctiption,
      @required this.globalKey})
      : super(key: key);
  final Widget child;
  final String desctiption;
  final GlobalKey globalKey;
  @override
  Widget build(BuildContext context) => Showcase.withWidget(
        overlayColor: Colors.black,
        overlayOpacity: 0.3,
        height: 80,
        width: 140,
         key: globalKey, // 之前在这里错过
        shapeBorder: CircleBorder(),
        description: desctiption,
        container: Column(
          children: [
            Container(
              decoration: BoxDecoration(
                color: Color(0xfff2abc3),
                borderRadius: BorderRadius.circular(10),
              ),
              padding: EdgeInsets.all(10),
              child: Text(
                "$desctiption",
                style: TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ],
        ),
        child: child,
      );
}

第四步 使用

CustomShowcaseWidget(
  desctiption: '个人中心',
  globalKey: _three,
  child: Icon(
    Icons.person,
    size: 28,
  ),
),

第五步 触发 在init触发

@override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback(
        (_) => ShowCaseWidget.of(context).startShowCase([_one, _two, _three]));
  }

通过事件触发

someEvent(){
    ShowCaseWidget.of(context).startShowCase([_one, _two, _three]);
}

实现居中的tab 菜单

floatingActionButtnoLocation:FloatingAcetionButtonLocatin.centerDocked, floationgActonButton:xxx

实现横屏和竖屏的切换

用 OrientationBuilder 包裹子widget

body: Container(
  padding: EdgeInsets.all(32),
  // orientation 包含两个方向一个水平,一个垂直
  child: OrientationBuilder(
    builder: (context, orientation) =>
        orientation == Orientation.portrait
            ? buildPortrait()
            : buildLandscape(),
  ),
),
// 方法出发
floatingActionButton: FloatingActionButton(
  child: Icon(Icons.rotate_left),
  onPressed: () {
    final isPortrait =
        MediaQuery.of(context).orientation == Orientation.portrait;
    isPortrait ? setLandscape() : setPortrait();
  },
),

// 设置竖屏
Future setPortrait() async => await SystemChrome.setPreferredOrientations(
  [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
// 设置横屏
Future setLandscape() async => await SystemChrome.setPreferredOrientations([
    DeviceOrientation.landscapeLeft,
    DeviceOrientation.landscapeRight,
  ]);
// 清除设置
Future setPortraintAndLandscape() =>
      SystemChrome.setPreferredOrientations(DeviceOrientation.values);

视频播放

参考video_player 插件介绍 里面介绍的相当的细 https://pub.flutter-io.cn/packages/video_player 实现逻辑: 1.页面使用pageview构建 对应主 pageController 2.每个视频对应一个controller playVideoController 3.在视频页面加载时 为每个视频添加一个controller,并在页面加载完成后,初始化,并播放,页面销毁后,销毁controller 4.在页面切换时,做两间事情,通过pageController改变展示的page,并将当前对应的视频,传递给video页面;从而实现视频的切换 5.通过当前播放视频的controller控制视频的状态

getX 实现 主题切换 比较简单,但demo案例

Error: Cannot fit requested classes in a single dex file【解决方法】

今天爱分享给大家带来Error: Cannot fit requested classes in a single dex file【解决方法】,希望能够帮助到大家。

Error: Cannot fit requested classes in a single dex file (# methods: 72725 > 65536)

1.在app的gradle下defaultConfig配置添加:

multiDexEnabled true

2.在app的gradle下的dependencies配置添加:

implementation 'com.android.support:multidex:1.0.3'

闪屏页的生成

插件 flutter_native_splash

配置:

dev_dependencies:
  flutter_test:
    sdk: flutter
    
  flutter_native_splash: ^1.2.0
flutter_native_splash:
  color: "#42a5f5"
  image: assets/splash/icon.png
  background_image: assets/splash/background.png
  color_dark: "#042a49"
  image_dark: assets/splash/icon.png
  background_image_dark: "assets/splash/dark-background.png"
  android: true
  ios: true
  web: true
  fullscreen: true
  android_gravity: fill
  ios_content_mode: scaleAspectFill

命令:

flutter clean && flutter pub get && flutter pub run flutter_native_splash:create

交织动画

https://book.flutterchina.club/chapter9/stagger_animation.html 参考 给动画添加顺序

看文档会比较清楚一点;可根据查看文档来了解

AnimationController _animationController;
  List<Animation<double>> _animation;
  int _selectedIndex = 1;

  @override
  void initState() {
    super.initState();
    _animationController = new AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 700),
    );
    final _intervalGap = 1 / widget.items.length;
    _animation = List.generate(
      widget.items.length,
      (index) => Tween(
        begin: 0.0,
        end: 1.6,
      ).animate(
        // 交织动画
        CurvedAnimation(
          parent: _animationController,
          curve: Interval(
            index * _intervalGap,// 该值在0-1.0 之间
            index * _intervalGap + _intervalGap,
          ),
        ),
      ),
    );
    // 一开始 隐藏菜单栏
    _animationController.forward(from: 1.0);
  }

  // 展示部分
  for (int i = 0; i < widget.items.length; i++)
  Positioned(
    left: 0,
    top: itemSize * i,
    width: width,
    height: itemSize,
    child: Transform(
      transform: Matrix4.identity()
        ..setEntry(3, 2, 0.001)
        ..rotateY(_animationController.status ==
                AnimationStatus.reverse
            ? -_animation[widget.items.length - 1 - i].value
            : -_animation[i].value),
      alignment: Alignment.bottomLeft,
      child: Material(
        color: i == _selectedIndex
            ? widget.selectedColor
            : widget.unselectedColor,
        child: InkWell(
          onTap: () {
            widget.onItemSelected(i);
            if (i != 0) {
              setState(() {
                _selectedIndex = i;
              });
            }
            _animationController.forward(from: 0.0);
          },
          child: widget.items[i],
        ),
      ),
    ),
  ),

翻转动画

AnimatedSwitcher 文章参考http://laomengit.com/flutter/widgets/AnimatedSwitcher.html

https://book.flutterchina.club/chapter9/animated_switcher.html#_9-6-1-animatedswitcher

const AnimatedSwitcher({
  Key key,
  this.child,
  @required this.duration, // 新child显示动画时长
  this.reverseDuration,// 旧child隐藏的动画时长
  this.switchInCurve = Curves.linear, // 新child显示的动画曲线
  this.switchOutCurve = Curves.linear,// 旧child隐藏的动画曲线
  this.transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder, // 动画构建器
  this.layoutBuilder = AnimatedSwitcher.defaultLayoutBuilder, //布局构建器
})

app状态监听

文章参考 https://blog.csdn.net/Mr_Tony/article/details/112261101

主要经历以下几个步骤: 1.with WidgetsBindingObserver 2.初始化 添加监听者 WidgetsBinding.instance.addObserver(this); 3.页面销毁 移除监听者 WidgetsBinding.instance.removeObserver(this); // 销毁监听 4.复写监听者的生命周期函数

// 以下是WidgetsBindObserver的方法复写
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    switch (state) {
      case AppLifecycleState.inactive:
        //  应用程序处于闲置状态并且没有收到用户的输入事件。
        //注意这个状态,在切换到后台时候会触发,所以流程应该是先冻结窗口,然后停止UI
        print('YM----->AppLifecycleState.inactive');
        break;
      case AppLifecycleState.paused:
//      应用程序处于不可见状态  相当于进入了后台
        TextPreferences.setText(controller.text);
        print('YM----->AppLifecycleState.paused');
        break;
      case AppLifecycleState.resumed:
        //    进入应用时候不会触发该状态
        //  应用程序处于可见状态,并且可以响应用户的输入事件。它相当于 Android 中Activity的onResume。
        print('YM----->AppLifecycleState.resumed');
        break;
      case AppLifecycleState.detached:
        //当前页面即将退出
        print('YM----->AppLifecycleState.detached');
        break;
    }
  }

  ///当前系统改变了一些访问性活动的回调
  @override
  void didChangeAccessibilityFeatures() {
    super.didChangeAccessibilityFeatures();
    print("YM-----@@@@@@@@@ didChangeAccessibilityFeatures");
  }

  ///低内存回调
  @override
  void didHaveMemoryPressure() {
    super.didHaveMemoryPressure();
    print("YM-----@@@@@@@@@ didHaveMemoryPressure");
  }

  ///用户本地设置变化时调用,如系统语言改变
  @override
  void didChangeLocales(List<Locale> locale) {
    super.didChangeLocales(locale);
    print("YM-----@@@@@@@@@ didChangeLocales");
  }

  ///应用尺寸改变时回调,例如旋转
  @override
  void didChangeMetrics() {
    super.didChangeMetrics();
    Size size = WidgetsBinding.instance.window.physicalSize;
    print(
        "YM-----@@@@@@@@@ didChangeMetrics  :宽:${size.width} 高:${size.height}");
  }

  @override
  Future<bool> didPopRoute() {
    print('YM--------didPopRoute'); //页面弹出
    return Future.value(false); //true为拦截,false不拦截
  }

  @override
  Future<bool> didPushRoute(String route) {
    print('YM--------PushRoute');
    return Future.value(true);
  }

  @override
  Future<bool> didPushRouteInformation(RouteInformation routeInformation) {
    print('YM--------didPushRouteInformation');
    return Future.value(true);
  }

  //文字大小改变时候的监听
  @override
  void didChangeTextScaleFactor() {
    print(
        "YM--------@@@@@@@@@ didChangeTextScaleFactor  :${WidgetsBinding.instance.window.textScaleFactor}");
  }

  @override
  void didChangePlatformBrightness() {
    final window = WidgetsBinding.instance.window;
    final brightness = window.platformBrightness;
    // Brightness.light 亮色
    // Brightness.dark 暗色
    print('YM----平台主题改变----didChangePlatformBrightness$brightness');
    // window.onPlatformBrightnessChanged = () {
    //   // This callback gets invoked every time brightness changes
    //   final brightness = window.platformBrightness;
    //   print('YM----平台亮度改变----didChangePlatformBrightness$brightness');
    // };
  }

开发者必须知道的30个命令

flutter doctor 检查flutter SDK是否正确
flutter doctor -v 查看SKD配置出现的详细问题

flutter upgrade 下载并更新flutter sdk

flutter packages get 下载第三方flutter包

flutter create my_app 创建一个flutter项目

flutter create--org=com.appxxxx my_app 创建包含报名的flutter项目

flutter create --org=com.appxxx --project-name=appxxx my_app 添加项目名称(在项目名称和app名称不止一致,可以添加一个project-name )

flutter create -org=com.appxxxx --android-language=java my_app 创建使用特定语言的项目
flutter create -org=com.appxxxx --ios-language=objc my_app 创建使用特定语言的项目

flutter create -org=com.appxxxx --android-language=java --ios-language=objc my_app 也可以使用该命令改已有的项目

flutter create --no-voerwrite -org=com.appxxxx my_app 如果我们删除了一些文件,想创建,但不覆盖原始的代码,使用该命令 创建flutter项目缺少的新文件

创建多平台 需要把channel改为master
flutter create --platforms=windows .
flutter create --platforms=macos .
flutter create --platforms=linux .
flutter create --platforms=web .
flutter channel master

flutter devices 列出连接到电脑中的所有设备

flutter run -d xxxx 在指定的设备上运行app
需要了解出错的原因,在后面添加一个 -v    flutter run -d xxxx -v 即可

flutter run -d xxxx -t lib/main.dart 指定运行的文件
flutter run -d windows -t lib/main_win.dart 指定运行的文件 运行到window平台

flutter channel 使用哪个版本
flutter channel <channer-name>

flutter logs-d <divice-id> 在控制台中,看到指定运行设备的日志

flutter clean 删除临时文件 比如build文件夹中的文件

flutter build <target-platform-name> --relese 构建发布版本的app 
flutter build <target-platform-name> --debug 构建发布版本的app 
flutter install -d <platform-name> 卸载老版本,安装新版本
flutter install --uninstall-only -d <platform-name> 卸载老版本
flutter attach -d <platform-name> 连接设备热加载
flutter config --enable-<platform-name>-desktop 支持左面端
flutter config --enable-web 支持web
flutter config --android-sdk <path-to-android-skd> 指定android sdk的环境路径
flutter config --clear-features 恢复默认配置
flutter format . 格式化后的代码

AnimatedList 的使用

AnimatedList提供了一种简单的方式使列表数据发生变化时加入过渡动画; 文章参考:http://laomengit.com/flutter/widgets/AnimatedList.html

相对于来说比较全,里面说到了两种获取AnimationListState的方式,通过AnimationListState可以控制动画的展示和隐藏,列表的添加和移除,都对应着动画,同时列表的移除,就需要在移除原始数据的基础上,还需要将AnimationListState对应的动画移除,在次渲染数据,才会产生动画,而添加动画,则是添加数据,添加动画,不需要从新渲染列表;

class _HomeScreenState extends State<HomeScreen> {
  final items = List.from(Data.shoppingList);
  final key = GlobalKey<AnimatedListState>();
  List<int> _list = [];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("动画列表"),
      ),
      body: Column(
        children: [
          Expanded(
            child: AnimatedList(
              key: key,
              initialItemCount: items.length,
              itemBuilder:
                  (BuildContext context, int index, Animation animation) {
                return buildItem(items[index], index, animation);
              },
            ),
          ),
          Container(
            child: buildInsetButton(),
          ),
        ],
      ),
    );
  }

  Widget buildInsetButton() => ElevatedButton(
        child: Text("添加"),
        onPressed: () => insertItem(3, Data.shoppingList.first),
      );

  Widget buildItem(
          ShopItemModel item, int index, Animation<double> animation) =>
      ShopItemWidget(
        item: item,
        animation: animation,
        onClicked: () => removeItem(index),
      );

  void removeItem(int index) {
    final item = items.removeAt(index);
    key.currentState.removeItem(
        index, (context, animation) => buildItem(item, index, animation));
  }

  void insertItem(int index, ShopItemModel item) {
    items.insert(index, item);
    key.currentState.insertItem(index);
  }
}




class ShopItemWidget extends StatelessWidget {
  final ShopItemModel item;
  final Animation animation;
  final VoidCallback onClicked;
  const ShopItemWidget(
      {@required this.item,
      @required this.animation,
      @required this.onClicked,
      Key key})
      : super(key: key);

  @override
  Widget build(BuildContext context) => ScaleTransition(
        scale: animation,
        child: Container(
          margin: EdgeInsets.all(8),
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(12),
          ),
          child: ListTile(
            contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
            leading: CircleAvatar(
              radius: 32,
              backgroundImage: AssetImage(item.urlImage),
            ),
            title: Text(
              item.title,
              style: TextStyle(
                fontSize: 20,
              ),
            ),
            trailing: IconButton(
              icon: Icon(Icons.check_circle, color: Colors.green, size: 32),
              onPressed: onClicked,
            ),
          ),
        ),
      );
}

banner 的使用

在父组件的角上显示一个对角线的消息的控件,比如debug模式下,显示在App右上角的DEBUG就是此组件实现的。 组件参考:http://laomengit.com/flutter/widgets/Banner.html#banner 自定义banner 的原理;使用一个帧布局,然后给需要的banner一个旋转的动画属性;在需要的banner上层使用position定位

import 'package:flutter/material.dart';

import 'card_widget.dart';

class BannerCustomPage extends StatelessWidget {
  final topLeft = AlwaysStoppedAnimation(-45 / 360);
  final topRight = AlwaysStoppedAnimation(45 / 360);
  @override
  Widget build(BuildContext context) => Scaffold(
      appBar: AppBar(
        title: Text("自定义banner"),
      ),
      body: ListView(
          physics: BouncingScrollPhysics(),
          padding: EdgeInsets.all(16),
          children: [
            buildCardTopLeft(),
            SizedBox(height: 16),
            buildCardTopRight(),
            SizedBox(height: 16),
          ]));

  buildCardTopLeft() => Stack(children: [
        CardWidget(),
        Positioned(
          left: -32,
          top: 20,
          child: buildBadge(turns: topLeft),
        ),
      ]);

  buildCardTopRight() => Stack(children: [
        CardWidget(),
        Positioned(
          right: -32,
          top: 20,
          child: buildBadge(turns: topRight),
        ),
      ]);

  buildBadge({Animation<double> turns}) => RotationTransition(
        turns: turns,
        child: Container(
          padding: EdgeInsets.symmetric(vertical: 8, horizontal: 36),
          color: Colors.teal,
          child: Text(
            "热门产品",
            style: TextStyle(
              fontWeight: FontWeight.bold,
              color: Colors.white,
              fontSize: 20,
            ),
          ),
        ),
      );
}

网络监测

需要用到 依赖包

注意,需要使用OverlaySupport.global 包裹materialApp;具体看插件说明

# 网络监测
connectivity: ^3.0.6
# appbar 上的提示框
overlay_support: ^1.2.1

整体来时不难,只需要通过异步获取网络的状态,然后判断其状态,处理自己需要的逻辑!在实时监听板块,需要用一个流获取传过来的值,并需要在页面销毁的时候取消监听;

项目参考

代码实现

import 'dart:async';

import 'package:connectivity/connectivity.dart';
import 'package:flutter/material.dart';

import '../../utils.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key key}) : super(key: key);

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  StreamSubscription subscription;
  @override
  void initState() {
    super.initState();
    subscription =
        Connectivity().onConnectivityChanged.listen(showConnectivityStackBar);
  }

  @override
  void dispose() {
    super.dispose();
    subscription.cancel();
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: Text("是否连接到网络?"),
        ),
        body: Center(
          child: ElevatedButton(
            child: Text(
              "检查连接",
              style: TextStyle(fontSize: 20),
            ),
            style: ElevatedButton.styleFrom(
              padding: EdgeInsets.all(12),
            ),
            onPressed: () async {
              final result = await Connectivity().checkConnectivity();
              showConnectivityStackBar(result);
            },
          ),
        ),
      );

  void showConnectivityStackBar(ConnectivityResult result) {
    final hasInternet = result != ConnectivityResult.none;
    final message = hasInternet ? '连接结果${result.toString()}' : '没有连接到Internet';
    final color = hasInternet ? Colors.green : Colors.red;
    Utils.showTopSnackBar(message, color);
  }
}

徽标 | 小红点 | Badge

如果使用组件,需要配置即可,如果使用自定义的 徽标 | 小红点 | Badge;实现逻辑 在底部buttonNavibarItem里面的icon 添加帧布局,在里面天机图标和 徽标 | 小红点 | Badge ;实现逻辑不难,具体参考

import 'package:badges/badges.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key key}) : super(key: key);

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  int index = 0;
  int countFavourites = 98;
  int countMessages = 9;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("徽章"),
      ),
      bottomNavigationBar: buildBottomBar(),
      body: Container(
        padding: EdgeInsets.all(32),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Badge(
                toAnimate: true,
                badgeColor: Colors.teal,
                padding: EdgeInsets.all(8),
                badgeContent: Text(
                  '$countFavourites',
                  style: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                child: buildButton(
                  text: "添加收藏",
                  onClicked: () => setState(() => countMessages += 1),
                ),
              ),
              const SizedBox(height: 32),
              Badge(
                position: BadgePosition.topStart(),
                toAnimate: true,
                badgeColor: Colors.teal,
                padding: EdgeInsets.all(8),
                badgeContent: Text(
                  '$countFavourites',
                  style: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                child: buildButton(
                  text: "添加消息",
                  onClicked: () => setState(() => countFavourites += 1),
                ),
              ),
              const SizedBox(height: 32),
            ],
          ),
        ),
      ),
    );
  }

  Widget buildBottomBar() {
    final style = TextStyle(color: Colors.black);
    return BottomNavigationBar(
      backgroundColor: Theme.of(context).primaryColor,
      selectedItemColor: Colors.black,
      unselectedItemColor: Colors.black54,
      currentIndex: index,
      items: [
        BottomNavigationBarItem(
            icon: buildCustomBadge(
              counter: countFavourites,
              child: Icon(Icons.favorite_border),
            ),
            label: '收藏'),
        BottomNavigationBarItem(icon: Icon(Icons.message), label: '消息'),
      ],
      onTap: (int index) => setState(() {
        this.index = index;
      }),
    );
  }

  buildButton({String text, VoidCallback onClicked}) => ElevatedButton(
        child: Text(
          text,
          style: TextStyle(
            fontSize: 20,
          ),
        ),
        onPressed: onClicked,
        style: ElevatedButton.styleFrom(
          minimumSize: Size.fromHeight(50),
          primary: Colors.pink,
        ),
      );

  Widget buildCustomBadge({@required int counter, @required Widget child}) {
    final text = counter.toString();
    final deltaFontSize = (text.length - 1) * 3.0;
    return Stack(
      clipBehavior: Clip.hardEdge,
      children: [
        child,
        Positioned(
          top: -6,
          right: -20,
          child: CircleAvatar(
            backgroundColor: Colors.white,
            child: Text(
              text,
              style: TextStyle(
                fontSize: 16 - deltaFontSize,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        )
      ],
    );
  }
}

Flutter页面过渡动画效果

页面过度动画效果,使用的是animations的OpenContainer() 组件; 重新里面的两个方法,达到容器的切换 完整代码参考

final transitionType = ContainerTransitionType.fade;
OpenContainer(
  transitionType: transitionType,
  transitionDuration: Duration(milliseconds: 500),
  openBuilder: (context, _) => DetailScreen(card: items[index]),
  closedBuilder: (context, VoidCallback openContainer) => CardWidget(
    card: items[index],
    onClicked: openContainer,
  ),
),

flutter 流式布局 配合页面动画

插件 flutter_staggered_grid_view

完整代码参考

StaggeredGridView.countBuilder(
  crossAxisCount: 4,
  mainAxisSpacing: 4.0,
  crossAxisSpacing: 4.0,
  itemCount: items.length,
  itemBuilder: (BuildContext context, int index) => OpenContainer(
    transitionType: transitionType,
    transitionDuration: Duration(milliseconds: 500),
    openBuilder: (context, _) => DetailScreen(card: items[index]),
    closedBuilder: (context, VoidCallback openContainer) => CardWidget(
      card: items[index],
      onClicked: openContainer,
    ),
  ),
  staggeredTileBuilder: (int index) =>
      new StaggeredTile.count(2, index.isEven ? 3 : 2),
),

Flutter获取设备电池电量、充电状态

主要有两个方法,一个是获取电量,一个是获取状态,充电的状态; 使用到插件 :battery: ^2.0.3

完整代码示例参考

 final battery = Battery();
  int batteryLevel = 100;
  Timer timer;
  BatteryState batteryState = BatteryState.full;
  StreamSubscription<BatteryState> subscription;

  @override
  void initState() {
    super.initState();
    listenBatteryLevel();
    listenBatteryState();
  }

  void listenBatteryState() {
    subscription = battery.onBatteryStateChanged.listen((BatteryState state) {
      setState(() {
        batteryState = state;
      });
    });
  }

  void listenBatteryLevel() {
    updateBatterylevel();
    timer = Timer.periodic(
      Duration(seconds: 10),
      (_) async => updateBatterylevel(),
    );
  }

  Future updateBatterylevel() async {
    final batteryLevel = await battery.batteryLevel;
    setState(() {
      this.batteryLevel = batteryLevel;
    });
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    timer.cancel();
    subscription.cancel();
  }

模糊效果

具体案列看

fliter_demo 模糊效果

BackdropFilter(
  filter: ImageFilter.blur(
    sigmaX: blurImage * 100,
    sigmaY: blurImage * 100,
  ),
  child: Container(
    color: Colors.black.withOpacity(0.2),
  ),
),

动画效果切换页面

数据animations包下面 代码参考

PageTransitionSwitcher(
    duration: Duration(milliseconds: 1200),
    reverse: isFirst, // 更好的体验优化
    transitionBuilder: (child, animation, secondaryAnimation) =>
        SharedAxisTransition(
      child: child,
      animation: animation,
      secondaryAnimation: secondaryAnimation,
      transitionType:
          SharedAxisTransitionType.horizontal, // 动画类型,水平方向过度下一个页面
    ),
    child: isFirst ? FirstScreen() : SecondScreen(),
  ),
MIT License Copyright (c) 2021 GGB Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

课程来源于 快学flutter 在博主分享的基础上,使用代码实现 展开 收起
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Dart
1
https://gitee.com/ggbhack/flutter_faster_study.git
git@gitee.com:ggbhack/flutter_faster_study.git
ggbhack
flutter_faster_study
flutter_faster_study
master

搜索帮助