[toc]
纯笔记,非技术博客
分类
Android原生项目内嵌Flutter项目现在有两种途径:源码集成以及产物集成
这里借下图来说明:
因为我是只有一个人开发,所以我想先试下源码集成,后续有机会也会试下产物集成
源码集成
新建安卓项目
这一部就不赘述了,安卓狗都知道的
新建flutter目录
这里有些分歧,有些人是直接在android目录里面新建了Flutter的module,有些人是把Flutter新建在和Module相齐的目录,其实应该是后者比较规范一点,毕竟还有ios模块要用嘛。
不过我只有安卓,就还是用普通module的方式吧。
Android Studio创建
这里有2种创建Flutter模块的方法,选自己喜欢的吧
在androidStudio中新建Flutter Module
稍等as一顿操作,这样就可以新建一个Flutter Module了
在命令行创建Flutter Module
flutter create -t module {moduleName}
比如创建一个叫ly_module
flutter create -t module ly_module
在android项目中关联Flutter Module
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
'xxxxx/.android/include_flutter.groovy'
))
在app的build.gradle里面加入对应依赖
implementation project(':flutter')
新建Flutter模块入口
其实就是新建了一个activity,在里面丢进去flutter模块:
/**
* Author: Ly
* Data:2019/4/23-15:49
* Description:
*/
class FlutterEntryActivity : AppCompatActivity() {
companion object {
fun toFlutterEntryActivity(a: Activity) {
a.startActivity(Intent(a, FlutterEntryActivity::class.java))
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_flutter_entry)
val flutterView = Flutter.createView(this, lifecycle, "Ly")
val layout = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
addContentView(flutterView, layout)
}
}
注意,新版本的接入方法
class MainActivity : FragmentActivity() {
companion object {
private const val TAG_FLUTTER_FRAGMENT = "flutter_fragment"
}
private var flutterFragment: FlutterFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fragmentManager: FragmentManager = supportFragmentManager
flutterFragment = fragmentManager
.findFragmentByTag(TAG_FLUTTER_FRAGMENT) as FlutterFragment?
if (flutterFragment == null) {
val newFlutterFragment = FlutterFragment.createDefault()
flutterFragment = newFlutterFragment
fragmentManager
.beginTransaction()
.add(
R.id.fragment_container,
newFlutterFragment,
TAG_FLUTTER_FRAGMENT
)
.commit()
}
}
}
这里在debug模式有个问题:
虽然加入了一个view是很快速的问题,但是初始化也是要时间的,所以在debug模式下,会有一定时间的黑屏时间(测试在release模式下是很ok的,果然flutter的debug模式和release模式是完全不一样的HHHHHH)
这里找了有三个解决方法,我丢在下面的踩坑上面去。
flutter处理
我们上面进入flutter的时候传了3个参数,我们注意下第三个Ly,对应的是一个路由地址,
这个我们在flutter中可以拿到
window.defaultRouteName
所以我们在main.dart中可以有如下处理:
void main() => runApp(_widgetForRoute(window.defaultRouteName));
Widget _widgetForRoute(String route) {
switch (route) {
case "Ly":
return MyApp();
default:
return MyApp();
}
}
这里可以根据路由地址跳转到对应的app地址
以上步骤很多博客都可以看到,接下来我们来讲下容易踩坑的地方
分支问题
现在的时间是2019年4月23日16点53分,版本信息如下:
切记一定要切到Master分支
settings.gradle报错
很多时候会发生setting.gradle 我们加的
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
'xxxxx/.android/include_flutter.groovy'
))
报错,我觉得有2个点可以排查
- 目录路径没有错
- .anroid/文件存在,如上文所说的命令行创建module,其实不会生成.android & .ios 目录,那这里的依赖就会报错了,这个时候可以在flutter窗口下执行
flutter packages get生成下
项目报错问题
依赖完毕后,跑项目,会出现
```java
Invoke-customs are only supported starting with Android O
```
这个时候需要在app的build.gradle文件中的*android{)*结点加入
```gradle
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
```
debug模式下黑屏
显示隐藏方法
- 一开始的的xml布局文件设置为Invisible,注意这里不能设置为GONE,因为两者有渲染与否的区别
- 加载完后再显示出来
val flutterView = Flutter.createView(this, lifecycle, "Ly")
flFlutterContainer.addView(flutterView)
val listeners = arrayOfNulls(1)
listeners[0] = FirstFrameListener { flFlutterContainer.visibility = View.VISIBLE }
flutterView.addFirstFrameListener(listeners[0])
重复设置contentView的方法
```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_flutter_entry)
val flutterView = Flutter.createView(this, lifecycle, "Ly")
val layout = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
addContentView(flutterView, layout)
```
渠道打包的问题(这个坑踩了比较久)
因为我的app是需要打渠道包的,之前也是做了一个很简陋的渠道包方法,也就是在gradle里面设置productFlavors,但是这样在嵌入flutter之后就会出问题了
Check failed: vm. Must be able to initialize the VM.
翻墙翻了好久~也找了很多资料,终于在issues中找到了
相关资料
这里是因为加了渠道,flutter_assets资源拷贝路径出了问题,那既然知道问题了,改吧。
app的build.gradle文件:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
flavorDimensions ""
compileSdkVersion 28
defaultConfig {
applicationId "com.rongyi.cook"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// ndk{abiFilters "armeabi-v7a"}
}
productFlavors {
xiaomi {
manifestPlaceholders = [TYJ_APP_CHANNEL: "xiaomi",]
}
c360 {
manifestPlaceholders = [TYJ_APP_CHANNEL: "c360",]
}
baidu {
manifestPlaceholders = [TYJ_APP_CHANNEL: "baidu",]
}
huawei {
manifestPlaceholders = [TYJ_APP_CHANNEL: "huawei",]
}
yingyongbao {
manifestPlaceholders = [TYJ_APP_CHANNEL: "yingyongbao",]
}
vivo {
manifestPlaceholders = [TYJ_APP_CHANNEL: "vivo",]
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
manifestPlaceholders = [TYJ_APP_CHANNEL: "\\ 19921208015",]
minifyEnabled false
}
}
lintOptions {
disable 'CheckResult'
}
// 自定义输出配置
android.applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "${variant.name}_${variant.versionName}_${variant.name}.apk"
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation project(':easyly')
implementation project(':flutter')
}
这里加入了6个渠道,而对应的,我们也要修改下flutter的build.gradle文件
[图片上传失败...(image-ef6d4f-1556012228778)]
之前的build.gradle:
// Generated file. Do not edit.
def localProperties = new Properties()
def localPropertiesFile = new File(buildscript.sourceFile.parentFile.parentFile, 'local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.library'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.12'
implementation 'com.android.support:support-v13:27.1.1'
implementation 'com.android.support:support-annotations:27.1.1'
}
修改后的build.gradle
// Generated file. Do not edit.
def localProperties = new Properties()
def localPropertiesFile = new File(buildscript.sourceFile.parentFile.parentFile, 'local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.library'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
flavorDimensions ""
defaultConfig {
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
productFlavors {
xiaomi {
manifestPlaceholders = [TYJ_APP_CHANNEL: "xiaomi",]
}
c360 {
manifestPlaceholders = [TYJ_APP_CHANNEL: "c360",]
}
baidu {
manifestPlaceholders = [TYJ_APP_CHANNEL: "baidu",]
}
huawei {
manifestPlaceholders = [TYJ_APP_CHANNEL: "huawei",]
}
yingyongbao {
manifestPlaceholders = [TYJ_APP_CHANNEL: "yingyongbao",]
}
vivo {
manifestPlaceholders = [TYJ_APP_CHANNEL: "vivo",]
}
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.12'
implementation 'com.android.support:support-v13:27.1.1'
implementation 'com.android.support:support-annotations:27.1.1'
}
手动控制draw的开关
打开
// 初始化标题
AppBar _initAppbar(BuildContext context) {
return new AppBar(
backgroundColor: ColorConf.red,
leading: Builder(
builder: (context) => IconButton(
onPressed: () {
debugPrint('openDrawer');
Scaffold.of(context).openDrawer();
},
icon: Icon(
Icons.menu,
color: ColorConf.colorWhite,
),
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
)),
title: new Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'当前位置:',
style: _textStyleForMsg,
),
new Padding(
padding: const EdgeInsets.only(left: 6),
child: Text(
'广州',
style: _textStyleForTitle,
),
)
],
),
centerTitle: true,
elevation: 1,
actions: [
Builder(
builder: (context) => IconButton(
onPressed: () {
Scaffold.of(context).openEndDrawer();
},
icon: Icon(
Icons.chat_bubble_outline,
color: ColorConf.colorWhite,
),
),
)
],
);
}
关闭
AppBar _initAppBar(BuildContext context) {
return AppBar(
backgroundColor: ColorConf.colorRed,
leading: Builder(builder: (context) =>
IconButton(icon: Icon(
Icons.clear,
color: ColorConf.colorWhite,
), onPressed: () {
Navigator.pop(context);
})),
);
}
CustomScrollView 嵌套GridView 滑动冲突
Container _initServiceGriView() {
List iconsStr = [
'assets/invite_frends.png',
'assets/price_standard.png',
'assets/service_center.png',
'assets/invite_driver.png',
'assets/user_feedback.png',
];
List iconsMsg = ['邀请好友', '收费说明', '服务中心', '成为司机', '用户反馈'];
return Container(
alignment: Alignment.centerLeft,
margin: const EdgeInsets.only(top: 20, left: 12),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
alignment: Alignment.centerLeft,
child: Text(
'业务范围',
style: _textStyleForTitle,
textAlign: TextAlign.left,
),
),
Container(
margin: const EdgeInsets.only(top: 20),
child: GridView.builder(
shrinkWrap: true,
primary: false,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4, childAspectRatio: 1.0),
itemCount: iconsMsg.length,
itemBuilder: (context, index) {
return Column(
children: [
Image.asset(
iconsStr[index],
width: 30,
),
Container(
margin: const EdgeInsets.only(top: 10),
child: Text(
iconsMsg[index],
style: _textStyleForIcon,
),
)
],
);
}),
)
],
),
);
}
其中两个属性:
- shrinkWrap 常用于内容大小不确定情况,如果滚动视图(ListView/GridView/ScrollView 等)没有收缩包装,则滚动视图将扩展到允许的最大大小。如果是无界约束,则 shrinkWrap 必须为 true。
- primary 如果为 true,即使滚动视图没有足够的内容来支撑滚动,滚动视图也是可滚动的。否则,默认为 false 情况下,只有具有足够内容的用户才能滚动视图。
软键盘遮挡输入框
使用scrollView包裹
class _RegisterPageState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
leading: Icon(
Icons.arrow_back,
color: ColorConf.colorWhite,
),
title: Text(
'注册',
style: TextStyle(fontSize: 18, color: ColorConf.colorWhite),
),
),
body: LayoutBuilder(
builder: (BuildContext context,BoxConstraints viewPort){
return SingleChildScrollView(
child: ConstrainedBox(constraints: BoxConstraints(
minHeight: viewPort.maxHeight,
),
child: IntrinsicHeight(
child: Column(
children: [],
),
),),
);
},
),
);
}
}
圆形头像
Container(
width: 70,
height: 70,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: loginBean == null
? AssetImage('assets/icon_baba2.png')
: NetworkImage(
'https://ss2.baidu.com/-vo3dSag_xI4khGko9WTAnF6hhy/image/h%3D300/sign=00af05b334f33a87816d061af65d1018/8d5494eef01f3a29f863534d9725bc315d607c8e.jpg'),
fit: BoxFit.cover),
border: Border.all(color: ColorConf.colorWhite, width: 2),
),
),
修改TextField下划线颜色
TextField(
style: StyleComm.styleCommLeft,
textAlign: TextAlign.right,
decoration: InputDecoration(
hintText: _hintMsg,
focusedBorder: UnderlineInputBorder(
borderSide:
BorderSide(color: Colors.lightGreenAccent)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.yellowAccent)),
hintStyle: StyleComm.hintCommRight,
border: UnderlineInputBorder(
borderSide: BorderSide(
color: ColorConf.colorBlack,
),
)),
),