Flutter: How to use one ReorderableListView nesting multiple consecutive ones inside one Scrollbar?

  Kiến thức lập trình

I am testing a simple scenario: Having many post categories, where each category has its own posts.

I want to be able to drag&drop and reorder the categories, but also the posts within a specific category.

I want to show fully extended all the lists inside a single scrollbar.

Yes it looks like I need one reorderablelistview for all the categories so I can reorder them, and for every category another reorderablelistview for ordering the posts within that category.
PS: Moving posts to another category by dragging would be awesome too

I will provide below some code that I am trying without success:

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);
}

The code above does not render at all.
I can fix it to render but it just doesn’t work, the lists don’t show all the items and I don’t know how to support my use case.

Any Flutter expert can provide useful insights?

When nesting scrollable widgets like ReorderableListView, especially with the same scroll direction, you often encounter layout constraint issues. This happens because Flutter cannot infer the correct height/width of the nested lists, leading to rendering problems.

To resolve this, you need to manually set the height of each category’s ReorderableListView. The height should account for both the label height of the category and the total height of the posts within that category.

Also, it’s a good idea to disable scrolling for the posts within each category by setting physics to NeverScrollableScrollPhysics.

Here’s a simplified and corrected version of your code:

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);
}

Theme wordpress giá rẻ Theme wordpress giá rẻ Thiết kế website

LEAVE A COMMENT