more_than_wrap 1.0.1 copy "more_than_wrap: ^1.0.1" to clipboard
more_than_wrap: ^1.0.1 copied to clipboard

A Flutter package that provides a custom Wrap widget with limited number of rows and optional overflow widget display.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:more_than_wrap/more_than_wrap.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'LimitedWrap Demo',
      theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
      home: const IntFieldControlWidget(),
    );
  }
}

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

  @override
  State<IntFieldControlWidget> createState() => _IntFieldControlWidgetState();
}

class _IntFieldControlWidgetState extends State<IntFieldControlWidget> {
  late final TextEditingController _maxLinesController;
  late final TextEditingController _itemWidthController;
  late final TextEditingController _itemCountController;

  int? maxLines = 2;
  int itemWidth = 57;
  int itemCount = 13;

  @override
  void initState() {
    super.initState();
    _maxLinesController = TextEditingController(text: '$maxLines');
    _itemWidthController = TextEditingController(text: '$itemWidth');
    _itemCountController = TextEditingController(text: '$itemCount');
  }

  @override
  void dispose() {
    _maxLinesController.dispose();
    _itemWidthController.dispose();
    _itemCountController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('LimitedWrap Example'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Card(
              elevation: 4,
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  children: [
                    Row(
                      children: [
                        Expanded(
                          child: _IntTextField(
                            label: 'Max Lines',
                            controller: _maxLinesController,
                            enabled: maxLines != null,
                            onChanged: (value) =>
                                setState(() => maxLines = value),
                          ),
                        ),
                        const SizedBox(width: 12),
                        Column(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            Text(
                              'Unlimited',
                              style: Theme.of(context).textTheme.labelMedium,
                            ),
                            Switch(
                              value: maxLines == null,
                              onChanged: (unlimited) {
                                setState(() {
                                  if (unlimited) {
                                    maxLines = null;
                                  } else {
                                    maxLines =
                                        int.tryParse(
                                          _maxLinesController.text,
                                        ) ??
                                        2;
                                  }
                                });
                              },
                            ),
                          ],
                        ),
                      ],
                    ),
                    const SizedBox(height: 12),
                    _IntTextField(
                      label: 'Item Width (px)',
                      controller: _itemWidthController,
                      onChanged: (value) => setState(() => itemWidth = value),
                    ),
                    const SizedBox(height: 12),
                    _IntTextField(
                      label: 'Item Count',
                      controller: _itemCountController,
                      onChanged: (value) => setState(() => itemCount = value),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'Visual Result:',
              style: Theme.of(context).textTheme.headlineSmall,
            ),
            const SizedBox(height: 10),
            Expanded(
              child: Container(
                width: double.infinity,
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.grey),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Center(
                  child: LimitedWrap.builder(
                    spacing: 0,
                    runSpacing: 0,
                    maxLines: maxLines,
                    overflowWidgetBuilder: (context, count) => GestureDetector(
                      onTap: () => setState(() => maxLines = null),
                      child: Container(
                        decoration: BoxDecoration(border: Border.all()),
                        padding: const EdgeInsets.symmetric(
                          horizontal: 8,
                          vertical: 4,
                        ),
                        child: Text(
                          '+$count more',
                          style: const TextStyle(
                            fontSize: 14,
                            color: Colors.red,
                          ),
                        ),
                      ),
                    ),
                    itemBuilder: (context, i) => sizedChild(
                      'Item $i',
                      key: ValueKey('item_$i'),
                      width: itemWidth.toDouble(),
                    ),
                    itemCount: itemCount,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget sizedChild(
    String text, {
    Key? key,
    double width = 60,
    double height = 30,
  }) {
    return DecoratedBox(
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(4),
        border: Border.all(color: Colors.grey),
      ),
      child: SizedBox(
        width: width,
        height: height,
        child: Center(child: Text(text, key: key)),
      ),
    );
  }
}

class _IntTextField extends StatelessWidget {
  const _IntTextField({
    required this.label,
    required this.controller,
    required this.onChanged,
    this.enabled = true,
  });

  final String label;
  final TextEditingController controller;
  final ValueChanged<int> onChanged;
  final bool enabled;

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
      enabled: enabled,
      keyboardType: TextInputType.number,
      inputFormatters: [
        FilteringTextInputFormatter.digitsOnly,
        const _PositiveIntFormatter(),
      ],
      decoration: InputDecoration(
        labelText: label,
        border: const OutlineInputBorder(),
        isDense: true,
      ),
      onChanged: (value) {
        if (value.isEmpty) return;
        final parsed = int.tryParse(value);
        if (parsed != null && parsed > 0) onChanged(parsed);
      },
    );
  }
}

class _PositiveIntFormatter extends TextInputFormatter {
  const _PositiveIntFormatter();

  @override
  TextEditingValue formatEditUpdate(
    TextEditingValue oldValue,
    TextEditingValue newValue,
  ) {
    if (newValue.text.isEmpty) return newValue;
    final value = int.tryParse(newValue.text);
    if (value == null || value <= 0) return oldValue;
    return newValue;
  }
}
1
likes
160
points
112
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter package that provides a custom Wrap widget with limited number of rows and optional overflow widget display.

Repository (GitHub)
View/report issues

Topics

#widget #layout #wrap #chips #overflow

License

BSD-3-Clause (license)

Dependencies

flutter

More

Packages that depend on more_than_wrap