boxy icon indicating copy to clipboard operation
boxy copied to clipboard

Can boxy deal with adjusting listview position when an item changes height?

Open mark8044 opened this issue 3 years ago • 3 comments

Im a boxy noob, but stumbled here in what looks promising

My problem

I have a listview of variable sized items. Inside these variable sized items are images loaded from a CachedNetworkImage. These images load at whatever rate the network allows, which then expands the height of the listview item.

This becomes a problem when the user is in the middle of a listview and an image somewhere above their position loads and it invariably pushes their scroll position down, now they aren't looking at the content they were looking at before

My solution so far

What I've done is basically use a MeasureSize widget

typedef void OnWidgetSizeChange(Size size);

class MeasureSizeRenderObject extends RenderProxyBox {
  Size? oldSize;
  final OnWidgetSizeChange onChange;

  MeasureSizeRenderObject(this.onChange);

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

    Size newSize = child!.size;
    if (oldSize == newSize) return;

    oldSize = newSize;
    WidgetsBinding.instance!.addPostFrameCallback((_) {
      onChange(newSize);
    });
  }
}

class MeasureSize extends SingleChildRenderObjectWidget {
  final OnWidgetSizeChange onChange;

  const MeasureSize({
    Key? key,
    required this.onChange,
    required Widget child,
  }) : super(key: key, child: child);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return MeasureSizeRenderObject(onChange);
  }
}
MeasureSize(
                    onChange: (size){
                      _processItemSizeChange(index: index, size:size);
                    },
               child: (...) /// My list item

Which detects a change in the widget size in then moves the listview _scrollController by the height of the image. _postHeights is a map that stores the last known widget height for each listview item

  void _processItemSizeChange({index, size})
  {
   if (_postHeights[index] == null)
    {
      //first layout so lets put the height of this widget into the map
      _postHeights[index] = size.height;
    }
    else
    {
      //this item has changed its height, so lets deal with that
      debugPrint(index.toString()+' changed height from: '+_postHeights[index].toString() + '  to:'+size.height.toString());
        _scrollController.jumpTo(_scrollController.position.pixels+(size.height - _postHeights[index]!));

        _postHeights[index] = size.height;
      }
  }

The problem with this 'solution'

The problem with this solution as you might guess is there is frame flicker between when the image starts to display (changes the height of the listview item) and when this hacky solution detects the change and does a scrollController.jumpTo

Can boxy help?

So I started using boxy to try to solve this problem

return CustomBoxy(
              delegate: MyDelegate(scrollController: _scrollController),
              children: [
                BoxyId(
                  id:#body,
                  child: (...) ///My list item
class MyDelegate extends BoxyDelegate {
  MyDelegate({required this.scrollController});
  final scrollController;

@override Size layout(){

  final body = getChild(#body);
  body.layout(constraints);
  
  scrollController.jumpTo(10); //Lets try to jump to position 10 just as an initial test if this would even work

  return body.size;

}
}

But I get a million errors trying to manipulate scrollController like that.

Before I go any further, do you think boxy can help with my situation, of trying to get the listview to jump to a new position at the same exact time the image changes the size of the layout without having a screen flicker as this happens?

Im just not even sure this is the intended use of boxy

Thank you!!

mark8044 avatar Feb 25 '22 05:02 mark8044

Hey, sorry for the late response!

Unfortunately, boxy can't circumvent these errors during layout

I think a possible workaround in your case is to do this scroll adjustment outside of the layout phase, i.e. listen to the underlying ImageProvider, enumerate the children of the ListView (via a GlobalKey), determine if an adjustment is necessary using the RenderBox's sizes / positions, and then finally do the adjustment

pingbird avatar Mar 05 '22 03:03 pingbird

Hey, sorry for the late response!

Unfortunately, boxy can't circumvent these errors during layout

I think a possible workaround in your case is to do this scroll adjustment outside of the layout phase, i.e. listen to the underlying ImageProvider, enumerate the children of the ListView (via a GlobalKey), determine if an adjustment is necessary using the RenderBox's sizes / positions, and then finally do the adjustment

Thanks for the concept, this is very helpful. In the way I'm imagining what you are saying, do you think there will be a visible 'glitch' between the time the image provider renders the image and then an adjustment is made using the render box size?

mark8044 avatar May 22 '22 14:05 mark8044

When the ImageProvider completes, it notifies every listener at the same time, so the scroll adjustment should happen on the same frame as the image being painted for the first time

pingbird avatar May 22 '22 16:05 pingbird