KM的博客.

Flutter 实战记录

字数统计: 3.3k阅读时长: 17 min
2021/09/20

The ‘Pods-wii’ target has frameworks with conflicting names: iflymsc.framework

原因是:iflymsc.framework接入 native 中和之前 flutter 中的iflymsc.framework冲突了

解决方式:flutter 缓存删除即可(从主工程的 development pods 的xxx_msc_plugin中删除)

TabController addListener两次回调

问题:这个监听在点击切换tab的时候会回调两次,左右滑动切换tab正常调用一次。

TabController addListener两次回调问题

1
2
3
4
5
6
7
8
9
10
_tabController = new TabController(
length: 3, vsync: this, initialIndex: currentTabIndex);
_tabController.addListener(() {
// 解决TabController多次回调问题
if (_tabController.index == _tabController.animation.value) {
// do somethings
//currentTabIndex = _tabController.index;
}
}
});

flutter 解决中英文自动换行

1
2
3
4
5
6
/// 解决中英文自动换行
extension FixAutoLines on String {
String fixAutoLines() {
return Characters(this).join('\u{200B}');
}
}

pageKey 区分不同页面 UniqueKey().toString()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
handleMsg(arguments) {
// 切换看板-隐藏返回箭头
setState(() {
isShowOverlay = false;
});

Map changeParam = Map%3CString, dynamic%3E.from(arguments ?? Map);

///默认来源mainActivity,非mainActivity来源一定传这个参数,区分来源,否则页面和数据都会有问题
if (widget.pageKey == changeParam[AppConstant.PAGE_KEY]) {
hardCoveTipInfo = null;
param.clear();
changeParam.forEach((key, value) {
if (TextUtils.isNotEmpty(value) && AppConstant.PAGE_KEY != key) {
param[key] = value;
}
});
if (param[AppConstant.CHANNELTYPE] == AppConstant.C4) {
// 处理 C4看板
} else {
// 处理 C5 C6看板

}
}
}

jumpC4Board() {
Map<String, dynamic> localParam = Map.castFrom(widget.param);
localParam["fromC4C5Board"] = "1";// 解决C4看板航道选择器和 返回 C5C6 页面刷新问题
localParam["pageKey"] = UniqueKey().toString();
FlutterBoost.singleton
.open(AnalysisRouteApi.nativeAnalysisHome, urlParams: localParam);
}

图片处理

MultipartFile 转 jpg 失败

1
2
3
4
5
6
List<int> imageData = await asset.originBytes;
MultipartFile multipartFile = new MultipartFile.fromBytes(
imageData,
filename: "asset.jpg",
contentType: MediaType("image", "jpg"),
);

原因是苹果原生相机图片格式 heic 不能直接转 jpg,需要

  • 1 借助 native 将 heic 转 jpg
  • 2 直接压缩 heic 资源:thumbDataWithSize

解决方式一:借助 native插件实现 heic 转 jpg(方法见文末0x0000001)

原生方法:jpegData(compressionQuality: CGFloat)-> -> Data? // return image as JPEG. May return nil if image has no CGImageRef or invalid bitmap format. compression is 0(most)..1(least)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class HeicToJPGPlugin {
static const String _channel = "com.xxxx/heicConvertJpg";

static const String _methodConvertJpg= "convertToJPG";

static const _perform = const MethodChannel(_channel);

static Future<String> convertToJPG(String path) {
return _perform.invokeMethod(_methodConvertJpg, {"heicPath": path});
}
}



bool isHeic = false;
List<int> heicData;
if (asset.title.contains(".HEIC")) {
isHeic = true;
File assetFile =await asset.originFile;
String jpgPath = await HeicToJPGPlugin.convertToJPG(assetFile.path); // JPG String formate
File jpgFile = File(jpgPath);// JPG File formate
final Uint8List byteData = await jpgFile.readAsBytes(); // JPG data formate: Uint8List
heicData = List.from(byteData); // jpg data formate: List<int>
}
List<int> imageData = await asset.originBytes;

解决方式 二: 直接压缩heic

asset.thumbDataWithSize

1
2
3
4
5
6
7
8
9
10
File assetFile =await asset.originFile;
List<int> imageData = await asset.thumbDataWithSize(asset.size.width.toInt(), asset.size.height.toInt(),quality:await getCompressRate(assetFile.path));

if (imageData != null) {
MultipartFile multipartFile = new MultipartFile.fromBytes(
imageData,
filename: "asset.jpg",
contentType: MediaType("image", "jpg"),
);
}

阿里通过原生传递图片内存地址,将指针类 Point 处理成 Image

1
2
3
4
5
6
7
8
9
Pointer<Uint8> pointer = Pointer<Uint8>.fromAddress(handle);
//handle 是 Native 端中一张图像的内存地址
Uint8List pixels = pointer.asTypedList(length);
ui.PixelFormat pixelFormat = Platform.isAndroid ? ui.PixelFormat.
rgba8888 : ui.PixelFormat.bgra8888;
ui.decodeImageFromPixels(pixels, width, height, pixelFormat,
(ui.Image image) {
//这个回调里的 image 对象是 Flutter 可以使用的 image 对象
}, rowBytes: rowBytes);

将原生处理后的图片地址,使用 File 处理为Uint8List

1
2
3
4
5
6
7
8
9
10
import 'dart:typed_data';
File assetFile =await asset.originFile;
// JPG Path
String jpgPath = await HeicToJPGPlugin.convertToJPG(assetFile.path);
// JPG File
File jpgFile = File(jpgPath);
// JPG Uint8List
final Uint8List byteData = await jpgFile.readAsBytes();
// JPG List<int>
List<int> heicData = List.from(byteData); // jpg data formate: List<int>

压缩

1
2
List<int> compressedImageData =  await asset.thumbDataWithSize(asset.size.width.toInt(), asset.size.height.toInt(), quality: 80 );

代码

0x0000001 Swift 插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class FlutterHeicToJpgPlugin: FlutterChannelType {

public var name: String {
return "com.xxx.xxx/plugin/heicConvertJpg"
}

public var method: String {
return "convertToJPG"
}

public func handleMessage(arguments: [String: Any]?,
completion: @escaping FlutterChannelCompletion) {
if let arg = arguments,
let heicPath = arg["heicPath"] as? String {
var jpgPath = heicPath
if jpgPath.count == 0 {
jpgPath = NSTemporaryDirectory().appendingFormat("%d.jpg", Date().timeIntervalSince1970 * 1000)
}
let finalPath = fromHeicToJpg(heicPath: heicPath, jpgPath: jpgPath)
completion(.success(finalPath))
}

}
func fromHeicToJpg(heicPath: String, jpgPath: String) -> String? {
let heicImage : UIImage? = UIImage(named:heicPath)
if heicImage == nil {
return nil
}
let jpgImageData = heicImage!.jpegData(compressionQuality: 1.0)
FileManager.default.createFile(atPath: jpgPath, contents: jpgImageData, attributes: nil)
return jpgPath
}
}

0x00000002 TabController addListener定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/// Register a closure to be called when the object changes.
///
/// If the given closure is already registered, an additional instance is
/// added, and must be removed the same number of times it is added before it
/// will stop being called.
///
/// This method must not be called after [dispose] has been called.
///
/// {@template flutter.foundation.ChangeNotifier.addListener}
/// If a listener is added twice, and is removed once during an iteration
/// (e.g. in response to a notification), it will still be called again. If,
/// on the other hand, it is removed as many times as it was registered, then
/// it will no longer be called. This odd behavior is the result of the
/// [ChangeNotifier] not being able to determine which listener is being
/// removed, since they are identical, therefore it will conservatively still
/// call all the listeners when it knows that any are still registered.
///
/// This surprising behavior can be unexpectedly observed when registering a
/// listener on two separate objects which are both forwarding all
/// registrations to a common upstream object.
/// {@endtemplate}
///
/// See also:
///
/// * [removeListener], which removes a previously registered closure from
/// the list of closures that are notified when the object changes.
@override
void addListener(VoidCallback listener) {
assert(_debugAssertNotDisposed());
_listeners!.add(_ListenerEntry(listener));
}

/// An animation whose value represents the current position of the [TabBar]'s
/// selected tab indicator as well as the scrollOffsets of the [TabBar]
/// and [TabBarView].
///
/// The animation's value ranges from 0.0 to [length] - 1.0. After the
/// selected tab is changed, the animation's value equals [index]. The
/// animation's value can be [offset] by +/- 1.0 to reflect [TabBarView]
/// drag scrolling.
///
/// If this [TabController] was disposed, then return null.
Animation<double>? get animation => _animationController?.view;
AnimationController? _animationController;
```>)

字体

FLutter 字体默认不会跟随系统, Android 端默认使用 Roboto,而在 iOS 端默认使用 San Francisco。- 出处

image

文本

flutter 文本自动换行

RowColumnFlex 组件,是无法滚动的,如果没有足够的空间,flutter就提示溢出错误。

这时候我们可以使用ExpandedFlexible 组件可用作长文本的自动换行。

Column Expanded Text maxLines多行文本计算报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
result!.trackName!,
style:
TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
maxLines: 2,
),
),
SizedBox(
height: 10,
),
Text(
result!.artistName!,
style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
),
],
)

这是因为我们没有给Column 确定的高度,Expanded可以拓展高度但是需要知道边界在哪里。

所以我们修改如下,使用 Container 包裹 Column 来给定高度,同时给 Container 包裹 Expanded

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Expanded(
child: Container(
height: 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
result!.trackName!,
style:
TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
maxLines: 2,
),
),
SizedBox(
height: 10,
),
Text(
result!.artistName!,
style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
),
],
),
),
)

图片问题

压缩png: https://tinypng.com/

image

网络图片圆角显示-ClipRRect
1
2
3
4
5
6
7
8
ClipRRect(
borderRadius: BorderRadius.circular(15),
child: Image.network(
result!.artworkUrl100.toString(),
width: 60,
height: 60,
),
)

MethodChannel通信

Flutter端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
...
class _MyHomePageState extends State<MyHomePage> {
static const platform = const MethodChannel('samples.flutter.io/battery');

@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
child: Text('Get Battery Level'),
onPressed: _getBatteryLevel,
),
Text(_batteryLevel),
],
),
),
);
}

// Get battery level.
String _batteryLevel = 'Unknown battery level.';

Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}

setState(() {
_batteryLevel = batteryLevel;
});
}


}

iOS端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__weak typeof(self) weakSelf = self
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if ([@"getBatteryLevel" isEqualToString:call.method]) {
int batteryLevel = [weakSelf getBatteryLevel];

if (batteryLevel == -1) {
result([FlutterError errorWithCode:@"UNAVAILABLE"
message:@"Battery info unavailable"
details:nil]);
} else {
result(@(batteryLevel));
}
} else {
result(FlutterMethodNotImplemented);
}
}];

Android端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;

public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "samples.flutter.io/battery";

@Override
public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);

new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
// TODO
}
});
}
}
@Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("getBatteryLevel")) {
int batteryLevel = getBatteryLevel();

if (batteryLevel != -1) {
result.success(batteryLevel);
} else {
result.error("UNAVAILABLE", "Battery level not available.", null);
}
} else {
result.notImplemented();
}
}

4、Flutter报错记录

1、Invalid Podfile file: cannot load such file – ../flutter_module/.ios/Flutter/podhelper.rb

原因一是flutter_module文件下缺少.ios /Flutter/podhelper.rb目录

其他原因:podfile.lock 缺少Flutter

1
cd 到flutter项目

1、flutter clean

2、flutter build ios

3、flutter pub get

cd 到iOS项目执行pod install即可解决问题

image

2、 No podspec found for Flutter in ../xxx/xxx/.ios/Flutter/engine

原因是:flutter中的.ios需要重新编译

1
第一步 cd 到flutter项目

1、flutter clean

2、flutter build ios

3、flutter pub get

第二步 cd 到iOS项目执行pod install即可解决问题

3、bitcode bundle could not be generated because

bitcode bundle could not be generated because ‘/Users/xxx/Desktop/Dev/xxx/xxx/.ios/Flutter/engine/Flutter.framework/Flutter’ was built without full bitcode. All frameworks and dylibs for bitcode must be generated from Xcode Archive or Install build file ‘/Users/xxx/Desktop/Dev/xxx/xxx/.ios/Flutter/engine/Flutter.framework/Flutter’ for architecture armv7

使用了flutter的话 需要重新编译flutter

1
2
flutter clean
flutter build ios

4、[!] Invalid Podfile file: cannot load such file – ../xxx/xxx/.ios/Flutter/podhelper.rb.

1
1 cd xxx_platform
1
2 flutter pub get
1
3 pod install

5、Flutter :MediaQuery.of() called with a context that does not contain a MediaQuery

MediaQuery.of() called with a context that does not contain a MediaQuery.

No MediaQuery ancestor could be found starting from the context that was passed to MediaQuery.of().

1
This can happen because you do not have a WidgetsApp or MaterialApp widget (those widgets introduce a MediaQuery), or it can happen if the context you use comes from a widget above those widgets.

The context used was: Scaffold dirty state: ScaffoldState#ba049(lifecycle state: initialized, tickers: tracking 2 tickers)

意思是你使用不包含mediaquery的上下文调用mediaquery.of()。

解决方案:MaterialApp()中home:里包裹你写好的页面即可

image

6、The following NoSuchMethodError was thrown building

*DatePickerDialog(dirty, dependencies: [*LocalizationsScope-[GlobalKey#3867d], MediaQuery, InheritedTheme], state: DatePickerDialogState#9687b):

1
The method 'formatMediumDate' was called on null.

Receiver: null

Tried calling: formatMediumDate(Instance of ‘DateTime’)

The relevant error-causing widget was:

MaterialApp file:///Users/xxx/github/FlutterCoding/flutter_widgets/lib/main.dart:12:12

When the exception was thrown, this was the stack:

#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)

#1 _DatePickerDialogState.build (package:flutter/src/material/pickers/date_picker_dialog.dart:350:23)

#2 StatefulElement.build (package:flutter/src/widgets/framework.dart:4619:28)

#3 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4502:15)

#4 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4675:11)

这是在增加国际化语言 locale: Locale(‘zh’), 后报错:原因是没有在MateirialApp添加supportedLocales:[]

在pubspec.yaml添加支持:

1
2
3
4
5
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter

在顶级控件MaterialApp添加国际化支持:

1
2
3
4
5
6
7
8
9
10
11
12
MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('zh', 'CH'),
const Locale('en', 'US'),
],
locale: Locale('zh'),
...
)

设置showDatePicker的local参数如下:

1
2
3
4
showDatePicker(
locale: Locale('zh'),
...
)

7、Waiting for another flutter command to release the startup lock…

解决办法:

当你的项目异常关闭,或者android studio用任务管理器强制关闭,下次启动就会出现上面的一行话,

此时需要打开 flutter/bin/cache/lockfile,删除就行了

使用:rm ./flutter/bin/cache/lockfile

如果:flutter doctor没有反应,先使用flutter --version

5 打包

![image](/Users/longhuadmin/Library/Application Support/typora-user-images/image-20200723163111932.png)

image

6 flutter 禁止字体大小跟随系统字体改变大小

1、适配页面:修改MaterialApp

1
2
3
4
5
6
7
MaterialApp(
builder: (BuildContext context, Widget child){

return MediaQuery(child: child,data:MediaQuery.of(context).copyWith(textScaleFactor: 1.0),);

},
)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@override
Widget build(BuildContext context) {
//1 ScreenUtil.init
ScreenUtil.init(context, width: 750, height: 1334, allowFontScaling: true);

return Scaffold(
appBar: _buildAppBar(),
backgroundColor: Pigment.fromString(AppColor.colorFFFFFF),
// 2 MediaQuery(data: , child: )
body: MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: Container(
child: new Column(
children: <Widget>[
_buildBodyAppBar(),
_buildHeader(),
Container(
height: 10,
color: Colors.white,
),
// List
Expanded(flex: 1, child: _buildListView()),
],
),
),
),// MediaQuery
);
}
1
MediaQuery _PointerListener

image

但是我当时使用了很多Text 为了以后迭代方便 管理方便我是用了下边的方法

2、适配Text:修改textScaleFactor为1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import 'package:flutter/material.dart';
import 'package:flutter_app2/View/FixedSizeText.dart';

class FixedSizeText extends Text {
const FixedSizeText(String data, {
Key key,
TextStyle style,
StrutStyle strutStyle,
TextAlign textAlign,
TextDirection textDirection,
Locale locale,
bool softWrap,
TextOverflow overflow,
double textScaleFactor = 1,
int maxLines,
String semanticsLabel,
}) : super(data,
key:key,
style:style,
strutStyle:strutStyle,
textAlign:textAlign,
textDirection:textDirection,
locale:locale,
softWrap:softWrap,
overflow:overflow,
textScaleFactor:textScaleFactor,
maxLines:maxLines,
semanticsLabel:semanticsLabel);
}

重点是这一行代码

1
double textScaleFactor = 1,

然后在布局Widget的时候使用FixedSizeText代替Text就可以达到禁止字体大小跟随系统字体改变大小目的了

3、适配TabBar()修改textScaleFactor为1

进入flutter/packages/flutter/lib/src/material/tabs.dart文件,修改_buildLabelText方法如下

1
2
3
Widget _buildLabelText() {
return child ?? Text(text, softWrap: false, overflow: TextOverflow.fade, textScaleFactor: 1);
}

image

7、限制TextFormField输入类型和长度

1
2
3
4
5
6
7
8
/// 10、TextField设置高度后hintText居中
/// TextField设置高度小于默认高度后会出现hintText不居中的情况,经过一番摸索后发现InputDecoration中contentPadding设置padding即可
TextField(
decoration: InputDecoration(
hintText: "用户名或邮箱",
border: InputBorder.none, // 去掉下滑线
contentPadding: EdgeInsets.all(0)
),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
TextFormField(
style: TextStyle(fontSize: 14),
controller: widget.textEditingController,
inputFormatters: listTextInputFormatter,
keyboardType: TextInputType.numberWithOptions(decimal: true),
textAlign: TextAlign.right,
decoration: InputDecoration(
isDense: true,
contentPadding:
EdgeInsets.all(1),
border: InputBorder.none,
hintStyle: TextStyle(
fontSize: 15,
color: Pigment.fromString(AppColor.colorC1C1C1)),
hintText: widget.hintTip ?? "",
),
)

print(‘系统手势边距 -> ${MediaQuery.of(context).systemGestureInsets}’);

iOS CupertinoAlertDialog报错:’alertDialogLabel’ was called on null.

在 main.dart 的 MaterialApp 加入这个属性:

1
2
3
4
5
6
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
YabandLocalizationsDelegate.delegate,
const FallbackCupertinoLocalisationsDelegate(), //加入这个, 上面三个是我用来国际化的
],

然后创建对应的 class :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class FallbackCupertinoLocalisationsDelegate
extends LocalizationsDelegate<CupertinoLocalizations> {
const FallbackCupertinoLocalisationsDelegate();

@override
bool isSupported(Locale locale) => true;

@override
Future<CupertinoLocalizations> load(Locale locale) =>
DefaultCupertinoLocalizations.load(locale);

@override
bool shouldReload(FallbackCupertinoLocalisationsDelegate old) => false;
}

缺少buildBodyAppBar

  • AddedBusinessListFromDistrict 增值业务收入增长率:从地区级增值业务跳转过来
  • AddedValueBusinessIndexRanking: 增值业务收入增长率
  • BusinessChargeFeeRate 收费率
  • BusinessPersonCreateRate 利润增长率 组件
  • BusinessProfitRate 利润增长率 组件
  • BusinessServiceSatisfy 外拓
  • RevenueAchieve 内容:利润增长率 组件

使用CustomScrollView 代替DefaultTabController

  • 解决header手势冲突问题
  • 解决ListView高度不能确定问题
  • 解决Android首页卡顿问题
CATALOG
  1. 1. The ‘Pods-wii’ target has frameworks with conflicting names: iflymsc.framework
  • TabController addListener两次回调
    1. 1. TabController addListener两次回调问题
    2. 2. flutter 解决中英文自动换行
  • pageKey 区分不同页面 UniqueKey().toString()
  • 图片处理
    1. 1. MultipartFile 转 jpg 失败
    2. 2. 解决方式一:借助 native插件实现 heic 转 jpg(方法见文末0x0000001)
    3. 3. 解决方式 二: 直接压缩heic
    4. 4. 阿里通过原生传递图片内存地址,将指针类 Point 处理成 Image
    5. 5. 将原生处理后的图片地址,使用 File 处理为Uint8List
    6. 6. 压缩
  • 代码
    1. 1. 0x0000001 Swift 插件
    2. 2. 0x00000002 TabController addListener定义
  • 字体
  • 文本
    1. 1. flutter 文本自动换行
    2. 2. Column Expanded Text maxLines多行文本计算报错
  • 图片问题
    1. 0.1. 网络图片圆角显示-ClipRRect
  • MethodChannel通信
    1. Flutter端
    2. iOS端
    3. Android端
  • 4、Flutter报错记录
    1. 1. 1、Invalid Podfile file: cannot load such file – ../flutter_module/.ios/Flutter/podhelper.rb
    2. 2. 2、 No podspec found for Flutter in ../xxx/xxx/.ios/Flutter/engine
    3. 3. 3、bitcode bundle could not be generated because
    4. 4. 4、[!] Invalid Podfile file: cannot load such file – ../xxx/xxx/.ios/Flutter/podhelper.rb.
    5. 5. 5、Flutter :MediaQuery.of() called with a context that does not contain a MediaQuery
    6. 6. 6、The following NoSuchMethodError was thrown building
    7. 7. 7、Waiting for another flutter command to release the startup lock…
  • 5 打包
  • 6 flutter 禁止字体大小跟随系统字体改变大小
    1. 1、适配页面:修改MaterialApp
    2. 2、适配Text:修改textScaleFactor为1
    3. 3、适配TabBar()修改textScaleFactor为1
  • 7、限制TextFormField输入类型和长度
  • iOS CupertinoAlertDialog报错:’alertDialogLabel’ was called on null.
  • 缺少buildBodyAppBar
  • 使用CustomScrollView 代替DefaultTabController