IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> flutter实战总结(老手不必看),看这里 -> 正文阅读

[移动开发]flutter实战总结(老手不必看),看这里

如果你在idea中使用鼠标拖拽移动了文件路径,那么恭喜你这个文件的热加载失效了。
原因是文件路径变成了绝对路径,类似C:\Users…,这种路径热加载识别不了,需改为package:…

点击空白无效

(1)GestureDetector设置参数 behaviorHitTestBehavior.opaque,
(2)使用InkWell组件

键盘溢出

默认情况下,键盘弹起,flutter会将页面上推,可能会导致溢出报错,解决办法有两个:

  1. 修改默认值
Scaffold(
  appBar: AppBar(
    title: new Text("首页"),
  ),
  resizeToAvoidBottomPadding: false, //默认值为true,表示页面上推, 设置false表示不上推,此时键盘可能会盖住页面,类似stack层叠效果
); 
  1. 使用滚动组件

使用SingleChildScrollView或者listview组件作为根元素,此时就不要设置resizeToAvoidBottomPadding为false了,要不然就没有页面上推了

showModalBottomSheet 底部弹出 问题

  • 顶部圆角
shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))), 
  • 高度限制

默认高度为半屏,设置isScrollControlled为true是全屏,不想全屏,使用BoxConstraints组件限制最大高度

  • 键盘溢出

首先使用SingleChildScrollView作为根组件让其可以滚动,然后获取键盘高度MediaQuery.of(context).viewInsets.bottom作为paddingbottom,因为这不在scaffold工作范围内,flutter不会为我们上推界面。

  • 状态更新

showModalBottomSheet等其他dialog组件,相当于跳转的一个新的路由页面,在这个页面setState(() {}); 更新上个页面的状态没有用。
解决办法有许多,其中一个是使用Builder组件包裹要更新的组件,在更新时调用 (context as Element).markNeedsBuild();

TextField 内容垂直不居中

 contentPadding: EdgeInsets.all(0.0), 

去除水波纹

默认情况下,可滚动组件滑到顶部和尾部会有水波纹效果,如图所示,那么怎么去掉呢?
全局去掉如下:

MaterialApp(

          builder: (context, child) {
            child= ScrollConfiguration(
              child:  child,
              behavior: RefreshScrollBehavior(),
            );
           
            return child;
          },

            ) 

class RefreshScrollBehavior extends ScrollBehavior {
@override
Widget buildViewportChrome(
BuildContext context, Widget child, AxisDirection axisDirection) {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
return child;
case TargetPlatform.macOS:
case TargetPlatform.android:
return GlowingOverscrollIndicator(
child: child,
showLeading: false, //顶部水波纹是否展示
showTrailing: false, //底部水波纹是否展示
axisDirection: axisDirection,
notificationPredicate: (notification) {
if (notification.depth == 0) {
// 越界是否展示水波纹
if (notification.metrics.outOfRange) {
return false;
}
return true;
}
return false;
},
color: Theme.of(context).primaryColor,
);
case TargetPlatform.fuchsia:
}
return null;
}
}


![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6af38d0efc6d43e5a01a5a1ecae5e1d2~tplv-k3u1fbpfcp-watermark.image)

渐变appbar
--------

通过设置**AppBar**的**flexibleSpace**属性

flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.cyan, Colors.blue, Colors.blueAccent],
),
),
),


![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/12199bfef2e242d494d882a44a0c76b6~tplv-k3u1fbpfcp-watermark.image)

动态渐变appbar
----------

使用**NotificationListener**监听页面滚动,动态改变appbar透明值。

body: NotificationListener(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollUpdateNotification) {
if (scrollNotification.metrics.axis == Axis.vertical) _onScroll(scrollNotification.metrics.pixels);
}
return false;
},

 _onScroll(offset) {
    //print(offset);
    if (offset > 200) return;
    double alpha = offset / 200;
    if (alpha < 0) {
      alpha = 0;
    } else if (alpha > 1) {
      alpha = 1;
    }
    setState(() {
      appBarAlpha = alpha;
    });
  } 
```

![](https://img-blog.csdnimg.cn/img_convert/5b01d5f5dcb967d56fb77192280ca6c3.gif)

自适应宽高
-----

使用**FittedBox**组件可自动调节内容,超出宽高会自动调节字体大小

自定义底部导航
-------

如图所示,这种导航条官方没有提供,只能靠我们自定义了。 通过自定义**Scaffold**的**bottomNavigationBar**属性来实现,其中bottomAppBarItem是一个自定义方法,生成一个个导航按钮,红点使用**stack**相对定位,中间是一个播放进度按钮,类似喜马拉雅,思路是**CircularProgressIndicator**组件作为进度条,**Container**组件形状指定为圆 **shape: BoxShape.circle**,子组件是图片,然后相对定位于**CircularProgressIndicator**

```
 bottomNavigationBar: BottomAppBar(
              child: Consumer<IMNoticeProvider>(
               builder: (context,_imNotice,child){
                 return Row(
                   children: [
                     bottomAppBarItem(0, Icons.home, '首页', badge: badge1),
                     bottomAppBarItem(1, Icons.email, '消息', badge: _imNotice.unreadMsgCount),
                     bottomAppBarItem(-1, Icons.store, '商店', badge: badge1),
                     bottomAppBarItem(2, Icons.store, '商店', badge: 101),
                     bottomAppBarItem(3, Icons.person, '我的', badge: 1, type: 'q'),
                   ],
                   mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部导航栏横向空间
                   mainAxisSize: MainAxisSize.max,
                 );
               },
              )
            ) 
```

![](https://img-blog.csdnimg.cn/img_convert/ca1f46c3c97ec067edfca9e7fd472b19.png) ![](https://img-blog.csdnimg.cn/img_convert/947540544f31b513659d6d5b6955b991.png)

popupMenu 弹出菜单 滑动关闭
-------------------

官方的弹出菜单,需要点击空白才能关闭,如何才能滑动屏幕就能关闭呢?参照微信长按聊天会话。  
官方没有提供,只能我们自定义了。  
复制showMenu函数源码到项目文件夹下,并更名为customShowMenu,防止与官方冲突,用法不变。  
大约在770行,添加**GestureDetector**组件,我们自己处理滑动事件。

```
 return MediaQuery.removePadding(
        context: context,
        removeTop: true,
        removeBottom: true,
        removeLeft: true,
        removeRight: true,
        child: GestureDetector(
          behavior: HitTestBehavior.translucent,
          onPanStart: (DragStartDetails details) {
            Navigator.of(context).maybePop();
          },
          child: Builder(
            builder: (BuildContext context) {
              return CustomSingleChildLayout(
                  delegate: _PopupMenuRouteLayout( 
```

![](https://img-blog.csdnimg.cn/img_convert/c2948645c5feccf9f46a5b850d944f66.gif)

tabbar 保存位置
-----------

默认情况下,tabbar切换,上一个页面滚动的位置会销毁, 解决办法:使用key保存位置

```
var _tab1 = PageStorageKey('_tab1'); 
```

自定义搜索
-----

如图所示 ,官方自带搜索组件**showSearch**,需要实现一个**SearchDelegate**,为了实现底部tabbar,我们需要修改源码。  
复制**SearchDelegate** 源码到我们项目文件夹下,并更名为myShowSearchGoods和MySearchDelegateGoods,名字随意防止与官方冲突,这个一个抽象类,后面我们实现它。

```
GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onTap: () => myShowSearchGoods(context: context, delegate: GoodsSearchBarDelegate()),
                  child: Container( 

class GoodsSearchBarDelegate extends MySearchDelegateGoods {
List recentSuggest = List.from(MySheetSearch.getData().reversed.toList());
int id = 0;

//List tabTitle = [‘单曲’, ‘专辑’, ‘歌手’, ‘歌单’];
List songList = [];
List albumList = [];
List artistList = [];
List sheetList = [];
int page1,page2,page3,page4=0;
List tabTitle = [
{“name”: “单曲”, “type”: 1},
{“name”: “专辑”, “type”: 10},
{“name”: “歌手”, “type”: 100},
{“name”: “歌单”, “type”: 1000},
];
String oldQuery;
RefreshController _controllerR1 =RefreshController(initialRefresh: false);
RefreshController _controllerR2 =RefreshController(initialRefresh: false);
RefreshController _controllerR3 =RefreshController(initialRefresh: false);
RefreshController _controllerR4 =RefreshController(initialRefresh: false);
GoodsSearchBarDelegate();

@override
String get searchFieldLabel => ‘搜点什么’;

@override
loadData(BuildContext context) async { //加载数据
if(query.isEmpty){
Utils.showToast(‘请输入搜索内容’);
return false;
}
if (oldQuery != query) {
oldQuery = query;
songList = [];
albumList = [];
artistList = [];
sheetList = [];
page1=0;
page2=0;
page3=0;
page4=0;
}
else
showResults(context);
if (tabController.index == 0 && (songListnull || songList.isNotEmpty))
return false;
else if (tabController.index == 1 && (albumList
null || albumList.isNotEmpty)) return false;
else if (tabController.index == 2 && (artistListnull || artistList.isNotEmpty)) return false;
else if (tabController.index == 3 && (sheetList
null || sheetList.isNotEmpty)) return false;
var cancel = Utils.showLoading();
List data = await GoodsSearch().getSearchRes(query, type: tabTitle[tabController.index][‘type’]);
cancel();
if (tabController.index == 0) songList = data;
else if (tabController.index == 1) albumList = data;
else if (tabController.index == 2) artistList = data;
else if (tabController.index == 3) sheetList = data;
showResults(context);

}
loadMoreData(int page) async{
// var cancel = Utils.showLoading();
List data = await GoodsSearch().getSearchRes(query, type: tabTitle[tabController.index][‘type’],page: page);
// cancel();
return data;
}

@override
Widget buildAppBarBottom(BuildContext context) { //tabbar
return PreferredSize(
preferredSize: Size.fromHeight(40.0),
child: Container(
height: 40,
child: TabBar(
controller: tabController,
indicatorSize: TabBarIndicatorSize.label,
labelColor: Theme.of(context).primaryColor,
tabs: List.generate(
tabTitle.length,
(index) => Tab(
text: tabTitle[index][‘name’],
)),
)));
}
下面代码与官方类似,重写相应方法

/* note
custom_serach_goods.dart 第347行,maintainState设置为true,可路由跳转返回后保持之前状态
在118行新增loadData,buildAppBarBottom等一些数据并重写用于tabbar,
296行设置historyIndex用于删除历史记录更新页面, and 477
556行用于回车加载数据
*/


![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/93f6af50b6bf4729a4972ce1c176a2c7~tplv-k3u1fbpfcp-watermark.image) ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5f7210eadb2a4bd1b83f9e73453d59f8~tplv-k3u1fbpfcp-watermark.image) ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6026a7e837d3441f8e2cb70dbc89957f~tplv-k3u1fbpfcp-watermark.image)

![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d4646ef090514c6ab82119d665488ff6~tplv-k3u1fbpfcp-watermark.image)

父子组件相互调用方法
----------

*   子组件调用父组件

方法少的话,直接传方法名到子组件,子组件调用即可。  
方法多的话,使用抽象类。父组件实现抽象类,然后将this作为参数传到子组件。

abstract class BottomInputBarDelegate {
String userIdOrGroupId;
int chatType=0;

void insertNewMessage(EMMessage msg);
void scrollBottom();
}

 @override // 父组件
  void scrollBottom() {
    if (_msgListController.offset != 0.0)
      //_msgListController.jumpTo(0.0);
      _msgListController.animateTo(
        0.0,
        curve: Curves.easeInOut,
        duration: const Duration(milliseconds: 200),
      );
  }
  ...
   ChatBottomInputTool(
                this,
                childController: childController,
              ) 

class ChatBottomInputTool extends StatefulWidget{ // 子组件
final ChildPageController childController;
final BottomInputBarDelegate delegate;

ChatBottomInputTool(this.delegate,{@required this.childController});
@override
State createState() {
// TODO: implement createState
return _ChatBottomInputToolState(childController);
}
}

widget.delegate.scrollBottom();


*   父组件调用子组件

借助Controller控制器思想来实现。

class ChildPageController{
bool Function() closeBottom;
}

class ChatBottomInputTool extends StatefulWidget{ // 子组件
  final ChildPageController childController;
  final BottomInputBarDelegate delegate;

  ChatBottomInputTool(this.delegate,{@required this.childController});
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _ChatBottomInputToolState(childController);
  }
}

### 学习宝典

对我们开发者来说,**一定要打好基础,随时准备战斗**。不论寒冬是否到来,都要**把自己的技术做精做深**。虽然目前移动端的招聘量确实变少了,但中高端的职位还是很多的,这说明行业只是变得成熟规范起来了。竞争越激烈,产品质量与留存就变得更加重要,我们进入了技术赋能业务的时代。

>不论遇到什么困难,都不应该成为我们放弃的理由!

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我针对Android程序员,我这边给大家整理了一套**学习宝典**!包括不限于高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://codechina.csdn.net/m0_60958482/android_p7)**

**【Android核心高级技术PDF文档,BAT大厂面试真题解析】**

![](https://img-blog.csdnimg.cn/img_convert/459e6e60de0a75728a8164cda74060e7.png)

**【算法合集】**

![](https://img-blog.csdnimg.cn/img_convert/ed8cd47abc8f7aa86f75da5bfb42f13b.png)

**【延伸Android必备知识点】**

![](https://img-blog.csdnimg.cn/img_convert/9f676ecc970e26fe7871dc20e730bbd8.png)

构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://codechina.csdn.net/m0_60958482/android_p7)**

**【Android核心高级技术PDF文档,BAT大厂面试真题解析】**

[外链图片转存中...(img-pOOQqX6o-1630574144594)]

**【算法合集】**

[外链图片转存中...(img-Rl7yojhB-1630574144595)]

**【延伸Android必备知识点】**

[外链图片转存中...(img-i3QsQ3Hm-1630574144597)]

**【Android部分高级架构视频学习资源】**
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-03 12:02:02  更:2021-09-03 12:03:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:10:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码