flutter与web端结合开发移动端app方案研究
由于项目需求将原来的安卓端开发的界面全部用web端重构,然后再在安卓机上运行,于是采取了系统界面与功能由web端开发; app由flutter开发,app的主要功能是一个输入地址界面,输入web端地址,点击跳转,然后进入web主界面,从而实现,flutter开发安卓端app,安装到安卓机上,通过输入记录web端地址,与web界面连接,从而实现系统运行。
app开发流程
flutter安装与开发文档有相关教程:flutterchina.club/setup-windo… 以下是我在安装和开发flutter项目时遇到的一些坑,记录一下,可供参考。
1. flutter安装
注意点:
- 在中国大陆地区,要想正常获取安装包列表或下载安装包,可能需要其他网络
- 更新环境变量
- 转到 “控制面板>用户帐户>用户帐户>更改我的环境变量”
- 在“用户变量”下检查是否有名为“Path”的条目:
- 如果该条目存在, 追加 flutter\bin的全路径,使用 ; 作为分隔符.
- 如果条目不存在, 创建一个新用户变量 Path ,然后将 flutter\bin的全路径作为它的值.
- 在“用户变量”下检查是否有名为”PUB_HOSTED_URL”和”FLUTTER_STORAGE_BASE_URL”的条目,如果没有,也添加它们。
PUB_HOSTED_URL=https://pub.flutter-io.cn
FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
- 初始安装flutter,第一次运行时,需要改动很多依赖下载路径改为阿里云的路径
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
}
2. 连接安卓设备
- 在安卓机上安装
adb.apk(adb.apk由厂商提供) - 启动adb,然后连接设备
adb start-server
adb connect **.**.**.**:****
连接成功后,可以运行adb devices查看你连接的设备. 这里也可以先下载Android模拟器,不过这个需要电脑性能比较好,而且下载的过程真是慢,需要其他网络。
如果连接其他设备,需要重启adb,在进行连接
adb kill-server
adb start-server
adb connect **.**.**.**:****
3. 基于flutter开发app
- 创建fltter项目
- 这里主要编辑器使用vs code,需要安装两个插件Flutter和Dart
- 安装完成后,重启vs code,然后快捷键 Ctrl Shift P,在命令栏输入flutter,然后选择Flutter:New Project,创建新的flutter项目
- 使用外部包
- 在pubspec.yaml添加你需要的外部包,然后运行 flutter packages get下载外部包。
- 在的 lib/main.dart 文件编辑Dart代码
- 项目编辑完成后,可以执行
flutter run --verbose
或者直接按快捷键F5,运行项目,运行成功会发现安卓机上已经进入你编辑的界面了。
- 项目开发完成后,打包为apk
flutter build apk --split-per-abi
打完之后的apk在build\app\outputs\apk\release****.apk 6. 将apk push到安卓机上,点击安装
abd push C:\Users\app.apk(具体apk所在路径) /sdcard
开发web端
由于wen端开发的界面需要适配不同分辨率安卓端,这里传统的web端界面就不能支持了,需要找到相应的解决方案,实现web端与移动端不同界面的界面适配。
视区 viewport
在开发界面前,我们需要先了解一下viewport(视区),viewport 的设置不会对 PC 页面产生影响,但对于移动页面却很重要。
viewport概念
手机浏览器是把页面放在一个虚拟的"窗口"(viewport)中,通常这个虚拟的"窗口"(viewport)比屏幕宽,这样就不用把每个网页挤到很小的窗口中(这样会破坏没有针对手机浏览器优化的网页的布局),用户可以通过平移和缩放来看网页的不同部分。
设置 Viewport
width:控制 viewport 的大小,可以指定的一个值,如 600,或者特殊的值,如 device-width 为设备的宽度(单位为缩放为 100% 时的 CSS 的像素)。
height:和 width 相对应,指定高度。
initial-scale:初始缩放比例,也即是当页面第一次 load 的时候缩放比例。
maximum-scale:允许用户缩放到的最大比例。
minimum-scale:允许用户缩放到的最小比例。
user-scalable:用户是否可以手动缩放。
移动端适配布局方式探讨
我们已上述例子一步一步来探讨。
使用网格视图
很多网页都是基于网格设计的,这说明网页是按列来布局的。响应式网格视图通常是 12 列,宽度为100%,在浏览器窗口大小调整时会自动伸缩。
eg:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
<title>测试</title>
<style>
* {
box-sizing: border-box;
}
.header {
border: 1px solid red;
padding: 15px;
}
.menu {
width: 25%;
height:200px;
float: left;
padding: 15px;
border: 1px solid red;
}
.main {
width: 75%;
float: left;
height:200px;
padding: 15px;
border: 1px solid red;
}
</style>
</head>
<body>
<div class="header">
<h1>测试</h1>
</div>
<div class="menu">
<ul>
<li>测试1</li>
<li>测试2</li>
</ul>
</div>
<div class="main">
<h1>内容</h1>
<p>这是一段很长很长的话这是一段很长很长的话这是一段很长很长的话这是一段很长很长的话这是一段很长很长的话.....</p>
</div>
</body>
</html>
12 列的网格系统可以更好的控制响应式网页。首先我们可以计算每列的百分比: 100% / 12 列 = 8.33%。在每列中指定 class, class="col-" 用于定义每列有几个 span :
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
* {
box-sizing: border-box;
}
.header {
border: 1px solid red;
padding: 15px;
}
.row:after {
content: "";
clear: both;
display: block;
}
[class*="col-"] {
float: left;
padding: 15px;
border: 1px solid red;
}
.col-1 {width: 8.33%;}
.col-2 {width: 16.66%;}
.col-3 {width: 25%;height:200px;}
.col-4 {width: 33.33%;}
.col-5 {width: 41.66%;}
.col-6 {width: 50%;}
.col-7 {width: 58.33%;}
.col-8 {width: 66.66%;}
.col-9 {width: 75%;height:200px;}
.col-10 {width: 83.33%;}
.col-11 {width: 91.66%;}
.col-12 {width: 100%;}
</style>
</head>
<body>
<div class="header">
<h1>测试</h1>
</div>
<div class="row">
<div class="col-3">
<ul>
<li>测试1</li>
<li>测试2</li>
</ul>
</div>
<div class="col-9">
<h1>内容</h1>
<p>这是一段很长很长的话这是一段很长很长的话这是一段很长很长的话这是一段很长很长的话这是一段很长很长的话.....</p>
</div>
</div>
</body>
</html>
使用网格视图,百分比加float来实现界面布局,这里有一些问题,如果界面复杂,采用很多float,使用浮动(float)的一个比较疑惑的事情是他们怎么影响包含他们的父元素的。如果父元素只包含浮动元素,且父元素未设置高度和宽度的时候。那么它的高度就会塌缩为零。如果父元素不包含任何的可见背景,这个问题会很难被注意到,但是这是一个很重要的问题。在这里我们可以称为“塌陷”。虽然有对应的解决办法,但是css会越写越复杂。然后我们就想采用flex来减少float布局,这样就能避免上述问题。
flex布局
flex 是 flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性,任何一个容器都可以指定为 flex 布局。flex布局很灵活,这种布局我们也可以称之为弹性布局, 弹性布局的主要优势就是元素的宽或者高会自动补全;
注:当我们为父盒子设为 flex 布局以后,子元素的 float、clear 和 vertical-align 属性将失效。flex布局又叫伸缩布局 、弹性布局 、伸缩盒布局 、弹性盒布局采用 Flex 布局的元素,称为 Flex 容器(flexcontainer),简称"容器"。它的所有子元素自动成为 容器成员,称为 Flex 项目(flexitem),简称"项目"。
flex属性值总结
一.父元素属性
1.display:flex;(定义了一个flex容器)
2.flex-direction(决定主轴的方向)
row(默认值,水平从左到右)colunm(垂直从上到下)row-reverse(水平从右到左)column-reverse(垂直从下到上)
3.flex-wrap(定义如何换行)
nowrap(默认值,不换行)wrap(换行)wrap-reverse(换行,且颠倒行顺序,第一行在下方)
4.flex-flow(属性是 flex-direction 属性和 flex-wrap 属性的简写形式,默认值为row nowrap)
5.justify-content(设置或检索弹性盒子元素在主轴(横轴)方向上的对齐方式)
flex-start( 默认值、弹性盒子元素将向行起始位置对齐)
flex-end(弹性盒子元素将向行结束位置对齐)
center(弹性盒子元素将向行中间位置对齐。该行的子元素将相互对齐并在行中居中对齐)
space-between(弹性盒子元素会平均地分布在行里)
space-around(弹性盒子元素会平均地分布在行里,两端保留子元素与子元素之间间距大小的一半)
6.align-items(设置或检索弹性盒子元素在侧轴(纵轴)方向上的对齐方式)
flex-start(弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴起始边界)
flex-end(弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴结束边界)
center( 弹性盒子元素在该行的侧轴(纵轴)上居中放置。(如果该行的尺寸小于弹性盒子元素的尺寸,则会向两个方向溢出相同的长度))
baseline(如弹性盒子元素的行内轴与侧轴为同一条,则该值与flex-start等效。其它情况下,该值将参与基线对齐。)
stretch(如果指定侧轴大小的属性值为'auto',则其值会使项目的边距盒的尺寸尽可能接近所在行的尺寸,但同时会遵照'min/max-width/height'属性的限制)
7.align-content(设置或检索弹性盒堆叠伸缩行的对齐方式)
flex-start(各行向弹性盒容器的起始位置堆叠。弹性盒容器中第一行的侧轴起始边界紧靠住该弹性盒容器的侧轴起始边界,之后的每一行都紧靠住前面一行)
flex-end(各行向弹性盒容器的结束位置堆叠。弹性盒容器中最后一行的侧轴起结束界紧靠住该弹性盒容器的侧轴结束边界,之后的每一行都紧靠住前面一行)
center(各行向弹性盒容器的中间位置堆叠。各行两两紧靠住同时在弹性盒容器中居中对齐,保持弹性盒容器的侧轴起始内容边界和第一行之间的距离与该容器的侧轴结束内容边界与第最后一 行之间的距离相等)
space-between(各行在弹性盒容器中平均分布。第一行的侧轴起始边界紧靠住弹性盒容器的侧轴起始内容边界,最后一行的侧轴结束边界紧靠住弹性盒容器的侧轴结束内容边界,剩余的行则 按一定方式在弹性盒窗口中排列,以保持两两之间的空间相等)
space-around( 各行在弹性盒容器中平均分布,两端保留子元素与子元素之间间距大小的一半。各行会按一定方式在弹性盒容器中排列,以保持两两之间的空间相等,同时第一行前面及最后 一行后面的空间是其他空间的一半)
stretch(各行将会伸展以占用剩余的空间。剩余空间被所有行平分,以扩大它们的侧轴尺寸)
二.子元素上属性
1.order(默认情况下flex order会按照书写顺训呈现,可以通过order属性改变,数值小的在前面,还可以是负数)
2.flex-grow(设置或检索弹性盒的扩展比率,根据弹性盒子元素所设置的扩展因子作为比率来分配剩余空间)
3.flex-shrink(设置或检索弹性盒的收缩比率,根据弹性盒子元素所设置的收缩因子作为比率来收缩空间)
4.flex-basis (设置或检索弹性盒伸缩基准值,如果所有子元素的基准值之和大于剩余空间,则会根据每项设置的基准值,按比率伸缩剩余空间)
5.flex (flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选)
6.align-self (设置或检索弹性盒子元素在侧轴(纵轴)方向上的对齐方式,可以覆盖父容器align-items的设置)
eg:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
* {
box-sizing: border-box;
}
html {
font-family: "Lucida Sans", sans-serif;
}
.header {
background-color: #9933cc;
color: #ffffff;
padding: 15px;
}
.menu ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.menu li {
padding: 8px;
margin-bottom: 7px;
background-color :#33b5e5;
color: #ffffff;
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
}
.menu li:hover {
background-color: #0099cc;
}
.row {
display: flex;
justify-content: space-between;
}
.menu {
font-size:0.16rem;
border:1px solid red;
width:25%;
}
.content{
border:1px solid red;
}
</style>
</head>
<body>
<div class="header">
<h1>测试</h1>
</div>
<div class="row">
<div class="menu">
<ul>
<li>测试1</li>
<li>测试2</li>
</ul>
</div>
<div class="content">
<h1>内容</h1>
<p>这是一段很长很长的话这是一段很长很长的话这是一段很长很长的话这是一段很长很长的话这是一段很长很长的话.....</p>
</div>
</div>
</body>
</html>
采用flex布局来替换float后,css比较简洁,但发现,当设备分辨率发生变化,字体和样式都有一定的变形,这里又引入了rem,这样针对不同设备分辨率,会动态根据设置样式比例,字体大小和界面宽等用px定义的样式都能完美兼容。
REM
rem是CSS3新增的一个相对单位(root em,根em),这个单位引起了广泛关注。这个单位与em有什么区别呢?区别在于使用rem为元素设定字体大小时,仍然是相对大小,但相对的只是HTML根元素。这个单位可谓集相对大小和绝对大小的优点于一身,通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。目前,除了IE8及更早版本外,所有浏览器均已支持rem。对于不支持它的浏览器,应对方法也很简单,就是多写一个绝对单位的声明。这些浏览器会忽略用rem设定的字体大小。
eg:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
* {
box-sizing: border-box;
}
html {
font-family: "Lucida Sans", sans-serif;
font-size:100px;
}
.header {
background-color: #9933cc;
color: #ffffff;
padding: 0.15rem;
font-size:0.24rem;
}
.menu ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.menu li {
padding: 0.08rem;
margin-bottom: 0.07rem;
background-color :#33b5e5;
color: #ffffff;
box-shadow: 0 0.01rem 0.03rem rgba(0,0,0,0.12), 0 0.01rem 0.02rem rgba(0,0,0,0.24);
}
.menu li:hover {
background-color: #0099cc;
}
.row {
font-size:0.16rem;
display: flex;
justify-content: space-between;
}
.menu {
font-size:0.16rem;
width:25%;
}
</style>
</head>
<body>
<div class="header">
<h1>测试</h1>
</div>
<div class="row">
<div class="menu">
<ul>
<li>测试1</li>
<li>测试2</li>
</ul>
</div>
<div class="content">
<h1>内容</h1>
<p>这是一段很长很长的话这是一段很长很长的话这是一段很长很长的话这是一段很长很长的话这是一段很长很长的话.....</p>
</div>
</div>
</body>
</html>
由于wen端开发的界面需要适配不同分辨率安卓端,不过主要还是针对这次设备的分辨率来开发,这时就需要计算视觉稿所在分辨率与安卓端所在分辨率的比例。
- eg: 视觉稿设计的分辨率是1920 * 1080,安卓机分辨率是960 *540, 那么对应比例为 n= 960 * 540/1920 * 1080. 然后对应视觉稿的宽高等,就需要乘以n,例如视觉稿设计的宽为1080px,那实际开发时,设计的宽度就为1080 * npx, 这样设计的界面才能很好的在安卓机上适配。
为了适配不能分辨率的安卓端,我们采用rem作为css单位,动态计算设备的font-size,来自动适配不同安卓端
getRem () {
/* 获取标签元素<html> */
let html = document.getElementsByTagName('html')[0]
/* 获取设备的宽度 ||后为兼容IE低版本写法 */
let oWidth = document.body.clientWidth || document.documentElement.clientWidth
/* 设置根元素<html>字体大小 计算出的值 就相当于1rem;为什么? 这就是rem单位的规定 1rem就等于根元素<html>字体大小*/
html.style.fontSize = oWidth / 9.6 + 'px'
}
这里实际font-size比例设置为font-size = 100px,然后界面上设置的px转换为rem时,都需要除以100,例如需要设计width=100px,转换为rem为1rem,上述代码实现的是每次进去获取设备的宽度,然后设置font-size的值,这里9.6其实是设备宽度除以100得到的。
注:因为浏览器可识别的font-size大小是不一样的,在pc端chrome上正常能显示的最小的文字大小是12px,所以我们设置font-size大小时,最好大一些,例如100px。
这里完成了web布局,但采用flutte编写app,web开发界面和功能,需要实现web端与app端的通信。
移动端适配方案对比与总结
前面完整介绍了这次项目中实现的移动端方案,我们把其他的一起对比下:
1、百分比适配
适合页面内容结构均匀分配,固定高度,结构不是很复杂,注意要设置viewport视口内容宽度等于设备的宽度。 对于结构复杂的界面,采用百分比难以实现,而且图片容易发生变形。
2、媒体查询media
meida queries 的方式可以说是我早期采用的布局方式,它主要是通过查询设备的宽度来执行不同的 css 代码,最终达到界面的配置。核心语法是:
@media screen and (max-width: 600px) { /*当屏幕尺寸小于600px时,应用下面的CSS样式*/
/*你的css代码*/
}
media query可以做到设备像素比的判断,方法简单,成本低,特别是对移动和PC维护同一套代码的时候。目前像Bootstrap等框架使用这种方式布局,图片便于修改,只需修改css文件
缺点是:代码量比较大,维护不方便,为了兼顾大屏幕或高清设备,会造成其他设备资源浪费,特别是加载图片资源,为了兼顾移动端和PC端各自响应式的展示效果,难免会损失各自特有的交互方式
3、Flex布局适配
同样是适合页面内容结构均匀分配,固定高度,注意要设置viewport视口内容宽度等于设备的宽度 老版本的display:box与新版本的display:flex都可以实现页面的自适应 优点:在于其容易上手,根据flex规则很容易达到某个布局效果,缺点:浏览器兼容性比较差,只能兼容到ie9及以上;当然,我们现在基本是兼容ie11以上了,所以缺点忽略,不过采用flex布局能完美结局移动端布局问题,但具体到具体图片大小,文字大小,采用传统的px就不能很好的展现了。
4、rem 实现
根据不同屏幕设备分辨率动态设置html的文字大小,达到等比缩放的功能 在不同的移动终端显示不同的元素等比例缩放效果 把设计图的宽度分成多少分之一,根据实际情况,不是你想分成多少就多少,一般10等分,16,20等分的 保证html最终算出来的字体大小,不小于12px(Google浏览器下,最小字体大小为12px,设置像素值小于它,仍是12px,但是在firefox浏览器下小于12px,仍然是可以减小生效的,太小了字体看不清,影响用户体验)
总结:其实移动端适配方法各有优缺点,我们也不需要局限只使用一种方法。所以,本项目中,采用了百分比布局或flex布局(具体看界面实现),css单位统一采用rem实现,根据设备宽度动态计算font-size,实现不同分辨率下界面适配。
web端与app端的通信
因为有时需要app与web端进行通讯,例如需要在web端增加返回按钮,返回app端输入网址界面,这里主要用到flutter外部包flutter_inappwebview的addJavaScriptHandler监听功能 eg: app端
import 'dart:async';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
class MyInAppBrowser extends InAppBrowser {
@override
void onBrowserCreated() {
this.webViewController.addJavaScriptHandler(
handlerName: "getHandleReturn",
callback: (args) {
this.close();
});
super.onBrowserCreated();
}
}
web端:
/**
* @desc 返回事件
*/
handleReurn () {
window.flutter_inappwebview.callHandler('getHandleReturn', true)
},
这样,在web执行返回函数时,app会监听返回,然后执行相应操作。同样app也可以通过这种方式向web端传递数据。 eg: app端:
this.webViewController.addJavaScriptHandler(
handlerName: "getHikDeviceType",
callback: (args) {
return Config.hikDeviceType;
});
wed端:
let result = await window.flutter_inappwebview.callHandler('getHikDeviceType')
到此,由web搭建主要界面,flutter设计app,完成在安卓机上将开发的系统的应用。
遇到的问题
1、安装flutter失败,初始安装有很多外部包的路径是没法下载的。
初始安装flutter,第一次运行时,需要改动很多外部包下载路径改为阿里云的路径
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
}
2、web端点击返回app端,在其中一个安卓机上正常,到另一个安卓机上就不行了,后面检查是不正常的安卓机上webview版本不对。解决办法:替换安卓机上的webview
替换webview操作
-
安装
adb.apk,然后就可以在所有app页面找到一键打开ADB,打开后等待关闭(记录IP地址,IP地址也可以在设置网络中查看)。调试模式启动。 -
解压
AndroidTool_Release_v2.58,进入AndroidTool_Release_v2.58\bin目录。 -
在当前目录打开cmd(点击地址栏输入cmd按回车)。
-
输入
adb.exe connect xxx.xxx.xxx.xxx,xxx为机器的IP地址。如果显示
connected to xxxxxxx:xxx或者already connected to xxxxxx:xxx,说明连接成功。如果不成功,要么IP错了,要么看看你的电脑是否和机器属于同一个局域网。 -
连接上后,输入如下命令
# 进入机器shell adb.exe shell # 切换到root su # 重新挂在system分区 mount -o rw,remount /system # 删除老得webview相关文件 rm /system/app/webview/* # 重启 reboot -
重启后安装
com.android.webview_83.0.4103.101-1591592960_minAPI21(armeabi-v7a)(nodpi)_apkmirror.com.apk,再重启一下。将本地webview.apk push到安卓机上命令
abd push C:\Users\learn\com.android.webview.apk(具体apk所在路径) /sdcard
- 替换完成。
参考资料: