Flutter:如何使用一个ReorderableListView在一个Scrollbar中嵌套多个连续的ListView?

问题描述 投票:0回答:1

我正在测试一个简单的场景:有很多帖子类别,每个类别都有自己的帖子。

我希望能够拖放并重新排序类别,以及特定类别中的帖子。

我想在单个滚动条内显示完全扩展的所有列表。

是的,看起来我需要一个用于所有类别的可重新排序列表视图,以便我可以重新排序它们,并且对于每个类别,另一个可重新排序列表视图用于对该类别中的帖子进行排序。 PS:通过拖动将帖子移动到另一个类别也很棒

我将在下面提供一些我正在尝试但没有成功的代码:

import 'dart:math';

import 'package:flutter/material.dart';

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

  @override
  State<TmpScreen> createState() => _TmpScreenState();
}

class _TmpScreenState extends State<TmpScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: const Row(
        children: [
          Text('Posts'),
        ],
      )),
      body: SingleChildScrollView(
        child: Container(
            height: MediaQuery.of(context).size.height,
            padding: const EdgeInsets.all(16),
            child: Column(
              children: [
                Text('All posts'),
                const SizedBox(
                  height: 36,
                ),
                Expanded(
                  child: ReorderableListView(
                      onReorder: (oldIndex, newIndex) {
                        print(
                            'Reordering post categories $oldIndex -> $newIndex');
                      },
                      children: List.generate(postsByCategory.length, (index) {
                        final postByCategory = postsByCategory[index];
                        return Column(
                          key: ValueKey(postByCategory.category),
                          children: [
                            Text(postByCategory.category),
                            Expanded(
                                child: ReorderableListView(
                                    onReorder: (oldIndex, newIndex) {
                                      print(
                                          'Reordering posts inside category ${postByCategory.category}, $oldIndex -> $newIndex');
                                    },
                                    children: List.generate(
                                        postByCategory.posts.length, (index) {
                                      final post = postByCategory.posts[index];
                                      return Text(
                                          key: ValueKey(post.name),
                                          'Post ${post.name}');
                                    })))
                          ],
                        );
                      })),
                )
              ],
            )),
      ),
    );
  }
}

final postsByCategory =
    List.generate(10, (index) => generateRandomPostsByCategory(10));

class PostsByCategory {
  final String category;
  final List<Post> posts;

  PostsByCategory({required this.category, required this.posts});
}

class Post {
  final String name;

  Post(this.name);
}

String generateRandomString(int length) {
  const String chars =
      'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  Random random = Random();
  return String.fromCharCodes(
    Iterable.generate(
      length,
      (_) => chars.codeUnitAt(random.nextInt(chars.length)),
    ),
  );
}

Post generateRandomPost() {
  String randomPostName = generateRandomString(10);
  return Post(randomPostName);
}

PostsByCategory generateRandomPostsByCategory(int numPosts) {
  String randomCategory =
      generateRandomString(8); // Generate a random category name

  List<Post> randomPosts = List.generate(numPosts, (_) => generateRandomPost());

  return PostsByCategory(category: randomCategory, posts: randomPosts);
}

上面的代码根本不渲染。 我可以修复它以进行渲染,但它不起作用,列表没有显示所有项目,而且我不知道如何支持我的用例。

任何 Flutter 专家都可以提供有用的见解吗?

flutter
1个回答
0
投票

当嵌套像

ReorderableListView
这样的可滚动小部件时,特别是具有相同的滚动方向时,您经常会遇到布局约束问题。发生这种情况是因为 Flutter 无法推断嵌套列表的正确高度/宽度,从而导致渲染问题。

要解决此问题,您需要手动设置每个类别的

ReorderableListView
的高度。高度应考虑类别的标签高度和该类别内帖子的总高度。

此外,最好通过将

physics
设置为
NeverScrollableScrollPhysics
来禁用每个类别中帖子的滚动。

这是代码的简化和更正版本:

import 'package:flutter/material.dart';

void main() => runApp(const MainApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('All Posts'),
        ),
        body: const PostsView(),
      ),
    );
  }
}

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

  @override
  State<PostsView> createState() => _PostsViewState();
}

class _PostsViewState extends State<PostsView> {
  final _categories = [
    PostsByCategory(
      category: 'Category 1',
      posts: [Post('Post 1'), Post('Post 2'), Post('Post 3')],
    ),
    PostsByCategory(
      category: 'Category 2',
      posts: [Post('Post 4'), Post('Post 5'), Post('Post 6')],
    ),
    PostsByCategory(
      category: 'Category 3',
      posts: [Post('Post 7'), Post('Post 8'), Post('Post 9')],
    ),
  ];

  @override
  Widget build(BuildContext context) {
    const postHeight = 56.0; // Set a fixed height for each post
    const labelHeight = 48.0; // Set a fixed height for each category label

    return ReorderableListView.builder(
      primary: true,
      padding: const EdgeInsets.all(16),
      onReorder: (oldIndex, newIndex) {
        if (newIndex > oldIndex) newIndex -= 1;

        final category = _categories.removeAt(oldIndex);
        _categories.insert(newIndex, category);
      },
      itemCount: _categories.length,
      itemBuilder: (_, index) {
        final category = _categories[index];

        return SizedBox(
          key: ValueKey(category.category),
          height: (category.posts.length * postHeight) + labelHeight, // Dynamically calculate height based on the number of posts
          child: Column(
            children: [
              SizedBox(
                height: labelHeight, // Fixed height for label
                child: Center(
                  child: Text(category.category),
                ),
              ),
              Expanded(
                child: ReorderableListView.builder(
                  physics: const NeverScrollableScrollPhysics(), // Disable scrolling for the nested list
                  onReorder: (oldIndex, newIndex) {
                    if (newIndex > oldIndex) newIndex -= 1;

                    final post = category.posts.removeAt(oldIndex);
                    category.posts.insert(newIndex, post);
                  },
                  itemCount: category.posts.length,
                  itemBuilder: (_, index) {
                    final post = category.posts[index];

                    return SizedBox(
                      key: ValueKey(post.name),
                      height: postHeight, // Fixed height for each post
                      child: Center(
                        child: Text(post.name),
                      ),
                    );
                  },
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

class PostsByCategory {
  final String category;
  final List<Post> posts;

  PostsByCategory({required this.category, required this.posts});
}

class Post {
  final String name;

  Post(this.name);
}

Example GIF

© www.soinside.com 2019 - 2024. All rights reserved.