android布局实战,Android Flutter 构建布局UI实战(二)

这几天一直在学习, 今天有时间整理一下学习的内容用于记录与分享,详细的控件使用描述有兴趣的可以去官网上看,我这边自己写了一个很简单的小demo,包含了一些基础的知识。

a5cfd13ebbf6

surprised.png

记录的知识点:

· 1 底部菜单导航

· 2 页面的跳转

· 3 ListView

· 4 吐司(这个是内部实现引用的,具体flutter自带的框架,暂时不清楚)

· 5 涉及到一些布局的书写(属性)

· 6 res资源的引用

· 7 涉及到的一些widget使用介绍,或在注解或在代码片段后。

项目效果图:

a5cfd13ebbf6

home.png

一、页面的跳转

我自己写了一个页面就放了一个RaisedButton,跳转到首页。

a5cfd13ebbf6

transition.png

RaisedButton就是一个button,实现onPressed监听btn事件。

Navigator这个是用来进行跳转页面的。

涉及到的一些需要介绍的控件我用都**来表示注解了, //看不清楚

import 'package:flutter/material.dart'; **基本多是这个包

import 'package:flutter_app/MainActivity.dart'; **我跳转的首页面

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget{

@override

Widget build(BuildContext context) {

return new MaterialApp(**MaterialApp个人理解为程序的渲染入口

title: 'Hello World',

theme: new ThemeData( **全局主题只是由应用程序根MaterialApp创建的Theme来表示

primaryColor: Colors.lightBlue,

),

home: new RandomWords(),**调用方法体

);

}

}

class RandomWordsState extends State{

**Scaffold 是 Material library 中提供的一个widget,

它提供了默认的导航栏、标题和包含主屏幕widget树的body属性。widget树可以很复杂。

** Center这个空间居中

** RaisedButton = button

** Navigator页面的跳转,差不多都是这个写法,固定

@override

Widget build(BuildContext context) {

return new Scaffold(

body: new Center(

child: new RaisedButton(

child: new Text('登录'),

onPressed: (){

Navigator.push(context, new MaterialPageRoute(builder: (context)=>new MainActivity()));

}),

),

);

}

}

class RandomWords extends StatefulWidget {

@override

** =>单行函数或方法的简写

createState() => new RandomWordsState();

}

其中StatelessWidget它是表示所有的属性都是最终的,可以理解为属性不可变。

StatefulWidget 我觉得可以理解为android中某一个自定义的方法(代码书写ui),方法体的内容是可变的,当然它也是一个widget。在flutter中书写一个这样的方法,就需要按照上述代码中的方式来书写。

二、MainActivity页面

images文件的引用。

页面中包含了一些图片资源,记录一下images的引用方式。

a5cfd13ebbf6

项目结构图.png

一开始Flutter是没有images文件夹的,自己创建一个,跟android ios保持同级。

在pubspec.yaml文件中进行images的关联,pubspec.yaml这个可以理解为build.gradle。

a5cfd13ebbf6

引用界面.png

在Flutter的节点下新增(引用全目录 ,若单张全名称包含后缀):

assets:

- images/

添加完毕后在右上角有同步按钮,别忘了

a5cfd13ebbf6

image.png

package get 加载引入的包

package upgradle 升级包

flutter upgradle 整理升级 包括Dart SDK version等

flutter doctor 检测需要安装的东西

包的引用

当初在看官方的时候,引用了一个english_words的包,项目中没有用,但是 这边记录一下引用包的方式。

a5cfd13ebbf6

image.png

在 lib/main.dart 中 import 'package:english_words/english_words.dart';就可以了,需要注意的是,在pubspec.yaml中添加了之后记得package get,在弹出的message窗口中Process finished with exit code 0 表示引用成功。

底部导航 BottomNavigationBarItem

在看代码之前:

此处简要一下代码的书写逻辑。

因为页面是可变可调整的,所以我肯定需要书写StatefulWidget。

接着 初始了切换的图片,文字等资源

在BottomNavigationBarItem中主要是通过 下标 切换图片和文字的显示,当然也包含切换页面,切换页面的书写方式类似android中的fragment,属于独立页面,配合使用IndexedStack进行切换页面的显示与隐藏。

具体的代码含义,我在注释里进行介绍。

import 'package:flutter/cupertino.dart'; **底部导航切换,需导入

import 'package:flutter/material.dart'; **上面注解介绍过

import 'package:flutter_app/page/homeinfo.dart';**fragment页面

import 'package:flutter_app/page/myinfo.dart';**fragment页面

void main() => runApp(new MainActivity());

class MainActivity extends StatelessWidget {

**这块没啥介绍的, 同上

@override

Widget build(BuildContext context) {

return new MaterialApp(

title: 'Hello World',

theme: new ThemeData(

primaryColor: Colors.blue,

),

home: new RandomWords(),

);

}

}

class RandomWordsState extends State {

int _tabIndex = 0; ** 默认当前页

// static const double IMAGE_ICON_WIDTH = 30.0; 标题上的返回按钮

// static const double ARROW_ICON_HEIGHT = 16.0; 标题上的返回按钮

final normalTextColor = new TextStyle(color: const Color(0xff969696)); **默认的颜色

final selectTextColor = new TextStyle(color: const Color(0xff63ca6c)); **选择的颜色

var tabImage; **切换的image

var _body; **IndexedStack的对象

var tabNameList = ['首页', '地图', '我的']; **底部导航名称

var titleNameList = ['动服务平台', '地图', '我的']; **标题名称

// var leftIcon;标题上的返回按钮

// RandomWordsState(){

// leftIcon = setImages("images/icon_left.png");

// }

**统一设置image属性 ,path为images的引用路径

Image getImagePath(path) {

return new Image.asset(

path,

width: 20.0,

height: 20.0,

);

}

**切换图片的初始化,包括切换页面的body初始 , getImagePath为统一设置的images属性。

void initData() {

if (tabImage == null) {

tabImage = [

[

getImagePath('images/activity_home_unchecked.png'),

getImagePath('images/activity_home_checked.png')

],

[

getImagePath('images/activity_map_unchecked.png'),

getImagePath('images/activity_map_checked.png')

],

[

getImagePath('images/activity_mine_unchecked.png'),

getImagePath('images/activity_mine_checked.png')

],

];

}

** children这个我个人理解它是一个组合控件,像是一个容器,可以包含很多不同的Ui,然后拼凑到一起。

_body = new IndexedStack(

children: [new HomeInfo(), new MyInfo(), new MyInfo()],

index: _tabIndex,

);

}

** 根据下标返回 text的颜色值

TextStyle getTabTextStyle(int curIndex) {

if (curIndex == _tabIndex) {

return selectTextColor;

}

return normalTextColor;

}

** 调用getTabTextStyle 根据下标设置text的颜色值

Text getTabTitle(int curIndex) {

return new Text(tabNameList[curIndex], style: getTabTextStyle(curIndex));

}

** 返回当前下标的images中的 所选图片

Image getTabIcon(int curIndex) {

if (curIndex == _tabIndex) {

return tabImage[curIndex][1];

}

return tabImage[curIndex][0];

}

//设置iamge的位置

// Widget setImages(path) {

// return new Padding(

// padding: const EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),

// child: new Image.asset(path,

// width: IMAGE_ICON_WIDTH, height: ARROW_ICON_HEIGHT));

// }

** AppBar标题{title标题文字 {Center标题位置 child{标题内容} } }

** body切换的index页面

** bottomNavigationBar底部导航{items导航数组{0,1,2}}

**onTap点击(Index作为点击返回值) {setState通知框架状态已经改变{_tabIndex 赋值当前Index}}

@override

Widget build(BuildContext context) {

initData();

return new MaterialApp(

home: new Scaffold(

appBar: new AppBar(

title: new Center(

child: new Text(titleNameList[_tabIndex],

style: new TextStyle(color: Colors.white)),

// child: new Row(

// children: [

// leftIcon,

// new Text(tabNameList[_tabIndex],

// style: new TextStyle(color: Colors.white))

// ],

// ),

),

iconTheme: new IconThemeData(color: Colors.white)),

body: _body,

bottomNavigationBar: new CupertinoTabBar(

items: [

new BottomNavigationBarItem(

icon: getTabIcon(0),

title: getTabTitle(0)),

new BottomNavigationBarItem(

icon: getTabIcon(1),

title: getTabTitle(1)),

new BottomNavigationBarItem(

icon: getTabIcon(2),

title: getTabTitle(2)),

],

currentIndex: _tabIndex,

onTap: (index) {

setState(() {

_tabIndex = index;

});

},

),

),

);

}

}

class RandomWords extends StatefulWidget {

@override

createState() => new RandomWordsState();

}

底部切换基本就这些,你要是只是想测试一下底部切换效果也可以像我indexedStack那样一样,除了首页,剩下的复用。

HomeInfo.dart页面

a5cfd13ebbf6

HomeInfo.png

考虑到页面的布局,我分成了2个层级,网格显示是一个层级,报表是另外的一个层级,可以说是ListView 的两个item,只不过是不同的item布局。当然这只是我个人的想法,经过思考后在StatefulWidget,我返回的就是一个ListView .

var title = ["项目信息", "农村公路建设统计报表", "路网结构改造统计报表"];

@override

Widget build(BuildContext context) {

var listview = new ListView.builder(

itemCount: title.length, itemBuilder: (context, i) => renderRow(i));

return listview;

}

ListView 初始化:

itemcount 网格是一个单独的布局,另外的两个可以复用布局。

renderRow是我自定义的方法

i 算是0 、1、 2,其中1、2布局复用。

renderRow(int i) {

if (i == 0) { ** 此处i=0初始化网格的样式。

var projectInfo = new Container(

// color: const Color.fromRGBO(255, 255, 255, 255.0),

decoration: new BoxDecoration(

color: Colors.white,

),

child: new Center(

child: new Column(

children: [

new Text(

title[0],

textAlign: TextAlign.left,

),

new Container(

color: const Color.fromRGBO(240, 248, 255, 200.0),

child: new Row(

children: [

new Expanded(

flex: 1,

child: new Container(

margin: const EdgeInsets.only(

top: 10.0, right: 5.0, left: 10.0, bottom: 10.0),

decoration: new BoxDecoration(

border: new Border.all(

width: 1.0, color: Colors.black12),

borderRadius: const BorderRadius.all(

const Radius.circular(10.0))),

height: 100.0,

child: new Center(

child: new Column(

mainAxisAlignment: MainAxisAlignment.center,

children: [

new IconButton(

icon: new Image.asset(

"images/icon_way.png",

width: 50.0,

height: 50.0,

),

onPressed: () {

showShort("农村公路建设类项目");

}),

new Center(child: new Text("农村公路建设类项目"))

],

),

),

)),

new Expanded(

flex: 1,

child: new Container(

margin: const EdgeInsets.only(

top: 10.0, right: 10.0, left: 5.0, bottom: 10.0),

decoration: new BoxDecoration(

border: new Border.all(

width: 1.0, color: Colors.black12),

borderRadius: const BorderRadius.all(

const Radius.circular(10.0))),

height: 100.0,

child: new Center(

child: new Column(

mainAxisAlignment: MainAxisAlignment.center,

children: [

new IconButton(

icon: new Image.asset(

"images/icon_reform.png",

width: 50.0,

height: 50.0,

),

onPressed: () {

showShort("农村公路建设类项目");

}),

new Center(child: new Text("危桥改造类项目"))

],

)),

))

],

),

),

new Container(

child: new Row(

children: [

new Expanded(

flex: 1,

child: new Container(

margin: const EdgeInsets.only(

top: 0.0, right: 5.0, left: 10.0, bottom: 10.0),

decoration: new BoxDecoration(

border: new Border.all(

width: 1.0, color: Colors.black12),

borderRadius: const BorderRadius.all(

const Radius.circular(10.0))),

height: 100.0,

child: new Center(

child: new Column(

mainAxisAlignment: MainAxisAlignment.center,

children: [

new IconButton(

icon: new Image.asset(

"images/icon_security.png",

width: 50.0,

height: 50.0,

),

onPressed: () {

showShort("县乡安防工程类项目");

}),

new Center(child: new Text("县乡安防工程类项目"))

],

),

),

)),

new Expanded(

flex: 1,

child: new Container(

margin: const EdgeInsets.only(

top: 0.0, right: 10.0, left: 5.0, bottom: 10.0),

decoration: new BoxDecoration(

border: new Border.all(

width: 1.0, color: Colors.black12),

borderRadius: const BorderRadius.all(

const Radius.circular(10.0))),

height: 100.0,

child: new Center(

child: new Column(

mainAxisAlignment: MainAxisAlignment.center,

children: [

new IconButton(

icon: new Image.asset(

"images/icon_security_green.png",

width: 50.0,

height: 50.0,

),

onPressed: () {

showShort("村道安防工程类项目");

}),

new Center(child: new Text("村道安防工程类项目"))

],

)),

))

],

),

),

],

),

),

);

return new GestureDetector(

child: projectInfo,

);

}

new Container(

child: new Text(title[i]),

);

var listCountItem = new Padding(

padding: const EdgeInsets.fromLTRB(15.0, 10.0, 0.0, 10.0),

child: new Column(

children: [

new Container(

child: new Column(

crossAxisAlignment: CrossAxisAlignment.start,

children: [

new Text(title[i]),

new Container(

height: 200.0,

child: new ListView(

children: [

new ListTile(

leading: new Icon(Icons.map),

title: new Text('Maps'),

),

new ListTile(

leading: new Icon(Icons.photo_album),

title: new Text('Album'),

),

new ListTile(

leading: new Icon(Icons.phone),

title: new Text('Phone'),

),

],

),

)

],

),

),

// new ListView(

// children: [

// new ListTile(

// title: new Text('123123'),

// )

// ],

// )

],

),

);

return new InkWell(

child: listCountItem,

onTap: () {

showShort("1111");

},

);

}

部分控件属性介绍:

Container也是一个widget,允许自定义其子widget。

个人理解就是一个容器,可以添加一些别的widget组合成想要的ui样式 。可添加填充,边距,边框或背景色。

BoxDecoration这个类似shape可以操作内填充颜色,圆角等

Expanded 这个可以理解为权重,flex表示当前包裹控件所在父布局的权重比例。

children: []这个属性相当于多个Item一样,数组中的每一个值都可以看做成一个Item,具体什么样的ui样式看你自己怎么写。

mainAxisAlignment和crossAxisAlignment属性用来对齐其子项。 对于行(Row)来说,主轴是水平方向,横轴垂直方向。对于列(Column)来说,主轴垂直方向,横轴水平方向。具体的详细参数,对照官网。

GestureDetector这个是用来检测用户做出的手势,点击的时候会回调onTap,我这边没有写onTap,我是单独在iconButton中做的点击处理。写的有点问题,不过顺带的介绍一下这个。

InkWell实现水波纹,边框效果,跟BoxDecorationc差不多。

关于吐司showShort,分享实现方式(改的原生):

Android

a5cfd13ebbf6

Android.png

new MethodChannel(getFlutterView(), "com.coofee.flutterdemoapp/sdk/toast")

.setMethodCallHandler(new MethodChannel.MethodCallHandler() {

@Override

public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {

if ("show".equals(methodCall.method)) {

String text = methodCall.argument("text");

int duration = methodCall.argument("duration");

Toast.makeText(MainActivity.this, text, duration).show();

}

}

});

IOS

a5cfd13ebbf6

image.png

#include "AppDelegate.h"

#include "GeneratedPluginRegistrant.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

[GeneratedPluginRegistrant registerWithRegistry:self];

// Override point for customization after application launch.

FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;

FlutterMethodChannel* toastChannel = [FlutterMethodChannel

methodChannelWithName:@"com.coofee.flutterdemoapp/sdk/toast"

binaryMessenger:controller];

[toastChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {

if ([@"show" isEqualToString:call.method]) {

// 展示toast;

NSLog(@"显示toast....")

}

}];

return [super application:application didFinishLaunchingWithOptions:launchOptions];

}

@end

Flutter

import 'package:flutter/services.dart';

// 下划线开头的变量只在当前package中可见。

const _toast = const MethodChannel('com.coofee.flutterdemoapp/sdk/toast');

const int _LENGTH_SHORT = 0;

const int _LENGTH_LONG = 1;

void show(String text, int duration) async {

try {

await _toast.invokeMethod("show", {'text': text, 'duration': duration});

} on Exception catch (e) {

print(e);

} on Error catch (e) {

print(e);

}

}

void showShort(String text) {

show(text, _LENGTH_SHORT);

}

void showLong(String text) {

show(text, _LENGTH_LONG);

}

MyInfo.dart页面

a5cfd13ebbf6

MyInfo.png

MyInfo相对上一个页面要简单不少,主要是ListView,剩下的就是一些资源的初始化。

import 'package:flutter/cupertino.dart';

import 'package:flutter/material.dart';

import 'package:flutter_app/utils/toastdart.dart';

class MyInfo extends StatefulWidget {

@override

createState() => new MyInfoState();

}

class MyInfoState extends State {

static const double IMAGE_ICON_WIDTH = 30.0;

static const double ARROW_ICON_WIDTH = 16.0;

var inons = [];

var titleTextStyle = new TextStyle(fontSize: 16.0);

var title = ["用户指南", "地图设置", "路网数据", "数据备份", "项目数据更新", "关于系统", "退出登录"];

var images = [

"images/one.png",

"images/two.png",

"images/three.png",

"images/four.png",

"images/five.png",

"images/six.png",

"images/senven.png",

];

var rightIcon = new Image.asset(

"images/icon_right.png",

width: IMAGE_ICON_WIDTH,

height: ARROW_ICON_WIDTH,

);

MyInfoState() {

for (int i = 0; i < images.length; i++) {

inons.add(setImages(images[i]));

}

}

//设置iamge的位置

Widget setImages(path) {

return new Padding(

padding: const EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),

child: new Image.asset(path,

width: IMAGE_ICON_WIDTH, height: ARROW_ICON_WIDTH));

}

@override

Widget build(BuildContext context) {

var listview = new ListView.builder(

itemCount: title.length , itemBuilder: (context, i) => renderRow(i));

return listview;

}

renderRow(int i) {

String itemName = title[i];

var itemCount = new Padding(

padding: const EdgeInsets.fromLTRB(25.0, 25.0, 25.0, 25.0),

child: new Row(

children: [

inons[i],

new Expanded(

child: new Text(

itemName,

style: titleTextStyle,

)),

rightIcon

],

),

);

return new InkWell(

child: itemCount,

onTap: (){

//toast

showShort(itemName);

// Navigator.of(context).push(new MaterialPageRoute(

// builder: (context)=> new MainActivity()));

},

);

}

}

EdgeInsets类似Android里面的margin。

总结:

万物皆Widget。

若看的不太舒服,望见谅···

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值