flutter各种功能实现方法及比较(更新中......)

2,006 阅读6分钟
  • 实现widget隐藏

方法

  1. 使用Opacity
bool _visiable = false;
Opacity(
  opacity: _visiable ? 1.0 : 0.0,
  child: const Text('Now you see me, now you don\'t!'),
)

隐藏后仍然进行渲染,仍然占有空间,仍然可以进行交互

  1. 使用Offstage
Offstage(
      offstage: true,
      child: child
      )
    );

隐藏后不会进行渲染,不占用空间

  1. 使用visibility
Visibility(
      visible: false,
      child: child
      )
    )

隐藏后不会进行渲染,不占用空间

  • 实现gridview单击一个item放大后左右滑动浏览

场景:

场景
阅读原文下面是gridView展示的widget,希望的效果是: 点击第二个item放大后
希望的效果

方法

  1. 使用Swiper
Swiper(
          itemCount: w_list.length,
          //itemWidth: MediaQuery.of(context).size.width,
          itemBuilder: _swiperBuilder,
          pagination: SwiperPagination(
              alignment: Alignment.bottomCenter,
              margin: const EdgeInsets.fromLTRB(0, 0, 0, 10),
              builder: DotSwiperPaginationBuilder(
                  color: Colors.black54,
                  activeColor: Colors.blue,
              )
          ),
          controller: SwiperController(),
          autoplayDisableOnInteraction: true,
          scrollDirection: Axis.horizontal,
          autoplay: false,
          index: initial_index,//设置成开始浏览时点击的item的index值
          viewportFraction: 1.0,
          scale: 1.0,
          //layout: SwiperLayout.STACK,
          //onTap: (index) => print('点击了第$index'),
        ),

使用pageview可以实现放大左右切换,但是相比swiper
(1)没有自带分页器
(2)pageview的pageViewController.jumpToPage()方法在必须在PageView构建后才可以使用,但是我们希望开始浏览时就已经跳转到目标item,无法满足需求。Swiper只需要简单设置index便可以解决上述问题

  • 场景同上,其中同时包含视频和图片类型的item,可以做到滑动浏览及在线视频点击播放

方法

在Swiper的itemBuilder中,对于视频类型的item,返回的widget并非视频,而是视频的第一帧加一个播放图标堆叠在上面,widget代码如下

Widget get_video_widget(String url)
{
  /*
  返回存放视频的容器,参数是视频第一帧url
   */
  return Container(
      decoration: BoxDecoration(
        image: new DecorationImage(
          // fit:BoxFit.fill,
            image: new NetworkImage(url, scale: 1.2)),
      ),
    child:Opacity(
          opacity: 0.8,
          child: Center(
              child:Icon(Icons.play_circle_filled,color: Colors.white,)
          ),
        ),
  );
}

当检测到用户点击时,弹出dialog形式的新的界面进行视频播放,而非Swiper内部播放,(Swiper内部播放会出现很多问题)

IjkMediaController jk_controller = IjkMediaController();
return GestureDetector(
          child:w_list[index],//是上面 get_video_widget返回的widget
          onTap: (){
            showIJKDialog();
          },
        );
      }
      
@override
void dispose() {
    jk_controller?.dispose();
    super.dispose();
}

void showIJKDialog() async {
    await jk_controller.setDataSource(
      DataSource.network(
          "http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4"),
    );
    await jk_controller.play();
    await showDialog(
      context: context,
      builder: (_) => _buildIJKPlayer(),
    );
    jk_controller.pause();
  }

_buildIJKPlayer() {
    return IjkPlayer(
      mediaController: jk_controller,
    );
  }

播放效果如下

播放效果

这里使用的用来播放视频的flutter库叫做flutter_ijkplayer,
相比较video_player,拥有更好的ui界面及更多的视频操作功能,
相比较cheiwe,拥有更快的加载速度,更高的稳定性
一个很好的flutter_ijkplayer demo地址

  • 实现http请求

方法

  1. 使用http请求
    String url = "http://xxxxxxx"
    Map<String,String> json_data = {};
    json_data.addAll({"xx":xxx});
    await http.post(url, body: json_data)
        .then((response) {
      print("post方式->status: ${response.statusCode}");
      print("post方式->body: ${response.body}");
    });
  • 显示圆形的container

方法

  1. 直接看代码
ClipOval(
      child: Container(
        height: MediaQuery.of(context).size.height/4,
        width: MediaQuery.of(context).size.height/4,
        child: Image.file(File(widget.imagePath),fit: BoxFit.fill,),
      ),
    );
  • Camera自定义拍照

方法

  1. 看代码 基本思路是 获取可用相机 取第一个相机 得到相机控制器 控制器初始化 打开相机预览 得到照片保存路径 控制器拍照 展示拍照结果
import 'dart:async';
import 'dart:io';

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart' show join;
import 'package:path_provider/path_provider.dart';

Future<void> main() async {
  // 获取设备可用摄像头
  final cameras = await availableCameras();

  // 取第一个摄像头
  final firstCamera = cameras.first;

  runApp(
    MaterialApp(
      theme: ThemeData.dark(),
      home: TakePictureScreen(
        // Pass the appropriate camera to the TakePictureScreen widget.
        camera: firstCamera,
      ),
    ),
  );
}

// A screen that allows users to take a picture using a given //camera.
class TakePictureScreen extends StatefulWidget {
  final CameraDescription camera;

  const TakePictureScreen({
    Key key,
    @required this.camera,
  }) : super(key: key);

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

class TakePictureScreenState extends State<TakePictureScreen> {
  CameraController _controller;
  Future<void> _initializeControllerFuture;

  @override
  void initState() {
    super.initState();
    // 获取摄像头的控制器
    _controller = CameraController(
      // Get a specific camera from the list of available cameras.
      widget.camera,
      ResolutionPreset.medium,
    );

    // 初始化控制器
    _initializeControllerFuture = _controller.initialize();
  }

  @override
  void dispose() {
    //销毁控制器
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Take a picture')),
      //FutureBuilder来确保控制器已经被初始化
      body: FutureBuilder<void>(
        future: _initializeControllerFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            //确定初始化完成,打开相机
            return CameraPreview(_controller);
          } else {
            // 否则,展示进度条.
            return Center(child: CircularProgressIndicator());
          }
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.camera_alt),
        onPressed: () async {
          try {
            // 确保相机已经被初始化
            await _initializeControllerFuture;

        
            final path = join(
              //得到图片保存路径
              (await getTemporaryDirectory()).path,
              '${DateTime.now()}.png',
            );

            // 进行拍照
            await _controller.takePicture(path);

            // 拍照后进行展示
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => DisplayPictureScreen(imagePath: path),
              ),
            );
          } catch (e) {
            print(e);
          }
        },
      ),
    );
  }
}

//展示拍照结果的界面
class DisplayPictureScreen extends StatelessWidget {
  final String imagePath;

  const DisplayPictureScreen({Key key, this.imagePath}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Display the Picture')),
      //根据路径得到图片
      body: Image.file(File(imagePath)),
    );
  }
}
  • ui实现悬浮设计

方法

  1. 使用Container的decoration设置背景,
    内嵌---Container,通过decoration的gradient设置背景图上层的一个渐变色渲染
    内嵌---Stack,进行布局

  2. 一些color使用Color.fromRGBO(247, 64, 106, 0.5),通过调节透明度实现一个悬浮效果

new Container(
              decoration: new BoxDecoration(
                image: backgroundImage,
              ),
              child: new Container(
                  decoration: new BoxDecoration(
                      gradient: new LinearGradient(
                    colors: <Color>[
                      const Color.fromRGBO(162, 146, 199, 0.8),
                      const Color.fromRGBO(51, 51, 63, 0.9),
                    ],
                    stops: [0.2, 1.0],
                    begin: const FractionalOffset(0.0, 0.0),
                    end: const FractionalOffset(0.0, 1.0),
                  )),
                  child: new ListView(
                    padding: const EdgeInsets.all(0.0),
                    children: <Widget>[
                      new Stack(
                        alignment: AlignmentDirectional.bottomCenter,
                        children: <Widget>[
                          new Column(
                            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                            children: <Widget>[
                              Container(height: 10,
                              child:Text("悬浮设计")),
                            ],
                          ),
                          
                        ],
                      ),
                    ],
                  ))),
  • ui保证异步操作结束后进行build

方法

  1. 使用FutureBuilder

注意:

  • initState和构造函数不可以直接间接调用异步方法
  • FutureBuilder 必须提供一个异步结束前显示的widget,一般是
Center(child: CircularProgressIndicator());

代码示例:

FutureBuilder<void>(
        future: _initializeControllerFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            // If the Future is complete, display the preview.
            return CameraPreview(_controller);
          } else {
            // Otherwise, display a loading indicator.
            return Center(child: CircularProgressIndicator());
          }
        },
      ),
  • 前端使用preferences

方法

  1. 添加依赖到pubspec.yaml
shared_preferences: ^0.4.3
  1. 存数据
final prefs = await SharedPreferences.getInstance();

prefs.setString("name","Jerry");

3.取数据

final prefs = await SharedPreferences.getInstance();

final  name = prefs.getString("count") ?? "";
  • build结束后的监听事件

方法

  1. 使用WidgetsBinding WidgetBinding类官方解释是控制层和Flutter引擎之间的粘合剂,这个类可以监听第一帧的绘制结束,第一帧绘制结束标志着build完成,使用方法见代码
WidgetsBinding widgetsBinding;//用来监听build结束后的响应事件
@override
  void initState() {
    super.initState();
    widgetsBinding = WidgetsBinding.instance;
    widgetsBinding.addPostFrameCallback((callBack){
      dealWithAutoLand();
      dealWithRememberPw();
    });
  }
  • 使用Form表单构建输入框

方法:

使用两个文件
InputFields.dart构建单独一个输入框widget,代码

import 'package:flutter/material.dart';


//对应一个输入框
class InputFieldArea extends StatelessWidget {
  final String hint;
  final bool obscure;
  final IconData icon;
  TextEditingController controller;
  FormFieldValidator validator;
  InputFieldArea({this.hint, this.obscure, this.icon,this.controller,this.validator});
  @override
  Widget build(BuildContext context) {
    return (new Container(
      decoration: new BoxDecoration(
        border: new Border(
          bottom: new BorderSide(
            width: 0.5,
            color: Colors.white24,
          ),
        ),
      ),
      child: new TextFormField(
        controller: controller,
        autofocus: true,
        obscureText: obscure,
        validator: validator,
        style: const TextStyle(
          color: Colors.white,
        ),
        decoration: new InputDecoration(
          icon: new Icon(
            icon,
            color: Colors.white,
          ),
          border: InputBorder.none,
          hintText: hint,
          hintStyle: const TextStyle(color: Colors.white, fontSize: 15.0),
          contentPadding: const EdgeInsets.only(
              top: 30.0, right: 30.0, bottom: 30.0, left: 5.0),
        ),
      ),
    ));
  }
}

form.dart,组织所有输入框,代码

TextEditingController studentNumberControllerLogin = new TextEditingController();
TextEditingController passwordNumberControllerLogin = new TextEditingController();

//登陆的输入表单,包含两个输入框
class loginFormContainer extends StatelessWidget {

  Map<String,String> inputData;

  //得到输入数据函数
  void getData()
  {
    inputData = new Map();
    inputData["studentNumber"] = studentNumberControllerLogin.text.toString();
    inputData["password"] = passwordNumberControllerLogin.text.toString();
  }

  @override
  Widget build(BuildContext context) {
    return (new Container(
      margin: new EdgeInsets.symmetric(horizontal: 20.0),
      child: new Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: <Widget>[
          new Form(
              child: new Column(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: <Widget>[
                  new InputFieldArea(
                    controller: studentNumberControllerLogin,
                    hint: "学号",
                    obscure: false,
                    icon: Icons.person_outline,
                  ),
                  new InputFieldArea(
                    controller: passwordNumberControllerLogin,
                    hint: "密码",
                    obscure: true,
                    icon: Icons.lock_outline,
                  ),
                ],
              )),
        ],
      ),
    ));
  }
}
  • 设置appBar高度

方法

  1. 使用PreferredSize
appBar:PreferredSize(
        preferredSize: Size.fromHeight(2.0),//修改appbar高度
        child:  AppBar(
          title: Text("地大知识交流平台"),
        ),
      ),
  • 设置渐变色

方法

1.gradient使用LinearGradinet,代码

Container(
             decoration: BoxDecoration(
               gradient: LinearGradient(
                 colors: [Colors.cyan, Colors.blue, Colors.cyan],
               ),
),
  • Container各个属性使用

给个不错的链接

blog.csdn.net/chenlove1/a…

  • 屏幕适配

方法

  1. 安装,写入pubspec.yaml处
flutter_screenutil: ^0.5.3
  1. materialApp的入口处,即home页面,传入设计手稿设计尺寸,进行初始化。整个app使用过程中只需要初始化一次
//填入设计稿中设备的屏幕尺寸
ScreenUtil.instance = ScreenUtil(width: 1080, height: 1920)..init(context);
  1. 使用时,分成字体大小、正方形、宽度、高度设置
  • 字体
ScreenUtil.getInstance().setSp(28) 
  • 正方形
Container(
           width: ScreenUtil.getInstance().setWidth(300),
           height: ScreenUtil.getInstance().setWidth(300),
            ),
  • 宽度
ScreenUtil.getInstance().setWidth(300),
  • 高度
ScreenUtil.getInstance().setHeight(300),
  • 上传图片到服务器

方法:使用dio

第一步,安装dio

dio: ^2.1.0

第二步,封装dio


import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';

//普通格式的header
Map<String, dynamic> headers = {
  "Accept":"application/json",
//  "Content-Type":"application/x-www-form-urlencoded",
};

//json格式的header
Map<String, dynamic> headersJson = {
  "Accept":"application/json",
  "Content-Type":"application/json; charset=UTF-8",
};

class HttpUtil {
  Dio dio;
  Map<String, dynamic> headers;
  BaseOptions options;

  HttpUtil({String baseUrl}) {
    headers = {
      "Accept":"application/json",
//  "Content-Type":"application/x-www-form-urlencoded",
    };

    print('dio赋值');
    // 或者通过传递一个 `options`来创建dio实例
    options = BaseOptions(
      // 请求基地址,一般为域名,可以包含路径
      baseUrl: baseUrl,
      //连接服务器超时时间,单位是毫秒.
      connectTimeout: 10000,
      //[如果返回数据是json(content-type),dio默认会自动将数据转为json,无需再手动转](https://github.com/flutterchina/dio/issues/30)
      responseType:ResponseType.plain,
      ///  响应流上前后两次接受到数据的间隔,单位为毫秒。如果两次间隔超过[receiveTimeout],
      ///  [Dio] 将会抛出一个[DioErrorType.RECEIVE_TIMEOUT]的异常.
      ///  注意: 这并不是接收数据的总时限.
      receiveTimeout: 3000,
      headers: headers,
    );
    dio = new Dio(options);
    dio.interceptors.add(CookieManager(CookieJar()));
  }

  get(url, {data, options, cancelToken}) async {
    print('get请求启动! url:$url ,body: $data');
    Response response;
    try {
      response = await dio.get(
        url,
        cancelToken: cancelToken,
      );
      print('get请求成功!response.data:${response.data}');
    } on DioError catch (e) {
      if (CancelToken.isCancel(e)) {
        print('get请求取消! ' + e.message);
      }
      print('get请求发生错误:$e');
    }
    return response.data;
  }

  post(url, {data, options, cancelToken}) async {
    print('post请求启动! url:$url ,body: $data');
    Response response;
    try {
      response = await dio.post(
        url,
        data: data,
      );
      print('post请求成功!response.data:${response.data}');
    } on DioError catch (e) {
      if (CancelToken.isCancel(e)) {
        print('post请求取消! ' + e.message);
      }
      print('post请求发生错误:$e');
    }
    return response.data;
  }
}

第三步,上传图片

String url = "http://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

    FormData formData = new FormData.from({
      "image01": new UploadFileInfo(file, "head.jpg")
    });
    HttpUtil httpUtil = new HttpUtil(baseUrl: url);
    String response = await httpUtil.post(url,data: formData);
    print(response);
    
  • flutter使用toast

方法

第一步,安装toast插件

toast: ^0.1.3

第二步,使用

//显示toast
  void showToastContent(String content)
  {
    Toast.show(content,curContext,duration: Toast.LENGTH_LONG,gravity: Toast.BOTTOM);
  }
  • flutter网络请求判断出网络原因

方法

设置异常处理块,思想是捕获到异常则代表网络连接失败

List<String> splits = response.split("_");
    String resultMark = splits[0];
    String content;
    try
    {
      content = splits[1];
    }catch(e,s)
    {
      content = "网络原因,登陆失败";
    }
  • 返回拦截,防止用户意外退出软件

方法

 DateTime lastTime;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(title: Text("点两次返回键退出"),),
            body: Builder(builder: (context)=>WillPopScope(
                onWillPop: () async {
                  if (lastTime == null || DateTime.now().difference(lastTime) >
                      Duration(seconds: 1)) {
                    lastTime = DateTime.now();
                    Scaffold.of(context).showSnackBar(SnackBar(content: Text("再点一次退出!")));
                    return false;
                  }
                  return true;
                },
                child: Container(
                  alignment: Alignment.center,
                  child: Text("1秒内连续按两次返回键退出"),
                )
            ))
        )
    );
  }
  • 强制竖屏不可旋转屏幕

方法

import 'package:flutter/services.dart';
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]).then((_){});
  • 禁止debug信息显示在手机屏幕上

方法

main函数内部放

ErrorWidget.builder = (FlutterErrorDetails details) => Container();
  • 定时前台显示通知

方法

1 安装插件

flutter_local_notifications: ^0.8.4

2 配置android
2.1 AndroidManifest.xml权限添加

<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

2.2 drawable下创建图标名为app_icon

3 配置ios 这一步不需要做,只要一步一步来,后面的代码中会包含

4 相关代码

FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;

//初始化
 var initializationSettingsAndroid =
    AndroidInitializationSettings('app_icon');
    var initializationSettingsIOS = IOSInitializationSettings(
        onDidReceiveLocalNotification: onDidReceiveLocalNotification);
    var initializationSettings = InitializationSettings(
        initializationSettingsAndroid, initializationSettingsIOS);
    flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
    flutterLocalNotificationsPlugin.initialize(initializationSettings,
        onSelectNotification: onSelectNotification);
        


  //显示通知的函数
  Future<void> showNotification(String content) async {
    
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your channel id', 'your channel name', 'your channel description',
        importance: Importance.Max, priority: Priority.High, ticker: 'ticker');
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    int notiId = prefs.getInt("notiId") ?? 0;
    prefs.setInt("notiId",notiId + 1);

    await flutterLocalNotificationsPlugin.show(
        notiId, '地大支持平台', "有人解答了你的问题", platformChannelSpecifics,
        payload: content);
  }
  
  //通知点击事件
  Future<void> onSelectNotification(String payload) async {
    if (payload != null) {
      print("payload*****************************" + payload);
    }
    
    await Navigator.push(
      projectTopContext,
      MaterialPageRoute(builder: (context) => showAnswerComplete(xxx)),
    );
  }
  
  //适配ios老版本,与点击事件响应一致
  Future onDidReceiveLocalNotification(
      int id, String title, String body, String payload) async {
    // display a dialog with the notification details, tap ok to go to another page
    showDialog(
      context: context,
      builder: (BuildContext context) => new CupertinoAlertDialog(
            title: new Text(title),
            content: new Text(body),
            actions: [
              CupertinoDialogAction(
                isDefaultAction: true,
                child: new Text('Ok'),
                onPressed: () async {
                  Navigator.of(context, rootNavigator: true).pop();
                  Navigator.push(
                  projectTopContext,
                  MaterialPageRoute(builder: (context) => showAnswerComplete(xxx))
                  );
                },
              )
            ],
          ),
    );
  }