flutter中好用的Widget-CupertinoPicker

简介: flutter中好用的Widget-CupertinoPicker

简介

Cupertino (库比蒂诺)是一个地名,苹果电脑的全球总公司所在地。CupertinoPicker是一个ios风格的齿轮滚动的选择器,常用于日期地址选择。

效果图

 

用法

CupertinoPicker(
                  itemExtent: 28,
                  onSelectedItemChanged: (position) {
                    print('The position is $position');
                  },
                  children: getListWidgets(10,Constants.default_min_cycle_day)),
            ),

简单的使用只需实现以上三个参数:

  1. itemExtent :子项高度,选中位置的高度。
  2. children: 子widget组。
  3. onSelectedItemChanged: 滚动选择的回调,每次滚动,都会触发此回调,会将选中的子widget的position返回。
CupertinoPicker.builder({
    Key key,
    this.diameterRatio = _kDefaultDiameterRatio,
    this.backgroundColor,
    this.offAxisFraction = 0.0,
    this.useMagnifier = false,
    this.magnification = 1.0,
    this.scrollController,
    this.squeeze = _kSqueeze,
    @required this.itemExtent,
    @required this.onSelectedItemChanged,
    @required IndexedWidgetBuilder itemBuilder,
    int childCount,
  }) : assert(itemBuilder != null),
       assert(diameterRatio != null),
       assert(diameterRatio > 0.0, RenderListWheelViewport.diameterRatioZeroMessage),
       assert(magnification > 0),
       assert(itemExtent != null),
       assert(itemExtent > 0),
       assert(squeeze != null),
       assert(squeeze > 0),
       childDelegate = ListWheelChildBuilderDelegate(builder: itemBuilder, childCount: childCount),
       super(key: key);

其他参数:

1.diameterRatio:直径比,double类型。

2. backgroundColor,背景颜色。

3. offAxisFraction,轴偏移,默认是0.0。控制选中的子widget的左右偏移

4. useMagnifier: 放大效果,默认false。

5. magnification: 放大倍数,需先开启放大效果,此参数才有作用。

6. scrollController:控制器

7.squeeze:压缩,这个控制的children之间的空隙,和diameterRatio的效果有相似之处。

flutter作为跨平台UI框架,最出色的莫过于快速构建出想要的UI效果。这个CupertinoPicker使用简单,操作方便。

 

升级到flutter 2.0之后,CupertinoPicker的item样式有所改变,不再是默认的上下横线分割样式,而是变成了圆角灰色背景。

CupertinoPicker 在Flutter 2.0  中的源码如下:

CupertinoPicker.builder({
    Key? key,
    this.diameterRatio = _kDefaultDiameterRatio,
    this.backgroundColor,
    this.offAxisFraction = 0.0,
    this.useMagnifier = false,
    this.magnification = 1.0,
    this.scrollController,
    this.squeeze = _kSqueeze,
    required this.itemExtent,
    required this.onSelectedItemChanged,
    required NullableIndexedWidgetBuilder itemBuilder,
    int? childCount,
    this.selectionOverlay = const CupertinoPickerDefaultSelectionOverlay(),
  }) : assert(itemBuilder != null),
       assert(diameterRatio != null),
       assert(diameterRatio > 0.0, RenderListWheelViewport.diameterRatioZeroMessage),
       assert(magnification > 0),
       assert(itemExtent != null),
       assert(itemExtent > 0),
       assert(squeeze != null),
       assert(squeeze > 0),
       childDelegate = ListWheelChildBuilderDelegate(builder: itemBuilder, childCount: childCount),
       super(key: key);

与1.*版本不同的是,多了个 selectionOverlay 字段,也就是选中样式。2.0中选中样式默认是CupertinoPickerDefaultSelectionOverlay。

this.selectionOverlay = const CupertinoPickerDefaultSelectionOverlay()

CupertinoPickerDefaultSelectionOverlay也是个widget,代码如下:

class CupertinoPickerDefaultSelectionOverlay extends StatelessWidget {
 
 
  const CupertinoPickerDefaultSelectionOverlay({
    Key? key,
    this.background = CupertinoColors.tertiarySystemFill,
    this.capLeftEdge = true,
    this.capRightEdge = true,
  }) : assert(background != null),
       assert(capLeftEdge != null),
       assert(capRightEdge != null),
       super(key: key);
 
  ......
 
  @override
  Widget build(BuildContext context) {
    const Radius radius = Radius.circular(_defaultSelectionOverlayRadius);
 
    return Container(
      margin: EdgeInsets.only(
        left: capLeftEdge ? _defaultSelectionOverlayHorizontalMargin : 0,
        right: capRightEdge ? _defaultSelectionOverlayHorizontalMargin : 0,
      ),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.horizontal(
          left: capLeftEdge ? radius : Radius.zero,
          right: capRightEdge ? radius : Radius.zero,
        ),
        color: CupertinoDynamicColor.resolve(background, context),
      ),
    );
  }
}

如果还想要1.0的上下横线分割的样式,可以参考1.0中CupertinoPicker的源码,关键代码如下所示:

/// Draws the magnifier borders.
  Widget _buildMagnifierScreen() {
    final Color resolvedBorderColor = CupertinoDynamicColor.resolve(_kHighlighterBorder, context);
 
    return IgnorePointer(
      child: Center(
        child: Container(
          decoration: BoxDecoration(
            border: Border(
              top: BorderSide(width: 0.0, color: resolvedBorderColor),
              bottom: BorderSide(width: 0.0, color: resolvedBorderColor),
            ),
          ),
          constraints: BoxConstraints.expand(
            height: widget.itemExtent * widget.magnification,
          ),
        ),
      ),
    );
  }

当然还可以自定义,代码如下所示:

 // 中间分割线
  Widget _selectionOverlayWidget(){
    return Padding(
      padding: EdgeInsets.only(left: 0, right: 0),
      child: Column(
        children: [
          Divider(
            height: 1,
            color: AppColor.green86Color,
          ),
          Expanded(child: Container()),
          Divider(
            height: 1,
            color: AppColor.green86Color,
          ),
        ],
      ),
    );
  }

使用:

CupertinoPicker(
                  key: key,
                  useMagnifier: true,
                  magnification: 1.2,
                  selectionOverlay: _selectionOverlayWidget(),
                  itemExtent: 34,
                  onSelectedItemChanged: (v){},
                  children: models.map((e) => _itemsWidget(e.name)).toList()),
            ))

延伸:

flutter 自定义城市选择器

因为城市选择的数据是从服务器上拿的的,在pub上面也没有找到合适插件,索性就自己写了一个,在写的过程也遇到很多问题,其实就是三个 CupertinoPicker 组合在一起的,当时写的过程中发现 CupertinoPicker setState不更新 以及onSelectedItemChanged 调用的问题

CupertinoPicker 不更新可以通过 GlobalKey 来解决 onSelectedItemChanged 调用问题可以通过 NotificationListener监听来拿到当前的索引

这里分享一下实现代码

import 'dart:async';
 
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:zhengda_health/app/custom_widgets/custom_text.dart';
import 'package:zhengda_health/app/http_util/http_api.dart';
import 'package:zhengda_health/app/http_util/http_util.dart';
import 'package:zhengda_health/app/support/app_color.dart';
 
//省市区类型
enum CityType {
  province,
  city,
  area
}
 
class CityAlertView extends StatefulWidget {
 
  CityAlertViewDelegate delegate;
  CityAlertView({Key key,this.delegate}) : super(key: key);
 
  @override
  _CityAlertViewState createState() => _CityAlertViewState();
}
 
class _CityAlertViewState extends State<CityAlertView> {
 
  List <CityAlertModel> _provinceList = [];
 
  List <CityAlertModel> _cityList = [];
 
  List <CityAlertModel> _areaList = [];
 
  GlobalKey _provinceGlobalKey = GlobalKey();
 
  GlobalKey _cityGlobalKey = GlobalKey();
 
  GlobalKey _areaGlobalKey = GlobalKey();
 
  int _provinceIndex = 0;
 
  int _cityIndex = 0;
 
  int _areaIndex = 0;
 
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
 
 
    _getAreaData(cityType:CityType.province,pid: '0' ,onSuccess: (){
      _getAreaData(cityType: CityType.city,pid: _provinceList.first.adcode,onSuccess: (){
          _getAreaData(cityType:  CityType.area,pid: _cityList.first.adcode,onSuccess: (){
 
          });
      });
    });
 
  }
 
  void _getAreaData({CityType cityType,String pid,Function onSuccess}){
    HttpUtil.getHttp('${HttpApi.areaInfo}?pid=$pid',onSuccess: (res){
      List<CityAlertModel> list =List<CityAlertModel>.from(res['areaLists'].map((it) => CityAlertModel.fromJson(it)));
 
      if(cityType == CityType.province){
        _provinceGlobalKey  =GlobalKey();;
        _provinceList = list ;
 
      }else if(cityType == CityType.city){
 
        _cityGlobalKey  =GlobalKey();;
        _cityList =list;
      }else{
        _areaGlobalKey  =GlobalKey();;
        _areaList = list;
      }
      setState(() {});
      onSuccess();
 
    });
  }
 
  //确定生成回调
  void _confirmClick(BuildContext context ){
    if(widget.delegate != null){
      widget.delegate.confirmClick([_provinceList[_provinceIndex],_cityList[_cityIndex],_areaList[_areaIndex]]);
    }
    Navigator.of(context).pop();
 
  }
 
  //取消
  void _canlClick(BuildContext context){
    Navigator.of(context).pop();
  }
 
  @override
  Widget build(BuildContext context) {
    return SafeArea(child: Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        SizedBox(height: 8,),
        _headerWidget(context),
        Row(
          children: [
 
            _pickerViewWidget(models:_provinceList,
                key:_provinceGlobalKey ,
                onSelectedItemChanged: (v){
              _provinceIndex = v;
              _getAreaData(cityType: CityType.city,pid: _provinceList[v].adcode,onSuccess: (){
                _getAreaData(cityType: CityType.area,pid: _cityList.first.adcode,onSuccess: (){
                });
              });
 
            }),
 
            _pickerViewWidget(models: _cityList,key:_cityGlobalKey,onSelectedItemChanged: (v){
              _cityIndex = v;
              _getAreaData(cityType: CityType.area,pid: _cityList[v].adcode,onSuccess: (){});
            } ),
 
            _pickerViewWidget(models: _areaList,key:_areaGlobalKey,onSelectedItemChanged: (v){
              _areaIndex =v;
            } ),
          ],
        )
      ],
    ));
  }
 
  Widget _headerWidget(BuildContext context){
    return Row(
      children: [
        _buttonWidget(title: '取消',textColor: Colors.black38,callback: (){
          _canlClick(context);
        }),
        Expanded(child: Container()),
        _buttonWidget(title: '确定',textColor: Colors.black,callback: (){
          _confirmClick(context);
        }),
      ],
    );
  }
 
  //piceerView
  Widget _pickerViewWidget({List<CityAlertModel> models,Key key,
    ValueChanged<int> onSelectedItemChanged,}){
    return Expanded(
        child: SizedBox(
            height: 200,
            child: NotificationListener(
              onNotification: (Notification scrollNotification) {
                  if (scrollNotification is ScrollEndNotification &&
                      scrollNotification.metrics is FixedExtentMetrics)
                  {
                    print((scrollNotification.metrics as FixedExtentMetrics).itemIndex); // Index of the list
                    onSelectedItemChanged((scrollNotification.metrics as FixedExtentMetrics).itemIndex);
 
                    return true;
                  } else {
                    return false;
                  }
              },
              child: CupertinoPicker(
                  key: key,
                  useMagnifier: true,
                  magnification: 1.2,
                  selectionOverlay: _selectionOverlayWidget(),
                  itemExtent: 34,
                  onSelectedItemChanged: (v){},
                  children: models.map((e) => _itemsWidget(e.name)).toList()),
            ))
    );
  }
 
  // 中间分割线
  Widget _selectionOverlayWidget(){
    return Padding(
      padding: EdgeInsets.only(left: 0, right: 0),
      child: Column(
        children: [
          Divider(
            height: 1,
            color: AppColor.green86Color,
          ),
          Expanded(child: Container()),
          Divider(
            height: 1,
            color: AppColor.green86Color,
          ),
        ],
      ),
    );
  }
 
  // cellItems
  Widget _itemsWidget(e){
    return Container(
      alignment: Alignment.center,
      child:  CustomText(e,fontSize: 14,),
    );
  }
 
  //公共button
  Widget _buttonWidget({String title ,Color textColor ,VoidCallback callback}){
    return  InkWell(
      onTap: callback,
      child: Container(
        alignment: Alignment.center,
        padding:EdgeInsets.only(left: 16,right: 16),
        height: 40,
        child: CustomText(title,color: textColor,),
      ),
    );
  }
 
}
 
abstract class  CityAlertViewDelegate{
  void confirmClick(List<CityAlertModel> models){}
}
 
class CityAlertModel {
  int id;
  String pAdcode;
  String adcode;
  String name;
  String level;
  String pinyin;
  String first;
  String lng;
  String lat;
 
  CityAlertModel(
      {this.id,
        this.pAdcode,
        this.adcode,
        this.name,
        this.level,
        this.pinyin,
        this.first,
        this.lng,
        this.lat});
 
  CityAlertModel.fromJson(Map<String, dynamic> json) {
    id = json['id'];
    pAdcode = json['p_adcode'];
    adcode = json['adcode'];
    name = json['name'];
    level = json['level'];
    pinyin = json['pinyin'];
    first = json['first'];
    lng = json['lng'];
    lat = json['lat'];
  }
 
  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['id'] = this.id;
    data['p_adcode'] = this.pAdcode;
    data['adcode'] = this.adcode;
    data['name'] = this.name;
    data['level'] = this.level;
    data['pinyin'] = this.pinyin;
    data['first'] = this.first;
    data['lng'] = this.lng;
    data['lat'] = this.lat;
    return data;
  }
}

调用

class _AddAddressPageState extends State<AddAddressPage> implements CityAlertViewDelegate 
要implements 实现回调协议
  //回调省市区
  @override
  void confirmClick(List<CityAlertModel> models) {}
 
//调用弹框
  showModalBottomSheet(
        context: context,
        shape: RoundedRectangleBorder(
            borderRadius: BorderRadiusDirectional.circular(10)),
        builder: (BuildContext context) {
          return CityAlertView(delegate: this,);
        });

CupertinoPicker组件的二次封装

SinglePickerWidget

SinglePickerWidget是我封装的组件之一,主要是为了实现UI设计的picker效果,效果图如下,需要单位和值分开:

正常的Flutter CupertinoPicker组件是没办法实现的:

 

所以对CupertinoPicker组件进行了二次封装。

功能实现思路

首先说下我的实现思路,是把单位用Position组件包裹,然后进行定位到选中的一行位置。这样滑动单位的区域也会滑动SinglePickerWidget组件。

代码

由于是为了实现UI设计需求,所以组件没有做的很灵活,具体使用时还需要更改部分代码。

 

import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
 
///
/// Author: chengzan
/// Date: 2020-6-8
/// Describe: 单个选择picker
///
 
class SinglePickerWidget extends StatefulWidget {
  final List<Map> values;
  final value;
  final double itemHeight;
  final double height;
  final double width;
  final String unit;
  final Function onChanged;
  final Color backgroundColor;
 
  const SinglePickerWidget(
      {Key key,
      @required this.values,
      @required this.value,
      @required this.onChanged,
      this.unit,
      this.itemHeight = 37.5,
      this.backgroundColor = const Color(0xffffffff),
      this.height = 150.0,
      this.width = 150.0})
      : super(key: key);
  @override
  _SinglePickerWidgetState createState() => _SinglePickerWidgetState();
}
 
class _SinglePickerWidgetState extends State<SinglePickerWidget> {
  int _selectedColorIndex = 0;
  FixedExtentScrollController scrollController;
  var values;
  var value;
 
  //设置防抖周期为300毫秒
  Duration durationTime = Duration(milliseconds: 300);
  Timer timer;
 
  @override
  void initState() {
    super.initState();
    values = widget.values;
    value = widget.value;
    getDefaultValue();
    scrollController =
        FixedExtentScrollController(initialItem: _selectedColorIndex);
  }
 
  @override
  void dispose() {
    super.dispose();
    scrollController.dispose();
    timer?.cancel();
  }
 
  // 获取默认选择值
  getDefaultValue() {
    // 查找要选择的默认值
    for (var i = 0; i < values.length; i++) {
      if (values[i]["value"] == value) {
        setState(() {
          _selectedColorIndex = i;
        });
        break;
      }
    }
  }
 
  // 触发值改变
  void _changed(index) {
    timer?.cancel();
    timer = new Timer(durationTime, () {
      // 触发回调函数
      widget.onChanged(values[index]["value"]);
    });
  }
 
  Widget _buildColorPicker(BuildContext context) {
    return Container(
      height: widget.height,
      color: Colors.white,
      child: Stack(
        alignment: Alignment.center,
        children: [
          widget.unit != null
              ? Positioned(
                  top: widget.height / 2 - (widget.itemHeight / 2),
                  left: widget.width / 2 + 18.0,
                  child: Container(
                    alignment: Alignment.center,
                    height: widget.itemHeight,
                    child: Text(
                      widget.unit,
                      style: TextStyle(
                        color: Color(0xff333333),
                        fontSize: 16.0,
                        height: 1.5,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ),
                )
              : Offstage(
                  offstage: true,
                ),
          CupertinoPicker(
            magnification: 1.0, // 整体放大率
            scrollController:
                scrollController, // 用于读取和控制当前项的FixedxtentScrollController
            itemExtent: widget.itemHeight, // 所有子节点 统一高度
            useMagnifier: true, // 是否使用放大效果
            backgroundColor: Colors.transparent,
            onSelectedItemChanged: (int index) {
              // 当正中间选项改变时的回调
              if (mounted) {
                print('index--------------$index');
                _changed(index);
              }
            },
            children: List<Widget>.generate(values.length, (int index) {
              return Container(
                alignment: Alignment.center,
                height: widget.itemHeight,
                child: Text(
                  values[index]["label"],
                  style: TextStyle(
                    color: Color(0xff333333),
                    fontSize: 21.0,
                    height: 1.2,
                    fontWeight: FontWeight.w500,
                  ),
                ),
              );
            }),
          ),
        ],
      ),
    );
  }
 
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: widget.height,
      width: widget.width,
      child: CupertinoPageScaffold(
        child: Container(
          child: ListView(
            padding: EdgeInsets.all(0),
            children: <Widget>[
              _buildColorPicker(context),
            ],
          ),
        ),
      ),
    );
  }
}
 

使用

// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
 
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
 
void main() => runApp(MyApp());
 
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
 
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
 
  final String title;
 
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
 
class _MyHomePageState extends State<MyHomePage> {
  List<Map> values = [
    {"label": "1", "value": 1},
    {"label": "2", "value": 2},
    {"label": "3", "value": 3},
    {"label": "4", "value": 4},
    {"label": "5", "value": 5}
  ];
 
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Container(
      constraints: BoxConstraints(maxWidth: 160),
      alignment: Alignment.centerLeft,
      child: SinglePickerWidget(
        values: values,
        value: 2,
        width: 150,
        itemHeight: 50,
        height: 250,
        unit: 'm',
        onChanged: (val) {
          print('val----$val');
        },
      ),
    )
      ],
    );
  }
}
 
///
/// Author: chengzan
/// Date: 2020-6-8
/// Describe: 单个选择picker
///
 
class SinglePickerWidget extends StatefulWidget {
  final List<Map> values;
  final value;
  final double itemHeight;
  final double height;
  final double width;
  final String unit;
  final Function onChanged;
  final Color backgroundColor;
 
  const SinglePickerWidget(
      {Key key,
      @required this.values,
      @required this.value,
      @required this.onChanged,
      this.unit,
      this.itemHeight = 37.5,
      this.backgroundColor = const Color(0xffffffff),
      this.height = 150.0,
      this.width = 150.0})
      : super(key: key);
  @override
  _SinglePickerWidgetState createState() => _SinglePickerWidgetState();
}
 
class _SinglePickerWidgetState extends State<SinglePickerWidget> {
  int _selectedColorIndex = 0;
  FixedExtentScrollController scrollController;
  var values;
  var value;
 
  //设置防抖周期为300毫秒
  Duration durationTime = Duration(milliseconds: 300);
  Timer timer;
 
  @override
  void initState() {
    super.initState();
    values = widget.values;
    value = widget.value;
    getDefaultValue();
    scrollController =
        FixedExtentScrollController(initialItem: _selectedColorIndex);
  }
 
  @override
  void dispose() {
    super.dispose();
    scrollController.dispose();
    timer?.cancel();
  }
 
  // 获取默认选择值
  getDefaultValue() {
    // 查找要选择的默认值
    for (var i = 0; i < values.length; i++) {
      if (values[i]["value"] == value) {
        setState(() {
          _selectedColorIndex = i;
        });
        break;
      }
    }
  }
 
  // 触发值改变
  void _changed(index) {
    timer?.cancel();
    timer = new Timer(durationTime, () {
      // 回调函数
      widget.onChanged(values[index]["value"]);
    });
  }
 
  Widget _buildColorPicker(BuildContext context) {
    return Container(
      height: widget.height,
      color: Colors.white,
      child: Stack(
        alignment: Alignment.center,
        children: [
          widget.unit != null
              ? Positioned(
                  top: widget.height / 2 - (widget.itemHeight / 2),
                  left: widget.width / 2 + 18.0,
                  child: Container(
                    alignment: Alignment.center,
                    height: widget.itemHeight,
                    child: Text(
                      widget.unit,
                      style: TextStyle(
                        color: Color(0xff333333),
                        fontSize: 16.0,
                        height: 1.5,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ),
                )
              : Offstage(
                  offstage: true,
                ),
          CupertinoPicker(
            magnification: 1.0, // 整体放大率
            scrollController:
                scrollController, // 用于读取和控制当前项的FixedxtentScrollController
            itemExtent: widget.itemHeight, // 所以子节点 统一高度
            useMagnifier: true, // 是否使用放大效果
            backgroundColor: Colors.transparent,
            onSelectedItemChanged: (int index) {
              // 当正中间选项改变时的回调
              if (mounted) {
                print('index--------------$index');
                _changed(index);
              }
            },
            children: List<Widget>.generate(values.length, (int index) {
              return Container(
                alignment: Alignment.center,
                height: widget.itemHeight,
                child: Text(
                  values[index]["label"],
                  style: TextStyle(
                    color: Color(0xff333333),
                    fontSize: 21.0,
                    height: 1.2,
                    fontWeight: FontWeight.w500,
                  ),
                ),
              );
            }),
          ),
        ],
      ),
    );
  }
 
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: widget.height,
      width: widget.width,
      child: CupertinoPageScaffold(
        child: Container(
          child: ListView(
            padding: EdgeInsets.all(0),
            children: <Widget>[
              _buildColorPicker(context),
            ],
          ),
        ),
      ),
    );
  }
}
 

 

相关文章
|
6月前
|
iOS开发
Flutter中好用的Widget-CupertinoPicker
Flutter中好用的Widget-CupertinoPicker CupertinoPicker是Flutter框架中的一个非常实用且美观的Widget,它可以让用户从一个预设的列表中选择一个或多个值。在iOS设计风格的应用程序中,CupertinoPicker非常常见。
378 0
|
1月前
|
Android开发 iOS开发 容器
鸿蒙harmonyos next flutter混合开发之开发FFI plugin
鸿蒙harmonyos next flutter混合开发之开发FFI plugin
|
5月前
|
开发框架 前端开发 测试技术
Flutter开发常见问题解答
Flutter开发常见问题解答
|
1月前
|
开发者
鸿蒙Flutter实战:07-混合开发
鸿蒙Flutter混合开发支持两种模式:1) 基于har包,便于主项目开发者无需关心Flutter细节,但不支持热重载;2) 基于源码依赖,利于代码维护与热重载,需配置Flutter环境。项目结构包括AppScope、flutter_module等目录,适用于不同开发需求。
75 3
|
18天前
|
传感器 开发框架 物联网
鸿蒙next选择 Flutter 开发跨平台应用的原因
鸿蒙(HarmonyOS)是华为推出的一款旨在实现多设备无缝连接的操作系统。为了实现这一目标,鸿蒙选择了 Flutter 作为主要的跨平台应用开发框架。Flutter 的跨平台能力、高性能、丰富的生态支持和与鸿蒙系统的良好兼容性,使其成为理想的选择。通过 Flutter,开发者可以高效地构建和部署多平台应用,推动鸿蒙生态的快速发展。
136 0
|
20天前
|
Dart 安全 UED
Flutter&鸿蒙next中的表单封装:提升开发效率与用户体验
在移动应用开发中,表单是用户与应用交互的重要界面。本文介绍了如何在Flutter中封装表单,以提升开发效率和用户体验。通过代码复用、集中管理和一致性的优势,封装表单组件可以简化开发流程。文章详细讲解了Flutter表单的基础、封装方法和表单验证技巧,帮助开发者构建健壮且用户友好的应用。
61 0
|
1月前
|
开发框架 移动开发 Android开发
安卓与iOS开发中的跨平台解决方案:Flutter入门
【9月更文挑战第30天】在移动应用开发的广阔舞台上,安卓和iOS两大操作系统各自占据半壁江山。开发者们常常面临着选择:是专注于单一平台深耕细作,还是寻找一种能够横跨两大系统的开发方案?Flutter,作为一种新兴的跨平台UI工具包,正以其现代、响应式的特点赢得开发者的青睐。本文将带你一探究竟,从Flutter的基础概念到实战应用,深入浅出地介绍这一技术的魅力所在。
79 7
|
1月前
|
编解码 Dart API
鸿蒙Flutter实战:06-使用ArkTs开发Flutter鸿蒙插件
本文介绍了如何开发一个 Flutter 鸿蒙插件,实现 Flutter 与鸿蒙的混合开发及双端消息通信。通过定义 `MethodChannel` 实现 Flutter 侧的 token 存取方法,并在鸿蒙侧编写 `EntryAbility` 和 `ForestPlugin`,使用鸿蒙的首选项 API 完成数据的读写操作。文章还提供了注意事项和参考资料,帮助开发者更好地理解和实现这一过程。
58 0
|
1月前
|
Dart Android开发
鸿蒙Flutter实战:03-鸿蒙Flutter开发中集成Webview
本文介绍了在OpenHarmony平台上集成WebView的两种方法:一是使用第三方库`flutter_inappwebview`,通过配置pubspec.lock文件实现;二是编写原生ArkTS代码,自定义PlatformView,涉及创建入口能力、注册视图工厂、处理方法调用及页面构建等步骤。
54 0
|
2月前
|
JSON Dart Java
flutter开发多端平台应用的探索
flutter开发多端平台应用的探索
50 6