今天我們要實現(xiàn)一個豆瓣的Top150頁面茂装,看下豆瓣的效果陪汽。
我們實現(xiàn)的效果:
ListView况增,顧名思義训挡,列表組件澜薄。
ListView({
Key key,
//垂直還是水平方向
Axis scrollDirection = Axis.vertical,
//列表數(shù)據(jù)是否翻轉(zhuǎn)
bool reverse = false,
//控制滾動的組件,比如:將listview滾動到某個位置颊艳,此時可以使用棋枕。
ScrollController controller,
//
bool primary,
ScrollPhysics physics,
//listView的大小是否占滿整個空間。false:占滿兵睛,true:不占滿
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
//listview的內(nèi)容窥浪,子組件漾脂。
List<Widget> children = const <Widget>[],
int semanticChildCount,
})
shrinkWrap(bool)
shrinkWrap | 效果 |
---|---|
true | shrinkWrap-true.png
|
false | shrinkWrap-false.png
|
import 'package:flutter/material.dart';
import 'dart:math';
class FlutterListView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
children: getListWidgets());
}
}
//生成listview children Widgets
List<Widget> getListWidgets() {
List<ItemData> list = List();
Random random = Random();
for (int i = 0; i < 100; i++) {
int r = random.nextInt(255);
int g = random.nextInt(255);
int b = random.nextInt(255);
list.add(ItemData(Color.fromARGB(255, r, g, b), i.toString()));
}
return list.map((item) => ListViewItem(item)).toList();
}
class ListViewItem extends StatelessWidget {
final ItemData itemData;
ListViewItem(this.itemData);
@override
Widget build(BuildContext context) {
return Container(
width: 150,
height: 70,
//ListTile可以作為listView的一種子組件類型拆融,支持配置點擊事件啊终,一個擁有固定樣式的Widget
child: ListTile(
leading: CircleAvatar(
backgroundColor: itemData.color,
child: Text(
itemData.text,
style: TextStyle(color: Colors.white),
),
),
title: Text(itemData.text),
),
);
}
}
class ItemData {
final Color color;
final String text;
ItemData(this.color, this.text);
}
ListView也支持水平方向顯示蓝牲,設置Axis scrollDirection = Axis.horizontal,即可例衍。
一般我們使用靜態(tài)listview已卸,也就是listview中的children較少的時候累澡,一般會直接使用ListView的構(gòu)造函數(shù)中的children,顯示W(wǎng)idget奥吩。如果比如加載網(wǎng)絡數(shù)據(jù)蕊梧,listView數(shù)據(jù)量較多肥矢,listview的構(gòu)建方式就不能直接使用默認構(gòu)造方法去設置了。這時我們通過使用ListView的builder方法旅东。
ListView.builder
ListView.builder({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
@required IndexedWidgetBuilder itemBuilder,
//listView的item數(shù)量
int itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
int semanticChildCount,
})
itemBuilder
typedef IndexedWidgetBuilder = Widget Function(BuildContext context, int index);
可以看到itemBuilder
是一個方法對象玉锌,參數(shù)為:(BuildContext context, int index)
,返回值是Widget禀倔,也就是listView的子item参淫。
ListView.builder(
//item 的數(shù)量涎才,subjects為我們自己的數(shù)據(jù)源。
itemCount: subjects.length,
itemBuilder: (BuildContext context, int index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
return Text('Hello')
],
);
});
使用起來還是比較簡單的邑闺。
下方高能能棕兼,直接開始講如何寫一個豆瓣Top150頁面
import 'dart:convert' as Convert;
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class DouBanListView extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return DouBanState();
}
}
class DouBanState extends State<DouBanListView> {
var subjects = [];
var itemHeight = 150.0;
requestMovieTop() async {
var httpClient = new HttpClient();
//http://api.douban.com/v2/movie/top250?start=25&count=10
var uri = new Uri.http(
'api.douban.com', '/v2/movie/top250', {'start': '0', 'count': '150'});
var request = await httpClient.getUrl(uri);
var response = await request.close();
var responseBody = await response.transform(Convert.utf8.decoder).join();
Map data = Convert.jsonDecode(responseBody);
setState(() {
subjects = data['subjects'];
});
}
@override
void initState() {
requestMovieTop();
}
@override
Widget build(BuildContext context) {
return Container(
child: getListViewContainer(),
);
}
getListViewContainer() {
if (subjects.length == 0) {
//loading
return CupertinoActivityIndicator();
}
return ListView.builder(
//item 的數(shù)量
itemCount: subjects.length,
itemBuilder: (BuildContext context, int index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
numberWidget(index+1),
getItemContainerView(subjects[index]),
//下面的灰色分割線
Container(
height: 10,
color: Color.fromARGB(255, 234, 233, 234),
)
],
);
});
}
//肖申克的救贖(1993) View
getTitleView(subject) {
var title = subject['title'];
var year = subject['year'];
return Container(
child: Row(
children: <Widget>[
Icon(
Icons.play_circle_outline,
color: Colors.redAccent,
),
Text(
title,
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black),
),
Text('($year)',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey))
],
),
);
}
getItemContainerView(var subject) {
var imgUrl = subject['images']['medium'];
return Container(
width: double.infinity,
padding: EdgeInsets.all(5.0),
child: Row(
children: <Widget>[
getImage(imgUrl),
Expanded(
child: getMovieInfoView(subject),
flex: 1,
)
],
),
);
}
//圓角圖片
getImage(var imgUrl) {
return Container(
decoration: BoxDecoration(
image:
DecorationImage(image: NetworkImage(imgUrl), fit: BoxFit.cover),
borderRadius: BorderRadius.all(Radius.circular(5.0))),
margin: EdgeInsets.only(left: 8, top: 3, right: 8, bottom: 3),
height: itemHeight,
width: 100.0,
);
}
getStaring(var stars) {
return Row(
children: <Widget>[RatingBar(stars), Text('$stars')],
);
}
//電影標題,星標評分颅眶,演員簡介Container
getMovieInfoView(var subject) {
var start = subject['rating']['average'];
return Container(
height: itemHeight,
alignment: Alignment.topLeft,
child: Column(
children: <Widget>[
getTitleView(subject),
RatingBar(start),
DescWidget(subject)
],
),
);
}
//NO.1 圖標
numberWidget(var no) {
return Container(
child: Text(
'No.$no',
style: TextStyle(color: Color.fromARGB(255, 133, 66, 0)),
),
decoration: BoxDecoration(
color: Color.fromARGB(255, 255, 201, 129),
borderRadius: BorderRadius.all(Radius.circular(5.0))),
padding: EdgeInsets.fromLTRB(8, 4, 8, 4),
margin: EdgeInsets.only(left: 12, top: 10),
);
}
}
//類別涛酗、演員介紹
class DescWidget extends StatelessWidget {
var subject;
DescWidget(this.subject);
@override
Widget build(BuildContext context) {
var casts = subject['casts'];
var sb = StringBuffer();
var genres = subject['genres'];
for (var i = 0; i < genres.length; i++) {
sb.write('${genres[i]} ');
}
sb.write("/ ");
List<String> list = List.generate(
casts.length, (int index) => casts[index]['name'].toString());
for (var i = 0; i < list.length; i++) {
sb.write('${list[i]} ');
}
return Container(
child: Text(
sb.toString(),
softWrap: true,
textDirection: TextDirection.ltr,
style:
TextStyle(fontSize: 16, color: Color.fromARGB(255, 118, 117, 118)),
),
);
}
}
class RatingBar extends StatelessWidget {
double stars;
RatingBar(this.stars);
@override
Widget build(BuildContext context) {
List<Widget> startList = [];
//實心星星
var startNumber = stars ~/ 2;
//半實心星星
var startHalf = 0;
if (stars.toString().contains('.')) {
int tmp = int.parse((stars.toString().split('.')[1]));
if (tmp >= 5) {
startHalf = 1;
}
}
//空心星星
var startEmpty = 5 - startNumber - startHalf;
for (var i = 0; i < startNumber; i++) {
startList.add(Icon(
Icons.star,
color: Colors.amberAccent,
size: 18,
));
}
if (startHalf > 0) {
startList.add(Icon(
Icons.star_half,
color: Colors.amberAccent,
size: 18,
));
}
for (var i = 0; i < startEmpty; i++) {
startList.add(Icon(
Icons.star_border,
color: Colors.grey,
size: 18,
));
}
startList.add(Text(
'$stars',
style: TextStyle(
color: Colors.grey,
),
));
return Container(
alignment: Alignment.topLeft,
padding: const EdgeInsets.only(left: 0, top: 8, right: 0, bottom: 5),
child: Row(
children: startList,
),
);
}
}
最終效果:
listview點擊事件
點擊事件,使用的是GestureDetector
,這是Flutter中的手勢處理Widget沪哺。
ListView.builder(
//item 的數(shù)量
itemCount: subjects.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(//Flutter 手勢處理
child: Container(
color: Colors.transparent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
numberWidget(index + 1),
getItemContainerView(subjects[index]),
//下面的灰色分割線
Container(
height: 10,
color: Color.fromARGB(255, 234, 233, 234),
)
],
),
),
onTap: () {
//監(jiān)聽點擊事件
print("click item index=$index");
},
);
});
總體來說枯途,上手ListView還是比較簡單的。只不過榴啸,在listView的item的布局排列中晚岭,可能看的不是太懂。如果有不太理解的库说,可以看看我的前面幾篇博客片择。這里用到的屬性字管,布局方式,之前都有講過亡呵。
本文代碼地址
Flutter 豆瓣客戶端硫戈,誠心開源
Flutter Container
Flutter SafeArea
Flutter Row Column MainAxisAlignment Expanded
Flutter Image全解析
Flutter 常用按鈕總結(jié)
Flutter ListView豆瓣電影排行榜
Flutter Card
Flutter Navigator&Router(導航與路由)
OverscrollNotification不起效果引起的Flutter感悟分享
Flutter 上拉抽屜實現(xiàn)
Flutter 豆瓣客戶端,誠心開源
Flutter 更改狀態(tài)欄顏色