如果你在idea中使用鼠标拖拽移动了文件路径,那么恭喜你这个文件的热加载失效了。 原因是文件路径变成了绝对路径,类似C:\Users…,这种路径热加载识别不了,需改为package:…
点击空白无效
(1)GestureDetector设置参数 behavior 为 HitTestBehavior.opaque, (2)使用InkWell组件
键盘溢出
默认情况下,键盘弹起,flutter会将页面上推,可能会导致溢出报错,解决办法有两个:
- 修改默认值
Scaffold(
appBar: AppBar(
title: new Text("首页"),
),
resizeToAvoidBottomPadding: false, //默认值为true,表示页面上推, 设置false表示不上推,此时键盘可能会盖住页面,类似stack层叠效果
);
- 使用滚动组件
使用SingleChildScrollView或者listview组件作为根元素,此时就不要设置resizeToAvoidBottomPadding为false了,要不然就没有页面上推了
showModalBottomSheet 底部弹出 问题
shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))),
默认高度为半屏,设置isScrollControlled为true是全屏,不想全屏,使用BoxConstraints组件限制最大高度
首先使用SingleChildScrollView作为根组件让其可以滚动,然后获取键盘高度MediaQuery.of(context).viewInsets.bottom作为padding的bottom,因为这不在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 && (albumListnull || albumList.isNotEmpty)) return false; else if (tabController.index == 2 && (artistListnull || artistList.isNotEmpty)) return false; else if (tabController.index == 3 && (sheetListnull || 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部分高级架构视频学习资源】**
|