Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Help needed on animating with layout. #8

Closed
nazrinharris opened this issue Sep 4, 2021 · 3 comments
Closed

Help needed on animating with layout. #8

nazrinharris opened this issue Sep 4, 2021 · 3 comments
Labels
question A question and not an issue

Comments

@nazrinharris
Copy link

Hi, I need some help on using Boxy.

Basically what I want to make is an expandable card widget. In which it's height can dynamically change depending on the length of the title.

What I basically did is make two widgets. ListCardLayer and ExpansionButtonLayer. The ExpansionButtonLayer's size should depend on the ListCardLayer. And for the most part I got it working. One thing to note (this is what causing my problem) is that the height of the expansion button, should be the same as the ListCard when it is not expanded, and is shortened to 51 px when expanded.

test

The problem that I face is when animating the expansion button. I tried using AnimatedContainer and gave it a height of null when it is not expanded and 51 when it is expanded. But I got this error.

Cannot interpolate between finite constraints and unbounded constraints.

Some advice on this would be greatly appreciated. If you'd want me to give a recording of the error while animating do tell me. (the error only pops up while animating. After the animation is finished, the expansion button is of correct size)

class ListCardBoxyDelegate extends BoxyDelegate {
  final bool isExpanded;

  ListCardBoxyDelegate({
    required this.isExpanded,
  });

  @override
  Size layout() {
    final listCardLayer = getChild(#listCardLayer);
    final expansionButtonLayer = getChild(#expansionButtonLayer);

    final listCardLayerSize = listCardLayer.layout(constraints);
    listCardLayer.position(Offset.zero);

    layoutData = listCardLayerSize;

    expansionButtonLayer.layout(constraints.tighten(
      width: listCardLayerSize.width,
      height: listCardLayerSize.height,
    ));

    return Size(listCardLayerSize.width, listCardLayerSize.height);
  }

  @override
  bool shouldRelayout(ListCardBoxyDelegate old) => true;

  @override
  bool shouldRepaint(ListCardBoxyDelegate old) => true;
}
CustomBoxy(
              delegate: ListCardBoxyDelegate(isExpanded: isExpanded),
              children: [
                BoxyId(
                  id: #listCardLayer,
                  child: listCardLayer,
                ),
                BoxyId(
                  id: #expansionButtonLayer,
                  child: expansionButtonLayer,
                ),
              ],
            ),
class ExpansionButtonLayer extends StatelessWidget {
  final bool isExpanded;
  final VoidCallback onPressed;

  const ExpansionButtonLayer({
    Key? key,
    required this.isExpanded,
    required this.onPressed,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.topRight,
      child: AnimatedContainer(
        duration: 350.milliseconds,
        width: 51,
        //height: isExpanded ? 51 : null,
        margin: EdgeInsets.only(right: 6, top: 6, bottom: 6),
        child: Material(
          borderRadius: BorderRadius.circular(20.0),
          color: kcSecondaryLighterShadeColor,
          child: Ink(
            child: InkWell(
              borderRadius: BorderRadius.circular(20.0),
              onTap: onPressed,
              splashColor: Theme.of(context).primaryColor.withOpacity(0.20),
              highlightColor: Theme.of(context).primaryColor.withOpacity(0.15),
              child: Align(
                child: Icon(Icons.expand_more),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
class ListCardLayer extends StatefulWidget {
  final Function()? onPressed;

  const ListCardLayer({
    Key? key,
    required this.isExpanded,
    required this.onPressed,
  }) : super(key: key);

  final bool isExpanded;

  @override
  _ListCardLayerState createState() => _ListCardLayerState();
}

class _ListCardLayerState extends State<ListCardLayer> with AnimationMixin {
  //late AnimationController animController;
  late Animation<double> view;

  @override
  void initState() {
    super.initState();

    final CurvedAnimation curve = CurvedAnimation(
      parent: controller,
      curve: Curves.fastOutSlowIn,
      reverseCurve: Curves.fastOutSlowIn.flipped,
    );

    view = Tween<double>(begin: 0.0, end: 1.0).animate(curve);

    controller.addListener(() {
      setState(() {});
    });

    widget.isExpanded
        ? controller.play(duration: 350.milliseconds)
        : controller.playReverse(duration: 350.milliseconds);
  }

  @override
  void didUpdateWidget(covariant ListCardLayer oldWidget) {
    super.didUpdateWidget(oldWidget);
    widget.isExpanded
        ? controller.play(duration: 350.milliseconds)
        : controller.playReverse(duration: 350.milliseconds);
  }

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(20),
      child: Material(
        color: Theme.of(context).canvasColor,
        child: Ink(
          child: InkWell(
            splashColor: Theme.of(context).primaryColor.withOpacity(0.20),
            highlightColor: Theme.of(context).primaryColor.withOpacity(0.15),
            onTap: widget.onPressed,
            child: Column(
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Expanded(
                      child: Container(
                        padding: const EdgeInsets.only(
                            left: 24, right: 24, top: 18, bottom: 18),
                        alignment: Alignment.centerLeft,
                        child: Text(
                          allListSGSnip[0].sgName,
                          style: appTextTheme(context)
                              .headline2!
                              .copyWith(fontSize: 20),
                        ),
                      ),
                    ),
                    const SizedBox(width: 51)
                  ],
                ),
                ClipRect(
                  child: Align(
                    heightFactor: view.value,
                    child: Opacity(
                      opacity: view.value,
                      child: Container(
                        margin: EdgeInsets.only(left: 24, bottom: 24),
                        child: Text(
                          allListSGSnip[0].sgDesc,
                          style: appTextTheme(context).bodyText1,
                        ),
                      ),
                    ),
                  ),
                )
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Visual look of an unexpanded and expanded card which I'm trying to make.
Group Page – 2

@nazrinharris
Copy link
Author

nazrinharris commented Sep 4, 2021

This is the error in action.

boxy.animation.issue.mp4

I now understand that this error is due to the fact that I asked AnimatedContainer to interpolate values between 51 and null which is impossible.

It seems that I must know the size of the ListCard after layout and give the height to ExpansionButton. How in the heck do I do that.

@pingbird
Copy link
Owner

pingbird commented Sep 6, 2021

I replicated what I think you are trying to do, the trick was using BoxyRow + LayoutBuilder: https://gist.github.com/PixelToast/7c898c8785145949e874ff15fdaa89fc

amTRS.mp4

@pingbird pingbird added the question A question and not an issue label Sep 6, 2021
@nazrinharris
Copy link
Author

nazrinharris commented Sep 8, 2021

@PixelToast Thank you so so much. It is exactly what I wanted. You're amazing! You saved me from making a full deep dive on RenderObjects, which might have been overkill I guess.

One more thing I would like to ask, since I want to implement a background image and make it expandable how should I go about doing it? Since the image needs to be behind the Material (I wrapped the Column in an Inkwell) I'm guessing I should use Stack but I can't get the size of the widget then. Should I use CustomBoxy?

boxy.expandable.background.image.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question A question and not an issue
Projects
None yet
Development

No branches or pull requests

2 participants