base flutter to design
一个简单的flutter封装,包括网络请求,基类等,以及一些常用的方法,包括一个简单的多语言实现。
参考大佬:https://github.com/385841539/flutter_BaseWidget.git
这个不是plugin插件。主要做参考,可自己移植,并导入或修改相关包。
【1】lib下的所有文件覆盖到自己的项目下
【2】assets下的目录结构,language.json为语言转换文件
【3】pubspec.yaml的dependencies(有需要可自己调整版本)
【4】android与iOS的相关权限等配置需自己添加
移植并Packages get后,相应界面的import会报红,用ctrl+shift+r替换baseflutter 为你的项目名
lib目录下分为:
--base -- 基类
--dialog -- 弹窗
--generated -- 自动生成的model管理文件
--network -- 网络相关与model
--res -- 资源管理
--ui -- 页面与组件
--utils -- 工具与管理器
assets目录下可分为
--images
----android
------hdpi
------mdpi
------xhdpi
------xxhdpi
------xxxhdpi
----ios
--language.json
Setting - File and Code Templates - Dart File 中设置:
import 'package:${PROJECT_NAME}/base/common/commonInsert.dart';
///
/// @author [yourName]
class ${NAME} {
}
可导入基础的包以及建立默认class,有需要可自己设置
import 'package:${PROJECT_NAME}/base/common/commonInsert.dart';
///
/// @author [yourName]
class ${NAME} extends BaseWidget {
@override
BaseWidgetState<BaseWidget> getState() => new ${NAME}State();
}
class ${NAME}State extends BaseWidgetState<${NAME}> {
@override
Widget buildWidget(BuildContext context) {
return Container();
}
@override
void onCreate() {
// TODO: implement onCreate
}
@override
void onPause() {
// TODO: implement onPause
}
@override
void onResume() {
// TODO: implement onResume
}
}
例:
void realRunApp() async {
WidgetsFlutterBinding.ensureInitialized();
//将LocalStorage 异步转为同步
bool success = await LocalStorage.getInstance();
assert(success);
//加载语言库
await LanguageUtil.loadLanguage();
runApp(MyApp());
}
MyApp中可以做一些全局的设置管理,如默认字体样式
【1】继承(extends) 使用 BaseWidget 代替 StatefulWidget ,BaseWidgetState 代替BaseWidgetState; 但是 组件(Widget) 依旧是用StatefulWidget,要分清楚页面与组件的区别。
【2】BaseWidget封装了页面状态,完善了生命周期(参考Android),
主要有onCreate,onResume, onPause, onDestory。替代initState等方法。
可以在
onCreate进行初始化处理
onResume中进行网络请求
onDestory中释放资源
【3】BaseFunction中封装了页面布局结构等,并与网络请求等结合。
如状态栏,标题,body,loading,error等
get相关的方法均可以在继承了BaseWidget的页面中进行重写(override)
set相关的方法可以调用以修改单独页面的特定属性。
loading,error等可以修改为项目所需的样式。与封装的网络请求结合使用。
网络请求例子:
await RequestUtil.testRequest(ShowLoadingIntercept(this), 100).then((event) {
// 成功处理
print(event.data);
}, onError: (e) {
// 错误处理
log(e.message.toString());
});
需传入ShowLoadingIntercept拦截器(可自定义)才能有loading等, 在StatefulWidget的组件中可通过传入BaseFunction参数来使用,不需要loading可传NoActionIntercept
ShowLoadingIntercept(widget.baseFunction)
也可手动调用loading展示
setLoadingWidgetVisible(true);
具体可以查看BaseFunction,均有注释。
新建一个继承BaseWidget的“底”,使用BaseTabBarWidget作为导航栏。
例:
@override
Widget buildWidget(BuildContext context) {
// TODO: implement buildWidget
return BaseTabBarWidget(
tabItems: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: Icon(Icons.home,size: 22,),
activeIcon: Icon(Icons.home,size: 22,color: MyColors.blue,),
title: Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
"Home",
),
),
),
new BottomNavigationBarItem(
icon: Icon(Icons.people,size: 22,),
activeIcon: Icon(Icons.people,size: 22,color: MyColors.blue,),
title: Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
"Mine",
),
),
),
],
tabViews: <Widget>[
new Home(),
new Mine(),
],
pageControl: pageController,
indicatorColor: MyColors.blue,
);
}
其中 Home Mine 页面继承 BaseInnerWidget setIndex需设置为第X个页面
@override
int setIndex() => 0;
需注意的是,
作为“底”的Lead,要在onCreate中设置状态栏和标题栏不显示(除非需要导航的所有页面都有同样的标题栏);
在页面(BaseInnerWidget)中需要状态栏和标题栏的在onCreate设置显示,默认为不显示。
BaseCommon是常用方法的封装
包括 页面导航方法,网络图片 等, 常用的方法在该类进行复用。
Commons常量需要专门管理
网络请求更改为future的方式,更好的处理控制。
网络的相关配置在HttpManager中进行配置
如何使用参考 Address 和 RequestUtil 中的写法。
基础拦截器如 BaseIntercept ,可在其中进行拦截处理,比如请求前添加token 可增加额外的拦截器比如ShowLoadingIntercept, 并继承 BaseIntercept
若不需要loading拦截器,可传一个基础的拦截器,如NoActionIntercept,不建议传null,因为涉及到统一错误管理需要基类。
在transform中进行处理,可自定义,可不同接口不同处理。
model的构建需用一个插件:FlutterJsonBeanFactory
通过请求所得的json,在model下,根据json直接通过该插件 new一个model即可。
创建的model在对应的请求接口上声明 Future<XXXModelEntity>
即在listener中直接使用。
颜色,样式,字符等的相关管理。
LanguageUtil为多语言管理器,通过读取assets中的json文件,得到相对应语言的翻译。
在runApp前进行读取
await LanguageUtil.loadLanguage();
使用:
LanguageUtil.getText("test")
//在BaseWidget页面可直接
getText("text")
切换语言:
// 必须要setState才能更改当前页面的语言,最好判断下mounted
setState(() {
LanguageUtil.changeLanguage(SwitchLanguage.zh);
});
Language.json写法:
{
"test" : {
"eg" : "test",
"zh" : "测试"
},
"yes" : {
"eg" : "yes",
"zh" : "是"
}
}
【1】本地图片, 返回Widget:
LocalImageSelecter.getImage("image")
// 在BaseWidget页面中可直接
getImage("image");
具体可看LocalImageSelecter
【2】网络图片,返回Widget:
BaseCommon.netImage("",20,20)
在runApp前可将异步转为同步
//将LocalStorage 异步转为同步
bool success = await LocalStorage.getInstance();
存:
LocalStorage.save();
取:
LocalStorage.get();
ScreenAdapter
根据设计图与实际屏幕的宽度(高度)计算得到相应比例的距离,
在main中对设计图的宽高设置,两个值由UI的设计图来定。
ScreenAdapter.designWidth = 300;
ScreenAdapter.designHeight = 800;
但必须谨慎使用
禁止直接用该工具直接做适配,一个页面的适配不是靠一个工具就能实现
改工具适配原理(BaseFunction中也有强调):
/// 根据设计图中的总宽高和所需[width],计算得出当前屏幕下的[width]。
/// 但是,大多数情况下并不需要换算这个值!
/// 系统默认距离单位是dp,简单来说就是会自动适配的单位,一般不同屏幕差距不大,不同于px。
/// 随意使用很容易产生比例与设计图不一致的问题,因为设计图宽高比与实际宽高比不一致就会
/// 产生这样的问题。比较明显的就是正方形变成长方形。
/// 因此,一般的使用方法是:
/// 当组件需要根据横向方向(竖直方向)分布时,适配设计图中的该组件的[width],
/// 但竖直方向(横向方向)不应适配,并且最好是不设置竖直方向(横向方向)的距离,
/// 或者说赋予与横向方向(竖直方向)适配的值,假如是正方形的话。
/// 通常整个页面的布局适配应利用margin、padding,或者position等属性值来实现,
/// 使用该适配应是少数情况。
///
即,就单位来说,系统已经是有自己的适配,不需要我们画蛇添足给他们换算。
仅有页面设计的布局拥挤且不可滚动,需要计算每一个控件的分布区域的少数特殊情况下,才需要用这个来计算。
很有用的工具。
当一个页面上的处理需要通知到另一个可见或不可见的页面进行处理时,就可利用这个工具进行通知。
例:同一级导航中,当我在 商场 消费了金币,需要同时保证 钱包 中的显示金额同步变化,但仅是切换导航(或者说返回页面)并不能调用接口更新,
就可以通过bus发送一个事件,通知钱包页调用接口更新钱包数据。
一个bus的设计:
class TestEventBus {
static final TestEventBus _gInstance = TestEventBus._init();
EventBus _eventBus = EventBus();
TestEventBus._init() {
///
}
factory TestEventBus() {
return _gInstance;
}
EventBus get bus {
return _eventBus;
}
}
/// 事件
class RefreshEvent {
String data;
RefreshEvent({this.data});
}
监听:
/// 监听
TestEventBus().bus.on<RefreshEvent>().listen((event) {
log(event.data);
});
发送事件:
/// 发送后所有添加了该监听的监听器都会收到
TestEventBus().bus.fire(RefreshEvent(data: "数据"));
fire可以指定事件给予不同的数据,在相同bus的监听上都会受到信息。
引导页具体设置在LaunchPage.dart中
根据版本号判断是否应该显示引导页,但版本号的获取不应在引导页中进行,应该在app加载前获取。
/// 在进入app前的初始化
void realRunApp() async {
// 加载版本号
await VersionInfo.getInstance();
runApp(MyApp());
}
通过接口等方法同理。
PermissionManager.dart:
检查并添加权限:
/// 检查权限
if (await Permission.storage.isUndetermined){
/// 添加权限
listPermission.add(Permission.storage);
}
在runApp中请求权限:
// 请求权限
await PermissionManager.request();
若想做请求之后结果的判断:
Map<Permission, PermissionStatus> statuses = await listPermission.request();
/// 处理请求结果
print(statuses);