如何使用 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 保留的高度。如果您想保持图像中显示的两个部分的滚动,最好使用CustomScrollViewSlivers

图片之后是我的提议。

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 在列表视图中创建网格视图的主要内容,如果未能解决你的问题,请参考以下文章

滚动列表视图垂直和水平相同的图像

如何动态创建图像的网格视图

Android - 网格视图或列表视图?

如何使用 JSON 正确读取 API 并创建列表?在 Python 中

从网格视图创建不同值的列表

我正在获取图像列表视图,而不是以网格格式显示