How to create a gridview within a listview by flut

2020-06-23 07:36发布

问题:

I'd like to create a shopping cart app

I had a problem

How to create a GridView within a ListView by flutter with JSON API

I want it exactly like this Example :

https://i.stack.imgur.com/2KQFG.png

https://i.stack.imgur.com/I0gY8.gif

--- Update ----

About SliverGrid

I tried to fetch the products but an error appeared (This is regarding the SliverGrid part)

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)".

With regard to ListView (It works fine)..

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:

You can't embed a GridView directly in a ListView unless you play with the height reserved for the GridView. If you want to maintain the scroll for both sections as you show in your images, the best thing is to use a CustomScrollView and Slivers.

After the image is my proposal.

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,
            ),
          ),
        ],
      ),
    );
  }
}

If you want to retrieve the json from the network you can add/replace the following code. Add a method that returns a Future and then build the ListView using a FutureBuilder.

....
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());
                        }
                      }),
                ),
              ),
    ....


回答2:

The simple answer to this would be Tabs . Render Tabs dynamically as per your category and under render GridView in TabView.

Here is Output :

Here is the Code :

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)
        ],
      ),
    );
  }
}

Hope it Helps!