先记录这么点,还有很多技巧干活,只要学,会思考,每天都在进步
定义模型 其实现在有json转dart工具
class SearchModel {
String keyword;
final List<SearchItem> data;
SearchModel({this.data});
factory SearchModel.fromJson(Map<String, dynamic> json) {
var dataJson = json['data'] as List;
List<SearchItem> data =
dataJson.map((item) => SearchItem.fromJson(item)).toList();
return SearchModel(data: data);
}
}
class SearchItem {
final String word;
final String type;
final String price;
final String star;
final String zonename;
final String districtname;
final String url;
SearchItem(
{this.word,
this.type,
this.price,
this.star,
this.zonename,
this.districtname,
this.url});
factory SearchItem.fromJson(Map<String, dynamic> json) {
return SearchItem(
word: json['word'],
type: json['type'],
price: json['price'],
star: json['star'],
zonename: json['zonename'],
districtname: json['districtname'],
url: json['url'],
);
}
}
接口请求
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flutterapptrip/model/search_model.dart';
const SEARCH_URL = 'https://xxxxxx/search?keyword=';
class SearchDao {
static Future<SearchModel> fetch(String keyword) async {
Response response = await Dio().get(SEARCH_URL + keyword);
if (response.statusCode == 200) {
SearchModel model = SearchModel.fromJson(response.data);
model.keyword = keyword;
return model;
} else {
throw Exception('Failed to load search');
}
}
}
监听scroll,下拉刷新
double appBarAlpha = 0;
const APPBAR_SCROLL_OFFSET = 100;
_onScroll(offset) {
double alpha = offset / APPBAR_SCROLL_OFFSET;
if (alpha < 0) {
alpha = 0;
} else if (alpha > 1) {
alpha = 1;
}
setState(() {
appBarAlpha = alpha;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xfff2f2f2),
body: LoadingContainer(
isLoading: _loading,
child: Stack(
children: <Widget>[
MediaQuery.removePadding(
removeTop: true,
context: context,
child: RefreshIndicator(
child: NotificationListener(
onNotification: (scrollNotification) {
if (scrollNotification
is ScrollUpdateNotification &&
scrollNotification.depth == 0) {
_onScroll(scrollNotification.metrics.pixels);
}
},
child: _listView,
),
onRefresh: _handleRefresh)),
_appBar
],
)));
}
Swiper的调用
Widget get _banner {
return Container(
height: 240,
child: Swiper(
itemCount: bannerList.length,
autoplay: true,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
CommonModel model = bannerList[index];
return WebView(
url: model.url,
title: model.title,
hideAppBar: model.hideAppBar);
}));
},
child: Image.network(
bannerList[index].icon,
fit: BoxFit.fill,
),
);
},
pagination: SwiperPagination(),
));
}
列表渲染
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
_appBar(),
MediaQuery.removePadding(
removeTop: true,
context: context,
child: Expanded(
flex: 1,
child: ListView.builder(
itemCount: searchModel?.data?.length ?? 0,
itemBuilder: (BuildContext context, int position) {
return _item(position);
}),
))
],
),
);
}
搜索关键字高亮技巧
_keywordTextSpans(String word, String keyword) {
List<TextSpan> spans = [];
if (word == null || word.length == 0) return spans;
List<String> arr = word.split(keyword);
TextStyle normalStyle = TextStyle(fontSize: 16, color: Colors.black87);
TextStyle keywordStyle = TextStyle(fontSize: 16, color: Colors.orange);
for (int i = 0; i < arr.length; i++) {
if ((i + 1) % 2 == 0) {
spans.add(TextSpan(text: keyword, style: keywordStyle));
}
String val = arr[i];
if (val != null && val.length > 0) {
spans.add(TextSpan(text: val, style: normalStyle));
}
}
return spans;
}
动画
Animation<double> animation;
AnimationController controller;
@override
void initState() {
controller = AnimationController(
vsync: this, duration: Duration(milliseconds: 1000));
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
super.initState();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
Center(
child: AnimatedMic(animation: animation),
)
const double MIC_SIZE = 80;
class AnimatedMic extends AnimatedWidget {
static final _opacityTween = Tween<double>(begin: 1, end: 0.5);
static final _sizeTween = Tween<double>(begin: MIC_SIZE, end: MIC_SIZE - 20);
AnimatedMic({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return Opacity(
opacity: _opacityTween.evaluate(animation),
child: Container(
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(MIC_SIZE / 2)),
child: Icon(
Icons.mic,
color: Colors.white,
size: 30,
),
),
);
}
}
布局,定位,元素绑定事件
_bottomItem() {
return FractionallySizedBox(
widthFactor: 1,
child: Stack(
children: <Widget>[
GestureDetector(
onTapDown: (e) {
_speakStart();
},
onTapUp: (e) {
_speakStop();
},
onTapCancel: () {
_speakStop();
},
child: Center(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.all(10),
child: Text(
speakTips,
style: TextStyle(color: Colors.blue, fontSize: 12),
),
),
Stack(
children: <Widget>[
Container(
height: MIC_SIZE,
width: MIC_SIZE),
Center(
child: AnimatedMic(animation: animation),
)
],
)
],
),
),
),
Positioned(
right: 0,
bottom: 20,
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Icon(
Icons.close,
size: 30,
color: Colors.grey,
),
),
)
],
),
);
}
}
跳转页面传参及返回页面
AsrManger.start().then((text) {
if (text != null && text.length > 0) {
setState(() {
speakResult = text;
});
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SearchPage(keyword: speakResult)));
}
}).catchError((onError) => print(onError));
}
延时操作
Future.delayed(Duration(seconds: 1), (){
Navigator.of(context).pop();
print('延时1s执行');
});