How to expand a card on tap in flutter?

前端 未结 2 2144
失恋的感觉
失恋的感觉 2021-02-08 22:56

I would like to achieve the material design card behavior on tap. When I tap it, it should expand fullscreen and reveal additional content/new page. How do I achieve it?

2条回答
  •  佛祖请我去吃肉
    2021-02-08 23:46

    I achieved this by using the Flutter Hero Animation Widget. In order to do that you will need:

    1. A source page where you start from and that contains the card you want to expand to full screen. Let's call it 'Home'
    2. A destination page that will represent how your card will look like once expanded. Let's call it 'Details'.
    3. (Optional) A data model to store data

    Now let's take a look at this example below (You can find the full project code here):

    First, let's make an Item class (i will put it in models/item.dart) to store our data. Each item will have its own id, title, subtitle, details and image url :

    import 'package:flutter/material.dart';
    
    class Item {
      String title, subTitle, details, img;
      int id;
    
      Item({this.id, this.title, this.subTitle, this.details, this.img});
    }
    

    Now, let's initialize our material app in the main.dart file :

    import 'package:flutter/material.dart';
    
    import 'package:expanding_card_animation/home.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          home: Home(),
        );
      }
    }
    

    Next, we will make our home page. It'll be a simple stateless widget, and will contain a list of Items that will be displayed in a ListView of Cards. A gesture detector is used to expand the card when tapping it. The expansion is just a navigation to the details page, but with the Hero animation, it looks like it just expanded the Card.

    import 'package:flutter/material.dart';
    
    import 'package:expanding_card_animation/details.dart';
    import 'package:expanding_card_animation/models/item.dart';
    
    class Home extends StatelessWidget {
      List listItems = [
        Item(
            id: 1,
            title: 'Title 1',
            subTitle: 'SubTitle 1',
            details: 'Details 1',
            img:
                'https://d1fmx1rbmqrxrr.cloudfront.net/cnet/i/edit/2019/04/eso1644bsmall.jpg'),
        Item(
            id: 2,
            title: 'Title 2',
            subTitle: 'SubTitle 2',
            details: 'Details 2',
            img:
                'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__340.jpg'),
        Item(
            id: 3,
            title: 'Title 3',
            subTitle: 'SubTitle 3',
            details: 'Details 3',
            img: 'https://miro.medium.com/max/1200/1*mk1-6aYaf_Bes1E3Imhc0A.jpeg'),
      ];
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Home screen'),
          ),
          body: Container(
            margin: EdgeInsets.fromLTRB(40, 10, 40, 0),
            child: ListView.builder(
                itemCount: listItems.length,
                itemBuilder: (BuildContext c, int index) {
                  return GestureDetector(
                    onTap: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                            builder: (context) => Details(listItems[index])),
                      );
                    },
                    child: Card(
                      elevation: 7,
                      shape: RoundedRectangleBorder(
                        side: BorderSide(color: Colors.grey[400], width: 1.0),
                        borderRadius: BorderRadius.circular(10.0),
                      ),
                      margin: EdgeInsets.fromLTRB(0, 0, 0, 20),
                      child: Column(
                        children: [
                          //Wrap the image widget inside a Hero widget
                          Hero(
                            //The tag must be unique for each element, so we used an id attribute
                            //in the item object for that
                            tag: '${listItems[index].id}',
                            child: Image.network(
                              "${listItems[index].img}",
                              scale: 1.0,
                              repeat: ImageRepeat.noRepeat,
                              fit: BoxFit.fill,
                              height: 250,
                            ),
                          ),
                          Divider(
                            height: 10,
                          ),
                          Text(
                            listItems[index].title,
                            style: TextStyle(
                              fontSize: 20,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          SizedBox(
                            height: 20,
                          ),
                        ],
                      ),
                    ),
                  );
                }),
          ),
        );
      }
    }
    

    Finally, let's make the details page. It's also a simple stateless widget that will take the item's info as an input, and display them on full screen. Note that we wrapped the image widget inside another Hero widget, and make sure that you use the same tags used in the source page(here, we used the id in the passed item for that) :

    import 'package:flutter/material.dart';
    
    import 'package:expanding_card_animation/models/item.dart';
    
    class Details extends StatelessWidget {
      final Item item;
    
      Details(this.item);
    
      @override
      Widget build(BuildContext context) {
        return SafeArea(
          child: Scaffold(
            appBar: AppBar(
              backgroundColor: Colors.transparent,
              elevation: 0,
            ),
            extendBodyBehindAppBar: true,
            body: Container(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Hero(
                    //Make sure you have the same id associated to each element in the
                    //source page's list
                    tag: '${item.id}',
                    child: Image.network(
                      "${item.img}",
                      scale: 1.0,
                      repeat: ImageRepeat.noRepeat,
                      fit: BoxFit.fitWidth,
                      height: MediaQuery.of(context).size.height / 3,
                    ),
                  ),
                  SizedBox(
                    height: 30,
                  ),
                  ListTile(
                    title: Text(
                      item.title,
                      style: TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 20,
                      ),
                    ),
                    subtitle: Text(item.subTitle),
                  ),
                  Divider(
                    height: 20,
                    thickness: 1,
                  ),
                  Padding(
                    padding: EdgeInsets.only(left: 20),
                    child: Text(
                      item.details,
                      style: TextStyle(
                        fontSize: 25,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    

    And that's it, now you can customize it as you wish. Hope i helped.

提交回复
热议问题