Home > database >  How to build an on tap Expandable container
How to build an on tap Expandable container

Time:12-30

So I was trying to build a user id page for my flutter app where you tap on a container and the containers height is increased and a different set of data is shown. On expanded I also wanted to add a scrollable tabview and that's second part of the problem.

the expected ui looks like thishttps://i.stack.imgur.com/62sro.gif.

I have tried Expanded and expansion tile, Can't quite achieve the output Is there any other method to achieve this?

CodePudding user response:

Welcome @Anand Pillai,

First add this line to your pubspec.yaml expandable: ^5.0.1

try this code

import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late PageController _pageController;
  final ExpandableController _controller = ExpandableController();

  int activePage = 1;
  int _counter = 0;
  List<String> images = [
    "https://images.pexels.com/photos/14686142/pexels-photo-14686142.jpeg",
    "https://wallpaperaccess.com/full/2637581.jpg",
    "https://uhdwallpapers.org/uploads/converted/20/01/14/the-mandalorian-5k-1920x1080_477555-mm-90.jpg"
  ];

  List<Widget> indicators(imagesLength, currentIndex) {
    return List<Widget>.generate(imagesLength, (index) {
      return Container(
        margin: const EdgeInsets.all(3),
        width: 10,
        height: 10,
        decoration: BoxDecoration(
            color: currentIndex == index ? Colors.white : Colors.blueGrey,
            shape: BoxShape.circle),
      );
    });
  }

  AnimatedContainer slider(images, pagePosition, active) {
    // double margin = active ? 10 : 20;

    return AnimatedContainer(
      duration: const Duration(milliseconds: 500),
      curve: Curves.easeInOutCubic,
      // margin: EdgeInsets.all(margin),
      decoration: BoxDecoration(
          image: DecorationImage(
        image: NetworkImage(images[pagePosition]),
        fit: BoxFit.cover,
      )),
    );
  }

  @override
  void initState() {
    super.initState();
    _pageController = PageController();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        SizedBox(
          width: MediaQuery.of(context).size.width,
          height: MediaQuery.of(context).size.height,
          child: Stack(
            alignment: Alignment.center,
            children: [imageSlider(), expandedWidget(context)],
          ),
        ),
      ],
    ));
  }

  Positioned expandedWidget(BuildContext context) {
    return Positioned.fill(
        bottom: _controller.expanded ? 0 : 60,
        left: 0,
        right: 0,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            _controller.expanded
                ? const SizedBox.shrink()
                : Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: indicators(images.length, activePage)),
            ExpandableNotifier(
                child: AnimatedContainer(
              height: _controller.expanded ? 400 : 110.0,
              width: double.infinity,
              alignment: Alignment.bottomCenter,
              padding: const EdgeInsets.all(15.0),
              margin: _controller.expanded
                  ? EdgeInsets.zero
                  : const EdgeInsets.all(8.0),
              decoration: BoxDecoration(
                color: const Color.fromARGB(255, 255, 79, 77).withOpacity(0.8),
                borderRadius: _controller.expanded
                    ? const BorderRadius.only(
                        topRight: Radius.circular(15),
                        topLeft: Radius.circular(15),
                      )
                    : BorderRadius.circular(15.0),
              ),
              duration: const Duration(milliseconds: 500),
              child: Column(
                children: <Widget>[
                  ScrollOnExpand(
                    scrollOnExpand: true,
                    scrollOnCollapse: false,
                    child: ExpandablePanel(
                      controller: _controller
                        ..addListener(() {
                          setState(() {});
                        }),
                      theme: const ExpandableThemeData(
                        headerAlignment: ExpandablePanelHeaderAlignment.center,
                        tapBodyToCollapse: true,
                        iconColor: Colors.white,
                      ),
                      header: Padding(
                          padding: const EdgeInsets.all(10),
                          child: Text(
                            "ExpandablePanel",
                            style: Theme.of(context)
                                .textTheme
                                .bodyText1!
                                .copyWith(color: Colors.white),
                          )),
                      collapsed: const Text(
                        "loremIpsum",
                        style: TextStyle(color: Colors.white),
                        softWrap: true,
                        maxLines: 2,
                        overflow: TextOverflow.ellipsis,
                      ),
                      expanded: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          for (var _ in Iterable.generate(5))
                            const Padding(
                                padding: EdgeInsets.only(bottom: 10),
                                child: Text(
                                  "loremIpsum",
                                  style: TextStyle(color: Colors.white),
                                  softWrap: true,
                                  overflow: TextOverflow.fade,
                                )),
                        ],
                      ),
                      builder: (_, collapsed, expanded) {
                        return Padding(
                          padding: const EdgeInsets.only(
                              left: 10, right: 10, bottom: 10),
                          child: Expandable(
                            collapsed: collapsed,
                            expanded: expanded,
                            theme: const ExpandableThemeData(crossFadePoint: 0),
                          ),
                        );
                      },
                    ),
                  ),
                ],
              ),
            )),
          ],
        ));
  }

  PageView imageSlider() {
    return PageView.builder(
        itemCount: images.length,
        physics: _controller.expanded
            ? const NeverScrollableScrollPhysics()
            : ScrollPhysics(),
        padEnds: false,
        controller: _pageController,
        onPageChanged: (page) {
          setState(() {
            activePage = page;
          });
        },
        itemBuilder: (context, pagePosition) {
          bool active = pagePosition == activePage;
          return slider(images, pagePosition, active);
        });
  }
}

CodePudding user response:

class _MyHomePageState extends State<MyHomePage> {
  double _margin = 30, _height = 100, _width = 300;
  
  final Text _widget1 = const Text('This is my Foo');
  final Text _widget2 = const Text('This is Bar');
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
          child: GestureDetector(
        // When the child is tapped, set state is called.
        onTap: () {
          setState(() {
            _margin = _margin == 30 ? 0 : 30;
            _height = _height == 100 ? 300 : 100;
            _width = _width == 300 ? MediaQuery.of(context).size.width : 300;
          });
        },
        // The custom button
        child: Align(
          alignment: Alignment.bottomCenter,
          child: AnimatedContainer(
            width: _width,
            height: _height,
            curve: Curves.easeInExpo,
            margin: EdgeInsets.fromLTRB(_margin, 0, _margin, _margin),
            duration: Duration(milliseconds: 250),
            padding: const EdgeInsets.all(0),
            decoration: BoxDecoration(
              color: Colors.lightBlue,
              borderRadius: BorderRadius.circular(8.0),
            ),
            child: _margin == 30 ? _widget1 : _widget2,
          ),
        ),
      )),
    );
  }
}

Simple logic is to animate the container when tapped and change the widget in it. On tap it calls setsate that sets the height, width, margin and child of the container.

  • Related