-
实现widget隐藏
方法
- 使用Opacity
bool _visiable = false;
Opacity(
opacity: _visiable ? 1.0 : 0.0,
child: const Text('Now you see me, now you don\'t!'),
)
隐藏后仍然进行渲染,仍然占有空间,仍然可以进行交互
- 使用Offstage
Offstage(
offstage: true,
child: child
)
);
隐藏后不会进行渲染,不占用空间
- 使用visibility
Visibility(
visible: false,
child: child
)
)
隐藏后不会进行渲染,不占用空间
-
实现gridview单击一个item放大后左右滑动浏览
场景:
阅读原文下面是gridView展示的widget,希望的效果是: 点击第二个item放大后方法
- 使用Swiper
Swiper(
itemCount: w_list.length,
//itemWidth: MediaQuery.of(context).size.width,
itemBuilder: _swiperBuilder,
pagination: SwiperPagination(
alignment: Alignment.bottomCenter,
margin: const EdgeInsets.fromLTRB(0, 0, 0, 10),
builder: DotSwiperPaginationBuilder(
color: Colors.black54,
activeColor: Colors.blue,
)
),
controller: SwiperController(),
autoplayDisableOnInteraction: true,
scrollDirection: Axis.horizontal,
autoplay: false,
index: initial_index,//设置成开始浏览时点击的item的index值
viewportFraction: 1.0,
scale: 1.0,
//layout: SwiperLayout.STACK,
//onTap: (index) => print('点击了第$index'),
),
使用pageview可以实现放大左右切换,但是相比swiper
(1)没有自带分页器
(2)pageview的pageViewController.jumpToPage()方法在必须在PageView构建后才可以使用,但是我们希望开始浏览时就已经跳转到目标item,无法满足需求。Swiper只需要简单设置index便可以解决上述问题
-
场景同上,其中同时包含视频和图片类型的item,可以做到滑动浏览及在线视频点击播放
方法
在Swiper的itemBuilder中,对于视频类型的item,返回的widget并非视频,而是视频的第一帧加一个播放图标堆叠在上面,widget代码如下
Widget get_video_widget(String url)
{
/*
返回存放视频的容器,参数是视频第一帧url
*/
return Container(
decoration: BoxDecoration(
image: new DecorationImage(
// fit:BoxFit.fill,
image: new NetworkImage(url, scale: 1.2)),
),
child:Opacity(
opacity: 0.8,
child: Center(
child:Icon(Icons.play_circle_filled,color: Colors.white,)
),
),
);
}
当检测到用户点击时,弹出dialog形式的新的界面进行视频播放,而非Swiper内部播放,(Swiper内部播放会出现很多问题)
IjkMediaController jk_controller = IjkMediaController();
return GestureDetector(
child:w_list[index],//是上面 get_video_widget返回的widget
onTap: (){
showIJKDialog();
},
);
}
@override
void dispose() {
jk_controller?.dispose();
super.dispose();
}
void showIJKDialog() async {
await jk_controller.setDataSource(
DataSource.network(
"http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4"),
);
await jk_controller.play();
await showDialog(
context: context,
builder: (_) => _buildIJKPlayer(),
);
jk_controller.pause();
}
_buildIJKPlayer() {
return IjkPlayer(
mediaController: jk_controller,
);
}
播放效果如下
这里使用的用来播放视频的flutter库叫做flutter_ijkplayer,
相比较video_player,拥有更好的ui界面及更多的视频操作功能,
相比较cheiwe,拥有更快的加载速度,更高的稳定性
一个很好的flutter_ijkplayer demo地址
-
实现http请求
方法
- 使用http请求
String url = "http://xxxxxxx"
Map<String,String> json_data = {};
json_data.addAll({"xx":xxx});
await http.post(url, body: json_data)
.then((response) {
print("post方式->status: ${response.statusCode}");
print("post方式->body: ${response.body}");
});
-
显示圆形的container
方法
- 直接看代码
ClipOval(
child: Container(
height: MediaQuery.of(context).size.height/4,
width: MediaQuery.of(context).size.height/4,
child: Image.file(File(widget.imagePath),fit: BoxFit.fill,),
),
);
-
Camera自定义拍照
方法
- 看代码 基本思路是 获取可用相机 取第一个相机 得到相机控制器 控制器初始化 打开相机预览 得到照片保存路径 控制器拍照 展示拍照结果
import 'dart:async';
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart' show join;
import 'package:path_provider/path_provider.dart';
Future<void> main() async {
// 获取设备可用摄像头
final cameras = await availableCameras();
// 取第一个摄像头
final firstCamera = cameras.first;
runApp(
MaterialApp(
theme: ThemeData.dark(),
home: TakePictureScreen(
// Pass the appropriate camera to the TakePictureScreen widget.
camera: firstCamera,
),
),
);
}
// A screen that allows users to take a picture using a given //camera.
class TakePictureScreen extends StatefulWidget {
final CameraDescription camera;
const TakePictureScreen({
Key key,
@required this.camera,
}) : super(key: key);
@override
TakePictureScreenState createState() => TakePictureScreenState();
}
class TakePictureScreenState extends State<TakePictureScreen> {
CameraController _controller;
Future<void> _initializeControllerFuture;
@override
void initState() {
super.initState();
// 获取摄像头的控制器
_controller = CameraController(
// Get a specific camera from the list of available cameras.
widget.camera,
ResolutionPreset.medium,
);
// 初始化控制器
_initializeControllerFuture = _controller.initialize();
}
@override
void dispose() {
//销毁控制器
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Take a picture')),
//FutureBuilder来确保控制器已经被初始化
body: FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
//确定初始化完成,打开相机
return CameraPreview(_controller);
} else {
// 否则,展示进度条.
return Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.camera_alt),
onPressed: () async {
try {
// 确保相机已经被初始化
await _initializeControllerFuture;
final path = join(
//得到图片保存路径
(await getTemporaryDirectory()).path,
'${DateTime.now()}.png',
);
// 进行拍照
await _controller.takePicture(path);
// 拍照后进行展示
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DisplayPictureScreen(imagePath: path),
),
);
} catch (e) {
print(e);
}
},
),
);
}
}
//展示拍照结果的界面
class DisplayPictureScreen extends StatelessWidget {
final String imagePath;
const DisplayPictureScreen({Key key, this.imagePath}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Display the Picture')),
//根据路径得到图片
body: Image.file(File(imagePath)),
);
}
}
-
ui实现悬浮设计
方法
-
使用Container的decoration设置背景,
内嵌---Container,通过decoration的gradient设置背景图上层的一个渐变色渲染
内嵌---Stack,进行布局 -
一些color使用Color.fromRGBO(247, 64, 106, 0.5),通过调节透明度实现一个悬浮效果
new Container(
decoration: new BoxDecoration(
image: backgroundImage,
),
child: new Container(
decoration: new BoxDecoration(
gradient: new LinearGradient(
colors: <Color>[
const Color.fromRGBO(162, 146, 199, 0.8),
const Color.fromRGBO(51, 51, 63, 0.9),
],
stops: [0.2, 1.0],
begin: const FractionalOffset(0.0, 0.0),
end: const FractionalOffset(0.0, 1.0),
)),
child: new ListView(
padding: const EdgeInsets.all(0.0),
children: <Widget>[
new Stack(
alignment: AlignmentDirectional.bottomCenter,
children: <Widget>[
new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(height: 10,
child:Text("悬浮设计")),
],
),
],
),
],
))),
-
ui保证异步操作结束后进行build
方法
- 使用FutureBuilder
注意:
- initState和构造函数不可以直接间接调用异步方法
- FutureBuilder 必须提供一个异步结束前显示的widget,一般是
Center(child: CircularProgressIndicator());
代码示例:
FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return CameraPreview(_controller);
} else {
// Otherwise, display a loading indicator.
return Center(child: CircularProgressIndicator());
}
},
),
-
前端使用preferences
方法
- 添加依赖到pubspec.yaml
shared_preferences: ^0.4.3
- 存数据
final prefs = await SharedPreferences.getInstance();
prefs.setString("name","Jerry");
3.取数据
final prefs = await SharedPreferences.getInstance();
final name = prefs.getString("count") ?? "";
-
build结束后的监听事件
方法
- 使用WidgetsBinding WidgetBinding类官方解释是控制层和Flutter引擎之间的粘合剂,这个类可以监听第一帧的绘制结束,第一帧绘制结束标志着build完成,使用方法见代码
WidgetsBinding widgetsBinding;//用来监听build结束后的响应事件
@override
void initState() {
super.initState();
widgetsBinding = WidgetsBinding.instance;
widgetsBinding.addPostFrameCallback((callBack){
dealWithAutoLand();
dealWithRememberPw();
});
}
-
使用Form表单构建输入框
方法:
使用两个文件
InputFields.dart构建单独一个输入框widget,代码
import 'package:flutter/material.dart';
//对应一个输入框
class InputFieldArea extends StatelessWidget {
final String hint;
final bool obscure;
final IconData icon;
TextEditingController controller;
FormFieldValidator validator;
InputFieldArea({this.hint, this.obscure, this.icon,this.controller,this.validator});
@override
Widget build(BuildContext context) {
return (new Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(
width: 0.5,
color: Colors.white24,
),
),
),
child: new TextFormField(
controller: controller,
autofocus: true,
obscureText: obscure,
validator: validator,
style: const TextStyle(
color: Colors.white,
),
decoration: new InputDecoration(
icon: new Icon(
icon,
color: Colors.white,
),
border: InputBorder.none,
hintText: hint,
hintStyle: const TextStyle(color: Colors.white, fontSize: 15.0),
contentPadding: const EdgeInsets.only(
top: 30.0, right: 30.0, bottom: 30.0, left: 5.0),
),
),
));
}
}
form.dart,组织所有输入框,代码
TextEditingController studentNumberControllerLogin = new TextEditingController();
TextEditingController passwordNumberControllerLogin = new TextEditingController();
//登陆的输入表单,包含两个输入框
class loginFormContainer extends StatelessWidget {
Map<String,String> inputData;
//得到输入数据函数
void getData()
{
inputData = new Map();
inputData["studentNumber"] = studentNumberControllerLogin.text.toString();
inputData["password"] = passwordNumberControllerLogin.text.toString();
}
@override
Widget build(BuildContext context) {
return (new Container(
margin: new EdgeInsets.symmetric(horizontal: 20.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Form(
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
new InputFieldArea(
controller: studentNumberControllerLogin,
hint: "学号",
obscure: false,
icon: Icons.person_outline,
),
new InputFieldArea(
controller: passwordNumberControllerLogin,
hint: "密码",
obscure: true,
icon: Icons.lock_outline,
),
],
)),
],
),
));
}
}
-
设置appBar高度
方法
- 使用PreferredSize
appBar:PreferredSize(
preferredSize: Size.fromHeight(2.0),//修改appbar高度
child: AppBar(
title: Text("地大知识交流平台"),
),
),
-
设置渐变色
方法
1.gradient使用LinearGradinet,代码
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.cyan, Colors.blue, Colors.cyan],
),
),
-
Container各个属性使用
给个不错的链接
-
屏幕适配
方法
- 安装,写入pubspec.yaml处
flutter_screenutil: ^0.5.3
- materialApp的入口处,即home页面,传入设计手稿设计尺寸,进行初始化。整个app使用过程中只需要初始化一次
//填入设计稿中设备的屏幕尺寸
ScreenUtil.instance = ScreenUtil(width: 1080, height: 1920)..init(context);
- 使用时,分成字体大小、正方形、宽度、高度设置
- 字体
ScreenUtil.getInstance().setSp(28)
- 正方形
Container(
width: ScreenUtil.getInstance().setWidth(300),
height: ScreenUtil.getInstance().setWidth(300),
),
- 宽度
ScreenUtil.getInstance().setWidth(300),
- 高度
ScreenUtil.getInstance().setHeight(300),
-
上传图片到服务器
方法:使用dio
第一步,安装dio
dio: ^2.1.0
第二步,封装dio
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
//普通格式的header
Map<String, dynamic> headers = {
"Accept":"application/json",
// "Content-Type":"application/x-www-form-urlencoded",
};
//json格式的header
Map<String, dynamic> headersJson = {
"Accept":"application/json",
"Content-Type":"application/json; charset=UTF-8",
};
class HttpUtil {
Dio dio;
Map<String, dynamic> headers;
BaseOptions options;
HttpUtil({String baseUrl}) {
headers = {
"Accept":"application/json",
// "Content-Type":"application/x-www-form-urlencoded",
};
print('dio赋值');
// 或者通过传递一个 `options`来创建dio实例
options = BaseOptions(
// 请求基地址,一般为域名,可以包含路径
baseUrl: baseUrl,
//连接服务器超时时间,单位是毫秒.
connectTimeout: 10000,
//[如果返回数据是json(content-type),dio默认会自动将数据转为json,无需再手动转](https://github.com/flutterchina/dio/issues/30)
responseType:ResponseType.plain,
/// 响应流上前后两次接受到数据的间隔,单位为毫秒。如果两次间隔超过[receiveTimeout],
/// [Dio] 将会抛出一个[DioErrorType.RECEIVE_TIMEOUT]的异常.
/// 注意: 这并不是接收数据的总时限.
receiveTimeout: 3000,
headers: headers,
);
dio = new Dio(options);
dio.interceptors.add(CookieManager(CookieJar()));
}
get(url, {data, options, cancelToken}) async {
print('get请求启动! url:$url ,body: $data');
Response response;
try {
response = await dio.get(
url,
cancelToken: cancelToken,
);
print('get请求成功!response.data:${response.data}');
} on DioError catch (e) {
if (CancelToken.isCancel(e)) {
print('get请求取消! ' + e.message);
}
print('get请求发生错误:$e');
}
return response.data;
}
post(url, {data, options, cancelToken}) async {
print('post请求启动! url:$url ,body: $data');
Response response;
try {
response = await dio.post(
url,
data: data,
);
print('post请求成功!response.data:${response.data}');
} on DioError catch (e) {
if (CancelToken.isCancel(e)) {
print('post请求取消! ' + e.message);
}
print('post请求发生错误:$e');
}
return response.data;
}
}
第三步,上传图片
String url = "http://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
FormData formData = new FormData.from({
"image01": new UploadFileInfo(file, "head.jpg")
});
HttpUtil httpUtil = new HttpUtil(baseUrl: url);
String response = await httpUtil.post(url,data: formData);
print(response);
-
flutter使用toast
方法
第一步,安装toast插件
toast: ^0.1.3
第二步,使用
//显示toast
void showToastContent(String content)
{
Toast.show(content,curContext,duration: Toast.LENGTH_LONG,gravity: Toast.BOTTOM);
}
-
flutter网络请求判断出网络原因
方法
设置异常处理块,思想是捕获到异常则代表网络连接失败
List<String> splits = response.split("_");
String resultMark = splits[0];
String content;
try
{
content = splits[1];
}catch(e,s)
{
content = "网络原因,登陆失败";
}
-
返回拦截,防止用户意外退出软件
方法
DateTime lastTime;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("点两次返回键退出"),),
body: Builder(builder: (context)=>WillPopScope(
onWillPop: () async {
if (lastTime == null || DateTime.now().difference(lastTime) >
Duration(seconds: 1)) {
lastTime = DateTime.now();
Scaffold.of(context).showSnackBar(SnackBar(content: Text("再点一次退出!")));
return false;
}
return true;
},
child: Container(
alignment: Alignment.center,
child: Text("1秒内连续按两次返回键退出"),
)
))
)
);
}
-
强制竖屏不可旋转屏幕
方法
import 'package:flutter/services.dart';
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]).then((_){});
-
禁止debug信息显示在手机屏幕上
方法
main函数内部放
ErrorWidget.builder = (FlutterErrorDetails details) => Container();
-
定时前台显示通知
方法
1 安装插件
flutter_local_notifications: ^0.8.4
2 配置android
2.1 AndroidManifest.xml权限添加
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
2.2 drawable下创建图标名为app_icon
3 配置ios 这一步不需要做,只要一步一步来,后面的代码中会包含
4 相关代码
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
//初始化
var initializationSettingsAndroid =
AndroidInitializationSettings('app_icon');
var initializationSettingsIOS = IOSInitializationSettings(
onDidReceiveLocalNotification: onDidReceiveLocalNotification);
var initializationSettings = InitializationSettings(
initializationSettingsAndroid, initializationSettingsIOS);
flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: onSelectNotification);
//显示通知的函数
Future<void> showNotification(String content) async {
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'your channel id', 'your channel name', 'your channel description',
importance: Importance.Max, priority: Priority.High, ticker: 'ticker');
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
int notiId = prefs.getInt("notiId") ?? 0;
prefs.setInt("notiId",notiId + 1);
await flutterLocalNotificationsPlugin.show(
notiId, '地大支持平台', "有人解答了你的问题", platformChannelSpecifics,
payload: content);
}
//通知点击事件
Future<void> onSelectNotification(String payload) async {
if (payload != null) {
print("payload*****************************" + payload);
}
await Navigator.push(
projectTopContext,
MaterialPageRoute(builder: (context) => showAnswerComplete(xxx)),
);
}
//适配ios老版本,与点击事件响应一致
Future onDidReceiveLocalNotification(
int id, String title, String body, String payload) async {
// display a dialog with the notification details, tap ok to go to another page
showDialog(
context: context,
builder: (BuildContext context) => new CupertinoAlertDialog(
title: new Text(title),
content: new Text(body),
actions: [
CupertinoDialogAction(
isDefaultAction: true,
child: new Text('Ok'),
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
Navigator.push(
projectTopContext,
MaterialPageRoute(builder: (context) => showAnswerComplete(xxx))
);
},
)
],
),
);
}