如何使用 json API 在列表视图中创建网格视图
Posted
技术标签:
【中文标题】如何使用 json API 在列表视图中创建网格视图【英文标题】:How to create a gridview within a listview by flutter with json API 【发布时间】:2019-02-28 04:57:55 【问题描述】:我想创建一个购物车应用
我有问题
如何使用 JSON API 在 ListView 中创建 GridView
我希望它完全像这个例子:
https://i.stack.imgur.com/2KQFG.png
https://i.stack.imgur.com/I0gY8.gif
---更新----
关于 SliverGrid
我尝试获取产品但出现错误(这是关于 SliverGrid 部分)
I/flutter ( 5992): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 5992): The following assertion was thrown building FutureBuilder<List<dynamic>>(dirty, state:
I/flutter ( 5992): _FutureBuilderState<List<dynamic>>#78747):
I/flutter ( 5992): A build function returned null.
I/flutter ( 5992): The offending widget is: FutureBuilder<List<dynamic>>
I/flutter ( 5992): Build functions must never return null. To return an empty space that causes the building widget to
I/flutter ( 5992): fill available room, return "new Container()". To return an empty space that takes as little room as
I/flutter ( 5992): possible, return "new Container(width: 0.0, height: 0.0)".
关于 ListView(它工作正常)..
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
Future<List<dynamic>> getCategoriesApi() async
http.Response response1 =
await http.get("http://159.89.228.206/");
Map<String, dynamic> decodedCategories = json.decode(response1.body);
//print(response1);
return decodedCategories['categories'];
Future<List<dynamic>> getProductsApi() async
http.Response response =
await http.get("http://159.89.228.206/");
Map<String, dynamic> decodedCategories2 = json.decode(response.body);
// print(response);
return decodedCategories2['last'];
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
return new MaterialApp(
home: new MyHomePage(title: 'Sliver Demo'),
);
class MyHomePage extends StatefulWidget
MyHomePage(Key key, this.title) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
class _MyHomePageState extends State<MyHomePage>
final ScrollController _scrollController = new ScrollController();
List<dynamic> products;
List<dynamic> categories;
@override
Widget build(BuildContext context)
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: Column(children: <Widget>[
Expanded(
child: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverToBoxAdapter(
child: SizedBox(
height: 120.0,
child: FutureBuilder(
future: getCategoriesApi(),
builder: (BuildContext context,
AsyncSnapshot<List<dynamic>> snapshot)
if (snapshot.connectionState ==
ConnectionState.done)
return ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (context, index)
Map<String, String> category =
snapshot.data[index].cast<String, String>();
return Card(
child: Container(
height: double.infinity,
color: Colors.grey[200],
child: Center(
child: Padding(
padding: EdgeInsets.all(30.0),
child: Text(category["name"]),
),
),
),
);
,
itemCount: snapshot.data.length,
);
else
return Center(child: CircularProgressIndicator());
),
),
),
SliverToBoxAdapter(
child: Container(
child: FutureBuilder(
future: getProductsApi(),
builder: (BuildContext context,
AsyncSnapshot<List<dynamic>> snapshot)
if (snapshot.connectionState ==
ConnectionState.done)
SliverGrid(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.8,
),
delegate: SliverChildBuilderDelegate(
(context, index)
Map<String, String> product = snapshot
.data[index]
.cast<String, String>();
return Card(
child: Container(
height: double.infinity,
color: Colors.grey[200],
child: Center(
child: Padding(
padding: EdgeInsets.all(30.0),
child: Text(product["name"]),
),
),
),
);
,
childCount: snapshot.data.length,
),
);
else
return Center(child: CircularProgressIndicator());
),
),
),
],
),
)
]));
【问题讨论】:
【参考方案1】:您不能将GridView
直接嵌入到ListView
中,除非您使用为GridView
保留的高度。如果您想保持图像中显示的两个部分的滚动,最好使用CustomScrollView
和Slivers
。
图片之后是我的提议。
import 'dart:convert';
import 'package:flutter/material.dart';
String productsJson =
'"last": ["product_id":"62","thumb":"sandwich.png","name":"Test Tilte","price":"\$55.00", '
'"product_id":"61","thumb":"sandwich.png","name":"Test Tilte","price":"\$55.00", '
'"product_id":"57","thumb":"sandwich.png","name":"Test Tilte","price":"\$55.00", '
'"product_id":"63","thumb":"sandwich.png","name":"Test Tilte","price":"\$55.00", '
'"product_id":"64","thumb":"sandwich.png","name":"Test Tilte","price":"\$55.00", '
'"product_id":"58","thumb":"sandwich.png","name":"Test Tilte","price":"\$55.00", '
'"product_id":"59","thumb":"sandwich.png","name":"Test Tilte","price":"\$55.00"]';
String categoriesJson = '"categories":['
'"name":"Category 1","image":"icon.png","id":2, '
'"name":"Category 2","image":"icon.png","id":4, '
'"name":"Category 3","image":"icon.png","id":4, '
'"name":"Category 4","image":"icon.png","id":4, '
'"name":"Category 5","image":"icon.png","id":6]';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
home: MyHomePage(title: 'Sliver Demo'),
);
class MyHomePage extends StatefulWidget
MyHomePage(Key key, this.title) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
class _MyHomePageState extends State<MyHomePage>
final ScrollController _scrollController = ScrollController();
List<dynamic> products;
List<dynamic> categories;
@override
initState()
super.initState();
Map<String, dynamic> decoded = json.decode(productsJson);
products = decoded['last'];
Map<String, dynamic> decodedCategories = json.decode(categoriesJson);
categories = decodedCategories['categories'];
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverToBoxAdapter(
child: SizedBox(
height: 120.0,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (context, index)
Map<String, String> category =
categories[index].cast<String, String>();
return Card(
child: Container(
height: double.infinity,
color: Colors.grey[200],
child: Center(
child: Padding(
padding: EdgeInsets.all(30.0),
child: Text(category["name"]),
),
),
),
);
,
itemCount: categories.length,
),
),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.8,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index)
Map<String, String> product =
products[index].cast<String, String>();
return Card(
child: Container(
color: Colors.grey[400],
child: Padding(
padding: EdgeInsets.symmetric(vertical: 30.0),
child: Center(
child: Text("Product $product["product_id"]")),
),
),
);
,
childCount: products.length,
),
),
],
),
);
如果您想从网络中检索 json,您可以添加/替换以下代码。添加一个返回Future
的方法,然后使用FutureBuilder
构建ListView
。
....
import 'package:http/http.dart' as http;
import 'dart:async';
....
Future<List<dynamic>> getCategories() async
http.Response response = await http.get("http://159.89.228.206");
Map<String, dynamic> decodedCategories = json.decode(response.body);
return decodedCategories['categories'];
...
...
SliverToBoxAdapter(
child: SizedBox(
height: 120.0,
child: FutureBuilder(
future: getCategories(),
builder: (BuildContext context, AsyncSnapshot<List<dynamic>> snapshot)
if (snapshot.connectionState == ConnectionState.done)
return ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (context, index)
Map<String, String> category =
snapshot.data[index].cast<String, String>();
return Card(
child: Container(
height: double.infinity,
color: Colors.grey[200],
child: Center(
child: Padding(
padding: EdgeInsets.all(30.0),
child: Text(category["name"]),
),
),
),
);
,
itemCount: snapshot.data.length,
);
else
return Center(child: CircularProgressIndicator());
),
),
),
....
【讨论】:
你能解释一下它们是如何从 http url 带来的 使用这个 api :159.89.228.206 非常感谢 :) 我添加了一些代码来从网络检索到的 json 构建类别列表。 谢谢 :) .. 问问你为什么第一次使用 initState .. 在这段代码中使用 initState 有什么好处... initState 用于在创建 State 对象后立即执行操作。在某种程度上就像在构造函数中进行初始化,但在这里它更安全,因为您现在对象已完全创建。对于网络部分,它没有在 initState 中初始化,因为未来是在构建方法中调用的。但是,如果您在 FutureBuilder 小部件中挖掘一点,您会发现 Future 在 FutureBuilder 自己的 initState 方法中被监听。 首先再次感谢您,关于SliverGrid 我尝试获取产品但出现错误(注意:问题已在顶部修改)我添加了完整的代码去请上一页..【参考方案2】:这个问题的简单答案是 Tabs 。 根据您的类别动态渲染选项卡,并在 TabView 中渲染 GridView。
这是输出:
这是代码:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
title: 'Shopping App',
theme: ThemeData(
primarySwatch: Colors.orange,
),
home: ShowProductScreen(),
);
class Product
String productId;
String image;
String name;
String price;
Product(this.productId, this.image, this.name, this.price);
class Category
String id;
String name;
String image;
List<Product> productList;
Category(this.id, this.name, this.image, this.productList);
class ShowProductScreen extends StatefulWidget
@override
_ShowProductScreenState createState() => _ShowProductScreenState();
class _ShowProductScreenState extends State<ShowProductScreen> with TickerProviderStateMixin
List<Category> categoryList = List();
TabController _tabController;
@override
void initState()
super.initState();
//Add data
for (int i = 0; i < 10; i++)
List<Product> productList = List();
for (int j = 0; j < 50; j++)
Product product = Product(
productId: "$i-$j",
price: "$(j + 1) * 10",
name: "Product $i-$j",
image: "assets/image.jpg",
);
productList.add(product);
Category category = Category(
id: "$i",
name: "Category $i",
image: "assets/image.jpg",
productList: productList,
);
categoryList.add(category);
_tabController = TabController(vsync: this, length: categoryList.length);
@override
void dispose()
super.dispose();
_tabController.dispose();
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
titleSpacing: 0.0,
title: IconButton(
icon: Icon(
Icons.shopping_cart,
),
onPressed: () ,
),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.menu,
),
onPressed: () ,
)
],
),
body: Column(
children: <Widget>[
Container(
height: 100.0,
child: TabBar(
controller: _tabController,
isScrollable: true,
tabs: categoryList.map((Category category)
return CategoryWidget(
category: category,
);
).toList(),
),
),
Expanded(
child: Container(
padding: EdgeInsets.all(5.0),
child: TabBarView(
controller: _tabController,
children: categoryList.map((Category category)
return Container(
child: GridView.count(
crossAxisCount: 2,
childAspectRatio: 4 / 3,
controller: ScrollController(keepScrollOffset: false),
scrollDirection: Axis.vertical,
children: category.productList.map(
(Product product)
return ProductWidget(
product: product,
);
,
).toList(),
),
);
).toList(),
),
),
)
],
),
);
class CategoryWidget extends StatelessWidget
final Category category;
const CategoryWidget(Key key, this.category) : super(key: key);
@override
Widget build(BuildContext context)
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(4.0),
child: Image(
image: AssetImage(category.image),
height: 60.0,
),
),
Text(category.name)
],
),
);
class ProductWidget extends StatelessWidget
final Product product;
const ProductWidget(Key key, this.product) : super(key: key);
@override
Widget build(BuildContext context)
return Container(
margin: EdgeInsets.all(4.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(8.0),
),
border: Border.all(
color: Colors.orange,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image(
image: AssetImage(product.image),
fit: BoxFit.contain,
height: 80.0,
),
Text(product.name)
],
),
);
希望对你有帮助!
【讨论】:
我和你的想法一样im5.ezgif.com/tmp/ezgif-5-3dd5b743dd.webp我想要一些不同的东西,请看我需要的图片:i.stack.imgur.com/I0gY8.gif 注意:你的想法很好..我会用在别的东西上..但是怎么用json调用呢? "categories":[ "name":"Category 1","image":"icon.png","id":2, "name":"Category 3","image":"icon. png","id":4, "name":"Category 5","image":"icon.png","id":6 ]以上是关于如何使用 json API 在列表视图中创建网格视图的主要内容,如果未能解决你的问题,请参考以下文章