跳至主要內容

组件

Emilia Zhen大约 6 分钟flutter

底部导航栏

  1. main.dartMyApp返回的MaterialApp中引入一个即将编写的底部导航控件
import 'bottom_navigation_widget.dart';
...
    return MaterialApp(
     ...
      home: BottomNavigationWidget()
    );
  1. 编写多个导航准备切换后的页面/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')
     ),
    );
  }
}
  1. 编写底部导航控件/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里的索引相对应了

不规则底部工具栏

  1. 在新建的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里一般放置Icon
  • tooltip长按显示的提示文字
  • onPressed点击事件
  • floatingActionButtonLocation让浮动按钮和底栏进行融合
  • BottomAppBar底部工具栏,比BottomNavigationBar灵活很多,可以放置文字和图标,也可以放置容器
  • color属性,底部工具栏的颜色
  • shape设置底栏的形状,一般使用这个都是为了和floatingActionButton融合,所以使用的值一般都是CircularNotchedRectangle(),有缺口的圆形矩形
  • child里可以放置大部分Widget
  1. 切换的页面,在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),
     ),
   );
 }
}

搜索条提示

  1. 模拟推荐词信息数据
const searchList = [
  'aaa',
  'aaaabbb',
  'aaaabbbbcccc'
];
const recentSuggest = [
  '推荐01',
  '推荐02',
  '推荐03',
];
  1. 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

  1. 在高德地图申请APIkey 获取appSHA1值:
keytool -list -v -keystore debug.keystore

获取PackageNameAndriod/app/build.gradle里的applicationId值 2. 在Andriod/app/build.gradle里添加amap_key

defaultConfig {
.
 manifestPlaceholders = [
   AMAP_KEY : "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", /// 高德地图key
 ]
}
  1. 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>
  1. 在需要使用的页面进行定位初始化
//初始化定位监听,
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();
}

若运行后latlong打印出来是null,去设置里查看下是否打开了定位权限 5. 最后在dispose关闭定位,防止内存泄漏

@override
void dispose() {
//注意这里关闭
AMapLocationClient.shutdown();
super.dispose();
}

amap_base

  1. 申请APIkey
  2. 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());
}
  1. 调用
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 = "无定位权限";
});
}
}
  1. 关闭定位
@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,
      ),
     ),
    )
   ],
  )
 ),
);