flutter 升级到 1.12.x android 启动时会黑屏解决办法

  1. 直接用flutter 1.12.x SDK创建的项目

    需要修改AndroidManifest.xml文件,修改如下:

    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
    <application
    android:name="io.flutter.app.FlutterApplication"
    android:label="ifeiyv"
    android:icon="@mipmap/ic_launcher">
    <activity
    android:name=".MainActivity"
    android:launchMode="singleTop"
    android:theme="@style/LaunchTheme"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
    android:hardwareAccelerated="true"
    android:windowSoftInputMode="adjustResize">
    //
    <meta-data
    android:name="io.flutter.embedding.android.SplashScreenDrawable"
    android:resource="@drawable/launch_background"
    />
    <meta-data
    android:name="io.flutter.embedding.android.NormalTheme"
    android:resource="@drawable/launch_background"
    />
    <intent-filter>
    <action android:name="android.intent.action.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
    </activity>
    <!-- Don't delete the meta-data below.
    This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
    <meta-data
    android:name="flutterEmbedding"
    android:value="2" />
    </application>

    如果存在meta-dataandroid:name="io.flutter.app.android.SplashScreenUntilFirstFrame",删除此meta-data,添加如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    <meta-data
    android:name="io.flutter.embedding.android.SplashScreenDrawable"
    android:resource="@drawable/launch_background"
    />
    <meta-data
    android:name="io.flutter.embedding.android.NormalTheme"
    android:resource="@drawable/launch_background"
    />

    修改launch_background.xml,这个要根据需求添加开屏视图

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?xml version="1.0" encoding="utf-8"?>
    <!-- Modify this file to customize your launch splash screen -->
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
    android:left="0dp"
    android:top="0dp"
    android:bottom="0dp"
    android:right="0dp"
    android:drawable="@drawable/pic_bg" />
    <!-- You can insert your own image assets here -->
    </layer-list>

    <application>标签下添加新的 <meta-data> ,一旦您在AndroidManifest中进行了声明并使用了插件,能够使用使用了新的Android的插件(将插件注册为,FlutterEngine而不是PluginRegistry.Registrar

    1
    2
    3
    < meta-data 
    androidname = “flutterEmbedding”
    androidvalue = “2” />
  1. 如果是低版本创建的项目升级,除了以上修改外还需要修改以下内容:

    1
    2
    3
    4
    5
    //FlutterActivity包路径修改
    //把
    import io.flutter.app.FlutterActivity;
    //修改为
    import io.flutter.embedding.android.FlutterActivity;

以前

1
2
3
4
5
6
7
8
9
10
11
12
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}

// ...some amount of custom code for your app is here.
}

现在

1
2
3
4
5
6
7
8
9
10
11
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}
  1. 这里仅有修复黑屏的问题,更多升级详细信息请前往 官方升级指南 -》 前往

使用AutomaticKeepAliveClientMixin 警告⚠️ This method overrides a method annotated as @mustCallSuper in 'AutomaticKeepAliveClientMixin', but doesn't invoke the overridden method.

该方法将覆盖在’AutomaticKeepAliveClientMixin’中标注为@mustCallSuper的方法,但不会调用覆盖的方法。

解决方案:

  1. 检查 添加AutomaticKeepAliveClientMixin

  2. 添加

    1
    2
    @override <br>
    bool get wantKeepAlive => true;
  3. build方法中记得调用父类方法super.build(context);

    1
    2
    3
    4
    Widget build(BuildContext context) {
    super.build(context);
    return Container();
    }

解决键盘弹起遮挡问题

  • Scaffold的resizeToAvoidBottomPadding属性(v1.1.9之后已废弃)
  • resizeToAvoidBottomInset:为true键盘弹起输入框会自动上移,为false不移动,如果输入框靠下,有可能被遮挡住。默认为true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// This flag is deprecated, please use [resizeToAvoidBottomInset]
/// instead.
///
/// Originally the name referred [MediaQueryData.padding]. Now it refers
/// [MediaQueryData.viewInsets], so using [resizeToAvoidBottomInset]
/// should be clearer to readers.
@Deprecated(
'Use resizeToAvoidBottomInset to specify if the body should resize when the keyboard appears. '
'This feature was deprecated after v1.1.9.'
)
final bool resizeToAvoidBottomPadding;

/// If true the [body] and the scaffold's floating widgets should size
/// themselves to avoid the onscreen keyboard whose height is defined by the
/// ambient [MediaQuery]'s [MediaQueryData.viewInsets] `bottom` property.
///
/// For example, if there is an onscreen keyboard displayed above the
/// scaffold, the body can be resized to avoid overlapping the keyboard, which
/// prevents widgets inside the body from being obscured by the keyboard.
///
/// Defaults to true.
final bool resizeToAvoidBottomInset;
  • 如果以上配置报错(键盘弹起,布局溢出)界面布局可以用滚动容器承载。eg:把当前界面放到SingleChildScrollView上

创建flutter项目命令

进入到要创建项目的位置,在当前目录下创建项目名称为mydemo的项目

  • 基本默认创建 flutter create mydemo

  • 创建一个 iOS 基于 Swift ,Android基于Java的flutter项目

    1
    flutter create  --ios-language swift --android-language kotlin mydemo
  • 创建一个 iOS 基于 Swift ,Android基于kotlin的flutter项目

    1
    flutter create  --ios-language swift --android-language kotlin mydemo
  • 创建一个 iOS 基于 OC ,Android基于kotlin的flutter项目

    1
    flutter create  --ios-language objc --android-language kotlin mydemo
  • 创建一个 iOS 基于 OC ,Android基于java的flutter项目

    1
    flutter create  --ios-language objc --android-language java mydemo
    • VSCode 配置创建

      配置路径 code>preferences>Settings — User > Extensions > Dart & Flutter

flutter

flutter

代码模拟鼠标和键盘事件

网上搜索了一下,基本上都是很早的代码。原理虽然一样,但是代码已经进行多次改版了,特别是现在的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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
//
// FyMKUtils.swift
// SimulationMouseKeyboard
//
// Created by l on 2020/3/30.
// Copyright © 2020 ifeiyv. All rights reserved.
//

import Cocoa

class FyMKUtils: NSObject {

//MARK:移动鼠标到坐标位置
open class func mouseMove(point:CGPoint,button:CGMouseButton = .left){
postMouseEvent(button: button, type: .mouseMoved, point: point);
}

//MARK:左键单击
open class func leftClick(point: CGPoint)
{
click(point: point, button: .left)
}
//MARK:左键双击
open class func leftDoubleClick(point: CGPoint)
{
doubleClick(point: point, button: .left)
}

//MARK:左键拖拽
///point : 初始位置坐标
///toPoint : 拖拽到的目的位置坐标
open class func leftMouseDragged(point:CGPoint,toPoint:CGPoint){
mouseDragged(point:point,toPoint:toPoint,button:.left)
}

//MARK:右键单击
open class func rightClick(point: CGPoint)
{
click(point: point, button: .right)
}

//MARK:右键双击
open class func rightDoubleClick(point: CGPoint)
{
doubleClick(point: point, button: .right)
}

//MARK:右键拖拽
///point : 初始位置坐标
///toPoint : 拖拽到的目的位置坐标
open class func rightMouseDragged(point:CGPoint,toPoint:CGPoint){
mouseDragged(point:point,toPoint:toPoint,button:.right)
}

//MARK:鼠标从一个坐标移动到另一个坐标
open class func mouseMove(point:CGPoint, toPoint:CGPoint){

//拖到的目的位置x大于原始位置的X坐标
let toMaxX:Bool = toPoint.x - point.x > 0
//拖到的目的位置y大于原始位置的Y坐标
let toMaxY:Bool = toPoint.y - point.y > 0

var tempPointY = point.y
var tempPointX = point.x


let blockOperation = BlockOperation()

//1.拖拽目的坐标的Y坐标
blockOperation.addExecutionBlock {
while toMaxY ? (toPoint.y > tempPointY) : (toPoint.y < tempPointY){
toMaxY ? (tempPointY += 1) : (tempPointY -= 1)
postMouseEvent(button: .left, type: .mouseMoved, point: CGPoint(x: tempPointX, y: tempPointY),clickCount: 1);
Thread.sleep(forTimeInterval: 0.001)
}
}
//2.拖拽目的坐标的X坐标
blockOperation.addExecutionBlock {
while toMaxX ? (toPoint.x > tempPointX) : (toPoint.x < tempPointX) {
toMaxX ? (tempPointX += 1) : (tempPointX -= 1)
postMouseEvent(button: .left, type: .mouseMoved, point: CGPoint(x: tempPointX, y: tempPointY),clickCount: 1);
Thread.sleep(forTimeInterval: 0.001)
}

}
//开始执行Operation
blockOperation.start()

}


//MARK:拖拽鼠标事件
open class func mouseDragged(point:CGPoint,toPoint:CGPoint,button:CGMouseButton){
//拖到的目的位置x大于原始位置的X坐标
let toMaxX:Bool = toPoint.x - point.x > 0
//拖到的目的位置y大于原始位置的Y坐标
let toMaxY:Bool = toPoint.y - point.y > 0

var tempPointY = point.y
var tempPointX = point.x

//1.按下鼠标
postMouseEvent(button: button, type: button == .left ? .leftMouseDown : .rightMouseDown, point: point,clickCount: 1);

let blockOperation = BlockOperation()

//2.拖拽目的坐标的Y坐标
blockOperation.addExecutionBlock {
while toMaxY ? (toPoint.y > tempPointY) : (toPoint.y < tempPointY){
toMaxY ? (tempPointY += 1) : (tempPointY -= 1)
postMouseEvent(button: button, type: button == .left ? .leftMouseDragged : .rightMouseDragged, point: CGPoint(x: tempPointX, y: tempPointY),clickCount: 1);
}
}
//3.拖拽目的坐标的X坐标
blockOperation.addExecutionBlock {
while toMaxX ? (toPoint.x > tempPointX) : (toPoint.x < tempPointX) {
toMaxX ? (tempPointX += 1) : (tempPointX -= 1)
postMouseEvent(button: button, type: button == .left ? .leftMouseDragged : .rightMouseDragged, point: CGPoint(x: tempPointX, y: tempPointY),clickCount: 1);
}

}
//4.松开鼠标
blockOperation.completionBlock = {
print("hhhhh")
postMouseEvent(button: button, type: button == .left ? .leftMouseUp : .rightMouseUp, point: toPoint,clickCount: 1);
}
//开始执行Operation
blockOperation.start()
}


//MARK:鼠标单击
open class func click(point: CGPoint,button:CGMouseButton,clickCount:Int64 = 1){
//1.按下鼠标左键(移动到坐标位置后,可以加适当延时再按鼠标左键)
postMouseEvent(button: button, type: button == .left ? .leftMouseDown : .rightMouseDown, point: point,clickCount: clickCount);
//2.松开鼠标左键
postMouseEvent(button: button, type: button == .left ? .leftMouseUp : .rightMouseUp, point: point,clickCount: clickCount);
}
//MARK:鼠标双击
open class func doubleClick(point: CGPoint,button:CGMouseButton){
click(point: point, button: button,clickCount:1)
click(point: point, button: button,clickCount:2)
}


//鼠标事件
private class func postMouseEvent(button:CGMouseButton, type:CGEventType, point: CGPoint,clickCount:Int64 = 1)
{
let event = createMouseEvent(button: button, type: type, point: point,clickCount:clickCount)
event.post(tap: CGEventTapLocation.cghidEventTap)
}
//创建鼠标事件
open class func createMouseEvent(button:CGMouseButton, type:CGEventType, point: CGPoint,clickCount:Int64 = 1) -> CGEvent
{
let event : CGEvent = CGEvent(mouseEventSource: CGEventSource.init(stateID: CGEventSourceStateID.privateState), mouseType: type, mouseCursorPosition: point, mouseButton: button)!
event.setIntegerValueField(CGEventField.mouseEventClickState, value: clickCount)
return event
}

///鼠标滚轮事件目前仅支持OSX 10.13版本以上使用
///postion 横向或者纵向滚动的距离,
///纵向 postion为正数 向下滚动,为负数 向上滚动,横向 postion为正数 向右滚动,为负数 向左滚动
///FyMKUtils.postScrollWheelEvent(position: -10000,scrollOrientation: .horizontal)//向左滚动10000 个像素点
///FyMKUtils.postScrollWheelEvent(position: 10000,scrollOrientation: .horizontal)//向右滚动10000 个像素点
///FyMKUtils.postScrollWheelEvent(position: -10000,scrollOrientation: .vertical)//向上滚动10000 个像素点
///FyMKUtils.postScrollWheelEvent(position: 10000,scrollOrientation: .vertical)//向下滚动10000 个像素点
///scrollOrientation 横向或者纵向
///units: 滚动距离单位 .pixel 像素 .line行。默认像素
@available(OSX 10.13, *)
open class func postScrollWheelEvent(position:Int32 ,scrollOrientation:ScrollOrientation = .vertical,units:CGScrollEventUnit = .pixel){
//翻转偏移值
let tempPosition = -position
let event = CGEvent(scrollWheelEvent2Source:nil, units: units, wheelCount: 2, wheel1: scrollOrientation == .vertical ? tempPosition : 0, wheel2: scrollOrientation == .horizontal ? tempPosition : 0,wheel3: 0)
event?.post(tap: .cghidEventTap)

}

//


//MARK:-------------------------------
//MARK:键盘类操作

/*
public struct CGEventFlags : OptionSet {

public init(rawValue: UInt64) /* Flags for events */


/* Device-independent modifier key bits. */

//大小写锁定键处于开启状态(亮灯状态)
public static var maskAlphaShift: CGEventFlags { get }

//Shift 键按下
public static var maskShift: CGEventFlags { get }

//Control 键按下
public static var maskControl: CGEventFlags { get }

//Alt(Option) 键按下
public static var maskAlternate: CGEventFlags { get }

//Command 键按下
public static var maskCommand: CGEventFlags { get }


/* Special key identifiers. */
//Help 键按下
public static var maskHelp: CGEventFlags { get }

//Fn 键按下
public static var maskSecondaryFn: CGEventFlags { get }


/* Identifies key events from numeric keypad area on extended keyboards. */
//数字键 按下
public static var maskNumericPad: CGEventFlags { get }


/* Indicates if mouse/pen movement events are not being coalesced */
//没有鼠标和苹果笔 按下
public static var maskNonCoalesced: CGEventFlags { get }
}
*/

/// 键盘类操作
/// - Parameters:
/// - keyCode: 键盘事件中使用的虚拟键码,CGKeyCode 要使用系统定义好的,需要导入 import Carbon eg A: kVK_ANSI_A
/// - keyDown: keyDown true按下 false 抬起 成对存在
/// - flags: CGEventFlags ---- 用作组合键
/// - ForExample:
/// - K: FyMKUtils.postKeyboardEvent(keyCode: CGKeyCode(kVK_ANSI_K), keyDown: true, flags: .maskNonCoalesced) <br>
/// FyMKUtils.postKeyboardEvent(keyCode: CGKeyCode(kVK_ANSI_K), keyDown: false, flags: .maskNonCoalesced) <br>
/// - Command + KC: FyMKUtils.postKeyboardEvent(keyCode: CGKeyCode(kVK_ANSI_K), keyDown: true, flags: .maskCommand) <br>FyMKUtils.postKeyboardEvent(keyCode: CGKeyCode(kVK_ANSI_K), keyDown: false, flags: .maskCommand)
/// - Command + Shift + K: <br> FyMKUtils.postKeyboardEvent(keyCode: CGKeyCode(kVK_ANSI_K), keyDown: true, flags: [.maskCommand,.maskShift]) <br> FyMKUtils.postKeyboardEvent(keyCode: CGKeyCode(kVK_ANSI_K), keyDown: false, flags: [.maskCommand,.maskShift])
open class func postKeyboardEvent(keyCode:CGKeyCode,keyDown:Bool,flags:CGEventFlags){
let event = CGEvent.init(keyboardEventSource: CGEventSource.init(stateID: CGEventSourceStateID.privateState), virtualKey: keyCode, keyDown: keyDown)
event?.flags = flags
event?.post(tap: .cghidEventTap)
}

}

//MARK:鼠标滚动方向
enum ScrollOrientation {
case horizontal
case vertical
}

FyNetWork

结合RxSwift、Moya、和HandyJSON封装网络请求模板

【该模板已经上传Github】-> 前往Github 获取代码

文件功能

Podfile

1
2
3
//可以根据不同版本,调整库的版本(不同版本可能需要库的版本不同)
pod 'Moya/RxSwift', '~> 12.0.1'
pod 'HandyJSON', '~> 5.0.1'

FyUrls.swift

主要放一些请求Url

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

struct FyUrls {
/// 服务器环境 true: 正服 false: 测服

#if DEBUG
//测试环境
static let service: Bool = false
#else
//正式环境
static let service: Bool = true
#endif

static var domain: String {
// "正服地址" : "测服地址" (这里是网上搜到的开放接口,没有测试地址,两个都写正式地址)
return FyUrls.service ? "https://v1.alapi.cn/" : "https://v1.alapi.cn/"
}


//这里写拼接到域名上的Url
static var searchMusic: String {
return "api/music/search"
}

//.......

}

封装颜色管理类

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

//
// FyColor.swift
// FyColor
//
// Created by l on 2020/3/13.
// Copyright © 2020 ifeiyv. All rights reserved.
//
import UIKit

class FyColors{

///深色模式适配 手动控制适配模式 启用 关闭(如非必要,可移除相关代码)
static let isOpenDarkModel:Bool = true


//MARK: eg文字颜色
//文字颜色 如果有多种文字颜色可以设置多个 eg: labelTextColor
public class var labelTextColor: UIColor {
return darkModeColor(dark:UIColor.white,light:UIColor.black)
}
//文字颜色 如果有多种文字颜色可以设置多个 eg: buttonTextColor
public class var buttonTextColor: UIColor {
return darkModeColor(dark:UIColor.white,light:UIColor.black)
}

//文字颜色 如果有多种文字颜色可以设置多个 eg: fieldTextColor
public class var fieldTextColor: UIColor {
return darkModeColor(dark:UIColor.white,light:UIColor.black)
}

//MARK: eg背景颜色
//背景颜色 如果有多种文字颜色可以设置多个 eg: labelBgColor
public class var labelBgColor: UIColor {
return darkModeColor(dark:UIColor.black,light:UIColor.white)
}
//背景颜色 如果有多种文字颜色可以设置多个 eg: buttonBgColor
public class var buttonBgColor: UIColor {
return darkModeColor(dark:UIColor.black,light:UIColor.white)
}

//背景颜色 如果有多种背景颜色可以设置多个 eg: viewBgColor
public class var viewBgColor: UIColor {
return darkModeColor(dark:UIColor.black,light:UIColor.white)
}

//.........................................
//根据需求增加相对应的颜色即可
//darkModeColor(dark:UIColor.white,light:UIColor.black)
//实际开发中不可能只有 UIColor.white,UIColor.black 两种颜色。
//根据产品需求增加和修改对应的颜色


//检测当前是否是深色模式
class func isDarkStyle() -> Bool{
if(!isOpenDarkModel){
return false
}
if #available(iOS 13.0, *){
let currentMode = UITraitCollection.current.userInterfaceStyle
if(currentMode == .dark){
return true
}
}
return false
}
// 适配 动态颜色
class func darkModeColor(dark:UIColor,light:UIColor) ->UIColor{
if(!isOpenDarkModel){
return light
}
if #available(iOS 13.0, *){
return UIColor{(trainCollection) -> UIColor in
if trainCollection.userInterfaceStyle == .dark{
return dark
}else{
return light
}
}
}
return light
}
}
使用方式:(深色模式切换时,系统会重新渲染颜色,自动在设置好的两种颜色中进行切换)
1
2
3
4
5
6
7
8
label.textColor = FyColors.labelTextColor

label.backgroundColor = FyColors.labelBgColor

//或者在此方法监听深色模式进行手动切换
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
}