1

When I scale a horizontal ListView widget, I observe that only a portion of the list items are visible when the widget is scrolled all the way to the right:

import 'dart:ui';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final sideLength = 50.0;
    final scale = 2.0;

    return MaterialApp(
        scrollBehavior: MyCustomScrollBehavior(),
        home: Scaffold(
            body: Transform.scale(
          scale: scale,
          alignment: Alignment(-1, -1),
          child: Container(
              height: sideLength * scale,
              child: ListView.builder(
                itemCount: 20,
                scrollDirection: Axis.horizontal,
                itemBuilder: (context, index) => Container(
                    width: sideLength,
                    height: sideLength,
                    child: Text(index.toString()),
                    decoration: BoxDecoration(
                        border: Border.all(width: 3, color: Colors.red))),
              )),
        )));
  }
}

class MyCustomScrollBehavior extends MaterialScrollBehavior {
  // Override behavior methods and getters like dragDevices
  @override
  Set<PointerDeviceKind> get dragDevices => {
        PointerDeviceKind.touch,
        PointerDeviceKind.mouse,
      };
}

On the Pixel 2 emulator, only the first 16 items are visible when I scroll to extreme right. For example:

enter image description here

When the scale is 1 or if the Transform.scale widget is not there, all 20 items are visible.

I observe the following behavior:

Total item count Last item scrollable to
8 4
10 6
20 16
30 26
50 46

So it seems like the last 4 items are always left out.

Ultimately my goal is to create a responsive widget that scales according to the dimensions of screen, so I'm looking for a generic solution.

The custom scroll behavior is only there so that horizontal scrolling works on dartpad.dev, as per this answer.

user2233706
  • 6,148
  • 5
  • 44
  • 86

3 Answers3

0

Transform only affects how the child should be painted.

On the other hand, The scale argument multiplies the x and y axes.

For example: Imagine a photographer who is taking a landscape photo that contains a tree, now if the photographer gets closer to the tree, the upper part of the tree will slightly be out of the photo.

Try adding padding or margin to the Container and observe how the widget is affected by the scale. Then you will know how to manipulate it.

body: Transform.scale(
              scale: scale,
              alignment: Alignment(0, -5),
              child: Container(
                  height: sideLength * scale,
                  margin: EdgeInsets.only(left: 100, right: 100),
                  child: ListView.builder(
Mhmd Zawi
  • 167
  • 1
  • 11
  • This requires a lot of guess work. Furthermore, setting the alignment of the transformation and the margin changes where and how the Container is rendered. – user2233706 Sep 20 '21 at 13:13
  • Yes, that is what I meant when I said you will know how to manipulate it. – Mhmd Zawi Sep 20 '21 at 15:12
  • I don't think I can change the alignment and margin. I want my widget to be displayed in a certain way at a particular location, and this solution does not do that. Setting the margin could result in the widget being displayed in the middle of the screen. Ultimately I want to scale a widget and for it behave as any other widget and for the subsequent widgets to respect the scaled dimensions. – user2233706 Sep 20 '21 at 15:22
  • The guesswork can be handled with simple math equations. – Mhmd Zawi Sep 20 '21 at 15:22
  • Can you please specify the desired output? I am interested in your topic. – Mhmd Zawi Sep 20 '21 at 15:25
  • The output should be exactly what is what displayed when you run the program on an emulator or dartpad.dev except that it should be possible to scroll all the way to the 20th list element. I've updated the OP for clarification. – user2233706 Sep 20 '21 at 22:54
0

Give here width and height by MediaQuery

Widget build(BuildContext context) {

return MaterialApp(
    scrollBehavior: MyCustomScrollBehavior(),
    home: Scaffold(
        body: Transform.scale(
      scale: scale,
      alignment: Alignment(-1, -1),
      child: Container(
          height: sideLength * scale,
          child: ListView.builder(
            itemCount: 20,
            scrollDirection: Axis.horizontal,
            itemBuilder: (context, index) => Container(
                width: MediaQuery.of(context).size.width*0.90 ,
                height: MediaQuery.of(context).size.height*0.80 ,
                child: Text(index.toString()),
                decoration: BoxDecoration(
                    border: Border.all(width: 3, color: Colors.red))),
          )),
    )));

}

Dharman
  • 30,962
  • 25
  • 85
  • 135
Vishal_VE
  • 1,852
  • 1
  • 6
  • 9
  • This makes every list item almost equal to the width of the screen. I don't want to change the dimensions of the inner most Container. Furthermore, the last element in the list is only partially shown. This happens regardless of the number of items. – user2233706 Sep 20 '21 at 14:52
0

I worked around this issue by:

  • setting itemCount to a higher value than the desired item count. This allows you to scroll to the last desired item in the ListView
  • having a scroll controller that checks whether you're past the last visible desired item within the viewport. If so, jump to the last possible scroll offset within the viewport
import 'dart:ui';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  final ScrollController _scrollController = ScrollController();
  static const actualTotalItems = 20;
  static const sideLength = 50.0;
  static const scale = 2.0;

  MyApp() {
    _scrollController.addListener(() {
      if (_scrollController.offset > sideLength * actualTotalItems - _scrollController.position.viewportDimension / scale) {
        _scrollController.jumpTo(sideLength * actualTotalItems - _scrollController.position.viewportDimension / scale);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        scrollBehavior: MyCustomScrollBehavior(),
        home: Scaffold(
            body: Container(
                transform: Matrix4.diagonal3Values(scale, scale, 1),
                height: sideLength * scale,
                child: ListView.builder(
                    itemCount: actualTotalItems * 2,
                    scrollDirection: Axis.horizontal,
                    controller: _scrollController,
                    itemBuilder: (context, index) => Container(
                        width: sideLength,
                        height: sideLength,
                        child: Text(index.toString()),
                        decoration: BoxDecoration(border: Border.all(width: 3, color: Colors.red)))))));
  }
}

class MyCustomScrollBehavior extends MaterialScrollBehavior {
  // Override behavior methods and getters like dragDevices
  @override
  Set<PointerDeviceKind> get dragDevices => {
        PointerDeviceKind.touch,
        PointerDeviceKind.mouse,
      };
}

I scaled the widget using the transform property in the Container to have a smaller widget tree, but that has no impact on the solution. The Transform widget could have been used as in the OP.

user2233706
  • 6,148
  • 5
  • 44
  • 86