简体   繁体   English

Flutter:使列表可滚动

[英]Flutter: Make list scrollable

this is a typical question that might be considered as low quality but I have been on this for about two hours, and I am just trying to understand this piece of code better, so instead of just telling me how to fix, could you please also explain a bit what is happening.这是一个典型的问题,可能被认为是低质量的,但我已经研究了大约两个小时,我只是想更好地理解这段代码,所以不仅仅是告诉我如何修复,你能不能也请解释一下发生了什么。 I am sure that for someone more experienced that me, should be very easy to spot.我相信对于比我更有经验的人来说,应该很容易发现。

I am trying to make a scrollable list, and draw each row of the list, and be able to click in each row item.我正在尝试制作一个可滚动的列表,并绘制列表的每一行,并能够单击每个行项目。 But my app draws all the items but I am only able to see some of the items, as much as the screen allows, which means it is not scrollable.但是我的应用程序绘制了所有项目,但我只能看到一些项目,只要屏幕允许,这意味着它是不可滚动的。

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Some App Page'),
    ),
    body: ListView(
      children: <Widget>[
        Stack(
          alignment: const Alignment(1.0, 1.0),
          children: <Widget>[
            TextField(
              controller: cityController,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(hintText: 'Enter city...'),
            ),
            TextButton(
              onPressed: () {
                cityController.clear();
              },
              child: const Icon(Icons.clear),
            ),
          ],
        ),
        ElevatedButton(
          onPressed: () {
            _futureTime = fetchTimes(int.parse(cityController.text));
            if (cityController.text.isNotEmpty) {
              setState(() {
                cityController.clear(); // Clear value
              }); // clear the textField
              FocusScope.of(context)
                  .requestFocus(FocusNode()); // hide the keyboard
            }
          },
          child: const Text('Get City', style: TextStyle(fontSize: 20)),
        ),
        Column(
          children: <Widget>[
            Center(
              child: FutureBuilder<Times>(
                future: _futureTime,
                builder: (context, snapshot) {
                  if (!snapshot.hasData) {
                    return const CircularProgressIndicator();
                  }
                  return ListView.builder(
                    scrollDirection: Axis.vertical,
                    shrinkWrap: true,
                    itemBuilder: (BuildContext context, int index) {
                      return myTimeCard( date, index);
                    },
                    itemCount: data == null ? 0 : data.length,
                  );
                },
              ),
            ),
          ],
        ),
      ],
    ),
  );
}

Widget myTimeCard(String date, int index) {
  return InkWell(
    onTap: () {
      // Navigate to the next page & pass data.
      print("tapped, -> " + index.toString()); // for debugging purpose!
    },
    child: Stack(
      children: <Widget>[
        Opacity(
          opacity: 1,
          child: Container(
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(8.0),
            ),
          ),
        ),
        Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.only(right: 16.0),
                  child: Text(
                    index.toString(),
                    style: const TextStyle(
                        color: Colors.black,
                        fontSize: 22.0,
                        fontWeight: FontWeight.bold),
                  ),
                ),
              ],
            )
          ],
        )
      ],
    ),
  );
}

You are using two ListView s nested inside each other.您正在使用两个相互嵌套的ListView In such cases you may need to let the Flutter know which ListView is the primary one.在这种情况下,您可能需要让 Flutter 知道哪个 ListView 是主要的。 So, there is a property called primary .因此,有一个名为primary的属性。 Try to set primary to false for the inner Listview.尝试将内部 Listview 的 primary 设置为false

return ListView.builder(
                primary: false,
                scrollDirection: Axis.vertical,
                shrinkWrap: true,
                itemBuilder: (BuildContext context, int index) {
                  return myTimeCard( date, index);
                },
                itemCount: data == null ? 0 : data.length,
              );

The code you shared does not compile because I do not have additional context, so I had to spend some time to be able to make it compile, please make sure to provide a compilable code in the future.您分享的代码无法编译,因为我没有额外的上下文,所以我不得不花费一些时间才能使其编译,请确保将来提供可编译的代码。

the problem you're facing is because the main ListView is taking control of the scroll, to see the effect try scrolling by holding the screen from the button Get City .您面临的问题是因为主ListView正在控制滚动,要查看效果尝试通过按住Get City按钮的屏幕滚动。

There are many ways to solve this problem, depending on your goal, do you want to make the whole screen scrollable, or just the data list解决这个问题的方法有很多,看你的目标,是想让整个屏幕滚动,还是只是数据列表

Way 1. Make the whole screen scrollable: by keeping the control of the scroll in the main ListView , and making all the descending widgets non-scrollable, which in your case, by making the widget that wraps the data a Column instead of ListView :方式 1. 使整个屏幕可滚动:通过将滚动控制保持在主ListView中,并使所有下降的小部件不可滚动,在您的情况下,通过使包装数据的小部件成为Column而不是ListView

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final TextEditingController cityController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Some App Page'),
      ),
      body: ListView(
        children: <Widget>[
          Stack(
            alignment: const Alignment(1.0, 1.0),
            children: <Widget>[
              TextField(
                controller: cityController,
                keyboardType: TextInputType.number,
                decoration: const InputDecoration(hintText: 'Enter city...'),
              ),
              TextButton(
                onPressed: () {
                  cityController.clear();
                },
                child: const Icon(Icons.clear),
              ),
            ],
          ),
          ElevatedButton(
            onPressed: () {
              _futureTime = fetchTimes(int.parse(cityController.text));
              if (cityController.text.isNotEmpty) {
                setState(() {
                  cityController.clear(); // Clear value
                }); // clear the textField
                FocusScope.of(context)
                    .requestFocus(FocusNode()); // hide the keyboard
              }
            },
            child: const Text('Get City', style: TextStyle(fontSize: 20)),
          ),
          Column(
            children: <Widget>[
              Center(
                child: FutureBuilder<Times>(
                  future: _futureTime,
                  builder: (context, snapshot) {
                    // if (!snapshot.hasData) {
                    //   return const CircularProgressIndicator();
                    // }

                    final data =

                        // snapshot.data;
                        List.generate(50, (index) => index.toString());

                    return Column(
                      children: [
                        for (int i = 0; i < data.length; i++)
                          myTimeCard(data[i], i)
                      ],
                    );
                  },
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }

  Widget myTimeCard(String date, int index) {
    return InkWell(
      onTap: () {
        // Navigate to the next page & pass data.
        print("tapped, -> " + index.toString()); // for debugging purpose!
      },
      child: Stack(
        children: <Widget>[
          Opacity(
            opacity: 1,
            child: Container(
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(8.0),
              ),
            ),
          ),
          Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.only(right: 16.0),
                    child: Text(
                      index.toString(),
                      style: const TextStyle(
                          color: Colors.black,
                          fontSize: 22.0,
                          fontWeight: FontWeight.bold),
                    ),
                  ),
                ],
              )
            ],
          )
        ],
      ),
    );
  }
}

Way 2. make the non-data widgets non-scrollable, and keep the scroll control in the data widget: can be done by converting the main ListView to a non-scrollable Widget (in your case Column ), and wrapping the data list in Expanded widget, so it takes all the space it can have (for more info about Expanded) :方式2.使非数据小部件不可滚动,并将滚动控件保留在数据小部件中:可以通过将主ListView转换为不可滚动的小部件(在您的情况下为Column )并将数据列表包装在Expanded的小部件,因此它占用了它可以拥有的所有空间(有关扩展的更多信息)

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final TextEditingController cityController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Some App Page'),
      ),
      body: Column(
        children: <Widget>[
          Stack(
            alignment: const Alignment(1.0, 1.0),
            children: <Widget>[
              TextField(
                controller: cityController,
                keyboardType: TextInputType.number,
                decoration: const InputDecoration(hintText: 'Enter city...'),
              ),
              TextButton(
                onPressed: () {
                  cityController.clear();
                },
                child: const Icon(Icons.clear),
              ),
            ],
          ),
          ElevatedButton(
            onPressed: () {
              _futureTime = fetchTimes(int.parse(cityController.text));
              if (cityController.text.isNotEmpty) {
                setState(() {
                  cityController.clear(); // Clear value
                }); // clear the textField
                FocusScope.of(context)
                    .requestFocus(FocusNode()); // hide the keyboard
              }
            },
            child: const Text('Get City', style: TextStyle(fontSize: 20)),
          ),
          FutureBuilder<Times>(
            future: _futureTime,
            builder: (context, snapshot) {
              // if (!snapshot.hasData) {
              //   return const CircularProgressIndicator();
              // }

              final data =

                  // snapshot.data;
                  List.generate(50, (index) => index.toString());

              return Expanded(
                child: ListView.builder(
                  scrollDirection: Axis.vertical,
                  shrinkWrap: true,
                  itemBuilder: (BuildContext context, int index) {
                    return myTimeCard(date, index);
                  },
                  itemCount: data == null ? 0 : data.length,
                ),
              );
            },
          ),
        ],
      ),
    );
  }

  Widget myTimeCard(String date, int index) {
    return InkWell(
      onTap: () {
        // Navigate to the next page & pass data.
        print("tapped, -> " + index.toString()); // for debugging purpose!
      },
      child: Stack(
        children: <Widget>[
          Opacity(
            opacity: 1,
            child: Container(
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(8.0),
              ),
            ),
          ),
          Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.only(right: 16.0),
                    child: Text(
                      index.toString(),
                      style: const TextStyle(
                          color: Colors.black,
                          fontSize: 22.0,
                          fontWeight: FontWeight.bold),
                    ),
                  ),
                ],
              )
            ],
          )
        ],
      ),
    );
  }
}

The issue is coming because we have two scrollable ListView .问题来了,因为我们有两个可滚动的ListView While both of them are scrollable, while scrolling when the inner ListView it gets focused and parent become unfocus and scroll event only effect on inner ListView and you can't rollback to parent ListView , A simple solution will be using NeverScrollableScrollPhysics on inner ListView.builder .虽然它们都是可滚动的,但当内部ListView获得焦点并且父级变得不聚焦并且滚动事件仅影响内部ListView并且您不能回滚到父级ListView时滚动时,一个简单的解决方案将在内部ListView.builder上使用NeverScrollableScrollPhysics .

child: ListView.builder(
  physics: NeverScrollableScrollPhysics(),
  scrollDirection: Axis.vertical,
singleChildScrollView(
child: ListView.builder(
   sinkwrap:true,
  physics: NeverScrollableScrollPhysics(),
  scrollDirection: Axis.vertical,)
)

Simple and Easy简单易行

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM