组件
底部导航栏
- 在
main.dart中MyApp返回的MaterialApp中引入一个即将编写的底部导航控件
import 'bottom_navigation_widget.dart';
...
return MaterialApp(
...
home: BottomNavigationWidget()
);
- 编写多个导航准备切换后的页面
/lib/pages/xxx.dart
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:Text('home')),
body: Center(
child:Text('this is the homePage')
),
);
}
}
- 编写底部导航控件
/lib/bottom_navigation_widget.dart
import 'package:flutter/material.dart';
import 'pages/home_screen.dart';
class BottomNavigationWidget extends StatefulWidget {
_BottomNavigationWidgetState createState() => _BottomNavigationWidgetState();
}
class _BottomNavigationWidgetState extends State<BottomNavigationWidget> {
final _BottomNavigationColor = Colors.blue;
int _currentIndex = 0;
List<Widget> screenList = List();
@override
void initState() {
screenList
..add(HomeScreen())
..add(StationScreen())
...;
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: screenList[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
items:[
BottomNavigationBarItem(
icon:Icon(
Icons.home,
color: _BottomNavigationColor,
),
title: Text(
'首页',
style: TextStyle(color: _BottomNavigationColor),
)
),
...
],
currentIndex: _currentIndex,
onTap: (int index){
setState(() {
_currentIndex = index;
});
},
),
);
}
}
StatefulWidget具有可变化状态(state)的窗口组件,使用这个要根据变化状态调整state值
使用StatefulWidget分为两个部分,第一个部分是继承于StatefullWidget,第二个部分是继承于State,这部分的State才是重点,主要代码都会写在State中
我们需要重写initState()方法,把已做好的切换页面进行初始化到一个Widget数组中,有了数组就可以根据数组的索引来切换不同的页面了 ..add()是Dart语言的..语法,返回调用者本身 BottomNavigationBar组件里提供了一个onTap响应事件,这个事件自带一个索引值index,通过索引值我们就可以让我们list里的索引相对应了
不规则底部工具栏
- 在新建的
myBottomNavigationWidget.dart中
import 'package:flutter/material.dart';
import 'each_view.dart';
class MyBottomNavigationWidget extends StatefulWidget {
_MyBottomNavigationWidgetState createState() => _MyBottomNavigationWidgetState();
}
class _MyBottomNavigationWidgetState extends State<MyBottomNavigationWidget> {
List<Widget> _eachView;
int _index = 0;
@override
void initState() {
_eachView = List();
_eachView..add(EachView('home'))..add(EachView('Message'));
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.adjust,
color: Colors.white
),
tooltip: 'Tips',
onPressed: (){
Navigator.of(context).push(MaterialPageRoute(builder:(BuildContext context){
return EachView('New Page');
}));
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomAppBar(
color: Colors.lightBlue,
shape: CircularNotchedRectangle(),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
IconButton(
icon: Icon(Icons.home),
color:Colors.white,
onPressed: (){
setState(() {
_index = 0;
});
},
),
IconButton(
icon: Icon(Icons.message),
color:Colors.white,
onPressed: (){
setState(() {
_index = 1;
});
},
)
],
),
),
body:_eachView[_index]
);
}
}
floatingActionButton 可交互的浮动按钮
child里一般放置Icontooltip长按显示的提示文字onPressed点击事件floatingActionButtonLocation让浮动按钮和底栏进行融合BottomAppBar底部工具栏,比BottomNavigationBar灵活很多,可以放置文字和图标,也可以放置容器color属性,底部工具栏的颜色shape设置底栏的形状,一般使用这个都是为了和floatingActionButton融合,所以使用的值一般都是CircularNotchedRectangle(),有缺口的圆形矩形child里可以放置大部分Widget
- 切换的页面,在
each_view.dart中
import 'package:flutter/material.dart';
class EachView extends StatefulWidget {
String _title;
EachView(this._title);
_EachViewState createState() => _EachViewState();
}
class _EachViewState extends State<EachView> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:Text(widget._title)),
body: Center(child:Text(widget._title))
);
}
}
毛玻璃效果
Stack(
children: <Widget>[
ConstrainedBox( // 约束盒子组件,添加额外的限制条件到child上
constraints: const BoxConstraints.expand(), // 限制条件 可扩展的
child: Image.network('xx')
),
Center(
child: ClipRect( // 裁切长方形
child: BackdropFilter( // 背景滤镜器
filter: ImageFilter.blur(sigmaX:5.0,sigmaY:5.0), // 图片模糊过滤,横向纵向都设置5.0
child: Opacity(
opacity: 0.5,
child: Container(
width: 500.0,
height: 100.0,
decoration: BoxDecoration(color: Colors.grey.shade200),
child: Center(
child:Text(
'AAAAss',
style: Theme.of(context).textTheme.display3,
),
),
),
),
),
),
)
],
),
顶部切换
SingleTickerProviderStateMixin主要是我们初始化TabController时需要用到vsync,垂直属性然后传递this
import 'package:flutter/material.dart';
import 'keep_alive.dart';
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin{
TabController _controller;
@override
void initState() {
super.initState();
_controller = TabController(length: 3,vsync:this);
}
//重写被释放的方法,只释放TabController
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Choose way'),
bottom: TabBar(
controller: _controller,
tabs: <Widget>[
Tab(
icon: Icon(Icons.directions_car),
),
Tab(
icon: Icon(Icons.directions_bus),
),
Tab(
icon: Icon(Icons.directions_bike),
)
],
),
),
body: TabBarView(
controller: _controller,
children: <Widget>[
keepAliveBtn(),
keepAliveBtn(),
keepAliveBtn()
],
),
);
}
}
保持状态
混入AutomaticKeepAliveClientMixin是保持状态的关键,然后重写wantKeepAlive的值为true
import 'package:flutter/material.dart';
class keepAliveBtn extends StatefulWidget {
_keepAliveBtnState createState() => _keepAliveBtnState();
}
class _keepAliveBtnState extends State<keepAliveBtn> with AutomaticKeepAliveClientMixin{
int _counter = 0;
@override
bool get wantKeepAlive => true;
void _incrementCounter(){
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('点击添加该行程'),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Add',
child: Icon(Icons.add),
),
);
}
}
搜索条提示
- 模拟推荐词信息数据
const searchList = [
'aaa',
'aaaabbb',
'aaaabbbbcccc'
];
const recentSuggest = [
'推荐01',
'推荐02',
'推荐03',
];
- appBar
import 'package:flutter/material.dart';
import 'asset.dart';
class SearchBar extends StatefulWidget {
_SearchBarState createState() => _SearchBarState();
}
class _SearchBarState extends State<SearchBar> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('appBar'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: (){
showSearch(context: context,delegate: searchBarDelegate());
},
)
],
),
);
}
}
class searchBarDelegate extends SearchDelegate<String>{
@override
List<Widget> buildActions(BuildContext context){
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () => query = '',
)
];
}
@override
Widget buildLeading(BuildContext context){
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () => close(context,null)
);
}
@override
Widget buildResults(BuildContext context){
return Container(
width: 100.0,
height: 100.0,
child: Card(
color: Colors.redAccent,
child: Center(
child: Text(query),
),
)
);
}
@override
Widget buildSuggestions(BuildContext context){
final suggestionList = query.isEmpty ? recentSuggest : searchList.where((input) => input.startsWith(query)).toList();
return ListView.builder(
itemCount: suggestionList.length,
itemBuilder: (context,index) => ListTile(
title: RichText(
text:TextSpan(
text: suggestionList[index].substring(0, query.length),
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold
),
children: [
TextSpan(
text: suggestionList[index].substring(query.length),
style: TextStyle(
color: Colors.grey
)
)
]
)
),
),
);
}
}
- 重写
buildActions方法,搜索条右侧的按钮执行方法 - 重写
buildLeading方法,搜索栏左侧图标的功能编写 - 重写
buildResults方法,搜索到内容后的展现 - 重写
buildSuggestions方法,设置推荐
flutter_swiper
import 'package:flutter_swiper/flutter_swiper.dart';
class SwiperDiy extends StatelessWidget {
final List swiperDataList;
SwiperDiy({Key key,this.swiperDataList}):super(key:key);
@override
Widget build(BuildContext context) {
return Container(
height: ScreenUtil().setHeight(333),
width: ScreenUtil().setWidth(750),
child: Swiper(
itemBuilder: (BuildContext context,int index){
return Image.network('${swiperDataList[index]['image']}',fit: BoxFit.fill);
},
itemCount: swiperDataList.length,
pagination: new SwiperPagination(),
autoplay: true,
),
);
}
}
flutter_screenutil
先设置一个UI稿的尺寸,它会根据这个尺寸,根据不同屏幕进行缩放,能满足大部分屏幕场景
import 'package:flutter_screenutil/flutter_screenutil.dart';
...
Widget build(BuildContext context) {
ScreenUtil.instance = ScreenUtil(width: 750, height: 1334)..init(context);
...
}
// 使用
540.w
200.h
28.sp; 配置字体大小参数2,根据系统字体大小进行缩放
url_launcher
在移动平台中启动URL的插件,可以打开网页、发送短信和邮件、拨打电话
@override
Widget build(BuildContext context) {
void _launcherUrl() async{
// String url = 'tel:' + leaderPhone;
String url = 'http://www.baidu.com';
if(await canLaunch(url)){
await launch(url);
} else {
throw 'Could not launch $url';
}
}
return Container(
child: InkWell(
onTap:_launcherUrl,
child: Image.network(leaderImage),
),
);
}
flutter_easyrefresh
上拉加载下拉刷新插件
地图
amap_location
- 在高德地图申请
APIkey获取app的SHA1值:
keytool -list -v -keystore debug.keystore
获取PackageName:Andriod/app/build.gradle里的applicationId值 2. 在Andriod/app/build.gradle里添加amap_key
defaultConfig {
….
manifestPlaceholders = [
AMAP_KEY : "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", /// 高德地图key
]
}
ios配置在main.dart里
void main() {
AMapLocationClient.setApiKey("xxxxxxxxxxxxxxxxxxxxx");
runApp(new MyApp());
}
在ios/info.Plist添加权限
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要用到定位</string>
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
- 在需要使用的页面进行定位初始化
//初始化定位监听,
void _initLocation() async {
AMapLocationClient.startup(new AMapLocationOption(
desiredAccuracy: CLLocationAccuracy.kCLLocationAccuracyHundredMeters));
//监听坐标实时变换
AMapLocationClient.onLocationUpate.listen((AMapLocation loc) {
if (!mounted) return;
setState(() {
lat = loc.latitude;
long = loc.longitude;
});
});
AMapLocationClient.startLocation();
}
若运行后lat、long打印出来是null,去设置里查看下是否打开了定位权限 5. 最后在dispose关闭定位,防止内存泄漏
@override
void dispose() {
//注意这里关闭
AMapLocationClient.shutdown();
super.dispose();
}
amap_base
- 申请
APIkey - 在
Android配置AndroidManifest.xml设置appKey
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="xxxxxxxx" />
在 IOS 上,main.dart
void main() {
AMap.init('您的Key');
runApp(new MyApp());
}
- 调用
final _amapLocation = AMapLocation();
var _result = '';
//初始化定位监听
void _initLocation() async {
_amapLocation.init();
final options = LocationClientOptions(
isOnceLocation: false,
locatingWithReGeocode: true,
);
if (await Permissions().requestPermission()) {
_amapLocation.startLocate(options).listen((_) => setState(() {
_result =
'坐标:${_.longitude},${_.latitude} @ ${DateTime.now().hour}:${DateTime.now().minute}:${DateTime.now().second}';
}));
} else {
setState(() {
_result = "无定位权限";
});
}
}
- 关闭定位
@override
void dispose() {
//注意这里关闭
_amapLocation.stopLocate();
super.dispose();
}
获取调试 sha1
// Windows终端
keytool -exportcert -list -v -alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore
// Mac/Linux终端
keytool -exportcert -list -v \
-alias androiddebugkey -keystore ~/.android/debug.keystore
截图保存到本机
import 'package:image_picker_saver/image_picker_saver.dart';
import 'dart:typed_data';
….
GlobalKey _rootWidgetKey = GlobalKey();
// 截图
void _capturePng() async {
try {
RenderRepaintBoundary boundary = _rootWidgetKey.currentContext.findRenderObject();
var image = await boundary.toImage(pixelRatio: 3.0);
ByteData byteData = await image.toByteData(format: ImageByteFormat.png);
Uint8List pngBytes = byteData.buffer.asUint8List();
ImagePickerSaver.saveFile(fileData: pngBytes).then((path) {
setState(() {
_cImg = path;
});
});
} catch (e) {
print(e);
}
}
RepaintBoundary(
key: _rootWidgetKey,
child:_card(),
),
Image.memory(
pngBytes,
fit: BoxFit.cover,
);
监听滑动并显示隐藏顶部
移除顶部padding,监听滚动,在滚动值不为0且只监听第一个Weiget时,调用方法更改透明度
Scaffold(
body: MediaQuery.removePadding(
removeTop: true,
context: context,
child: Stack(
children: <Widget>[
NotificationListener(
onNotification: (scrollNotification){
if(scrollNotification is ScrollUpdateNotification && scrollNotification.depth == 0){
_onScroll(scrollNotification.metrics.pixels);
}
},
child: ListView(),
),
Opacity(
opacity: _appBarAlpha,
child: Container(
height: 80,
decoration: BoxDecoration(
color: Colors.white,
),
),
)
],
)
),
);
