Skip to content

Instantly share code, notes, and snippets.

@slightfoot
Forked from austinstoker/main.dart
Last active July 16, 2025 17:19
Show Gist options
  • Select an option

  • Save slightfoot/cac192587f99e84be894e58788e4664f to your computer and use it in GitHub Desktop.

Select an option

Save slightfoot/cac192587f99e84be894e58788e4664f to your computer and use it in GitHub Desktop.

Revisions

  1. slightfoot revised this gist Jul 16, 2025. 1 changed file with 149 additions and 69 deletions.
    218 changes: 149 additions & 69 deletions main.dart
    Original file line number Diff line number Diff line change
    @@ -4,26 +4,45 @@ void main() {
    runApp(const MyApp());
    }

    class MyApp extends StatelessWidget {
    class AppState {
    AppState();

    final zoomScale = ValueNotifier(1.0); // Initial zoom level
    }

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

    static AppState appStateOf(BuildContext context) {
    return context.findAncestorStateOfType<_MyAppState>()!.state;
    }

    @override
    State<MyApp> createState() => _MyAppState();
    }

    class _MyAppState extends State<MyApp> {
    final state = AppState();

    @override
    Widget build(BuildContext context) {
    return MaterialApp(home: const ZoomableReorderableList());
    return ZoomScaleChild(
    child: MaterialApp(
    home: const ZoomableReorderableList(),
    ),
    );
    }
    }

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

    @override
    _ZoomableReorderableListState createState() =>
    _ZoomableReorderableListState();
    State<ZoomableReorderableList> createState() => _ZoomableReorderableListState();
    }

    class _ZoomableReorderableListState extends State<ZoomableReorderableList> {
    final List<String> _items = List.generate(10, (index) => 'Item $index');
    double _zoomScale = 1.0; // Initial zoom level

    @override
    Widget build(BuildContext context) {
    @@ -34,85 +53,146 @@ class _ZoomableReorderableListState extends State<ZoomableReorderableList> {
    IconButton(
    icon: const Icon(Icons.zoom_in),
    onPressed: () {
    setState(() {
    _zoomScale = (_zoomScale + 0.2).clamp(0.5, 2.0);
    });
    final appState = MyApp.appStateOf(context);
    appState.zoomScale.value = //
    (appState.zoomScale.value + 0.2).clamp(
    0.5,
    2.0,
    );
    },
    ),
    IconButton(
    icon: const Icon(Icons.zoom_out),
    onPressed: () {
    setState(() {
    _zoomScale = (_zoomScale - 0.2).clamp(0.5, 2.0);
    });
    final appState = MyApp.appStateOf(context);
    appState.zoomScale.value = //
    (appState.zoomScale.value - 0.2).clamp(
    0.5,
    2.0,
    );
    },
    ),
    ],
    ),
    body: SingleChildScrollView(
    child: Center(
    child: Transform.scale(
    scale: _zoomScale,
    // Align the transform to prevent offset issues
    alignment: Alignment.topCenter,
    child: Container(
    // Constrain width to prevent overflow
    constraints: BoxConstraints(
    maxWidth: MediaQuery.of(context).size.width / _zoomScale,
    ),
    child: ReorderableListView(
    shrinkWrap:
    true, // Ensure the list fits within the scaled container
    physics:
    const NeverScrollableScrollPhysics(), // Disable inner scroll
    onReorder: (oldIndex, newIndex) {
    setState(() {
    if (newIndex > oldIndex) {
    newIndex -= 1;
    }
    final item = _items.removeAt(oldIndex);
    _items.insert(newIndex, item);
    });
    },
    proxyDecorator: (child, index, animation) {
    // Customize the dragged item's appearance
    return AnimatedBuilder(
    animation: animation,
    builder: (context, child) {
    final animValue = Curves.easeInOut.transform(
    animation.value,
    );
    return Transform.scale(
    scale: 1.0, // Keep dragged item at normal scale
    child: Material(
    elevation: 4.0,
    borderRadius: BorderRadius.circular(8),
    shadowColor: Colors.black54,
    child: ClipRRect(
    borderRadius: BorderRadius.circular(8),
    child: child,
    ),
    ),
    );
    },
    child: child,
    );
    },
    children: _items.map((item) {
    return Card(
    key: ValueKey(item), // Unique key for each item
    margin: const EdgeInsets.symmetric(
    vertical: 4,
    horizontal: 8,
    child: ReorderableListView(
    shrinkWrap: true,
    // Ensure the list fits within the scaled container
    physics: const NeverScrollableScrollPhysics(),
    // Disable inner scroll
    onReorder: (oldIndex, newIndex) {
    setState(() {
    if (newIndex > oldIndex) {
    newIndex -= 1;
    }
    final item = _items.removeAt(oldIndex);
    _items.insert(newIndex, item);
    });
    },
    children: _items.map((item) {
    return Card(
    key: ValueKey(item), // Unique key for each item
    margin: const EdgeInsets.symmetric(
    vertical: 4,
    horizontal: 8,
    ),
    child: InkWell(
    onTap: () {
    showDialog(
    context: context,
    builder: (_) => TestDialog(),
    );
    },
    child: Padding(
    padding: const EdgeInsets.symmetric(
    horizontal: 16.0,
    vertical: 12.0,
    ),
    child: ListTile(title: Text(item)),
    );
    }).toList(),
    ),
    ),
    child: Text(item),
    ),
    ),
    );
    }).toList(),
    ),
    ),
    ),
    );
    }
    }

    class ZoomScaleChild extends StatelessWidget {
    const ZoomScaleChild({
    super.key,
    required this.child,
    });

    final Widget child;

    @override
    Widget build(BuildContext context) {
    return ZoomScaleBuilder(
    builder: (BuildContext context, double zoomScale, Widget? child) {
    return MediaQuery(
    data:
    MediaQuery.of(context) //
    .copyWith(textScaler: TextScaler.linear(zoomScale)),
    child: child!,
    );
    // final size = MediaQuery.sizeOf(context);
    // return Align(
    // alignment: Alignment.topCenter,
    // child: Container(
    // constraints: BoxConstraints.tightFor(
    // width: size.width / zoomScale,
    // height: size.height / zoomScale,
    // ),
    // child: Transform.scale(
    // scale: zoomScale,
    // alignment: Alignment.topCenter,
    // child: child,
    // ),
    // ),
    // );
    },
    child: child,
    );
    }
    }

    class ZoomScaleBuilder extends StatelessWidget {
    const ZoomScaleBuilder({
    super.key,
    required this.builder,
    this.child,
    });

    final Widget Function(BuildContext context, double zoomScale, Widget? child) builder;
    final Widget? child;

    @override
    Widget build(BuildContext context) {
    return ValueListenableBuilder(
    valueListenable: MyApp.appStateOf(context).zoomScale,
    builder: builder,
    child: child,
    );
    }
    }

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

    @override
    Widget build(BuildContext context) {
    return AlertDialog(
    content: Text('hello'),
    actions: [
    TextButton(
    onPressed: () => Navigator.of(context).pop(),
    child: Text('OK'),
    ),
    ],
    );
    }
    }
  2. @austinstoker austinstoker created this gist Jul 9, 2025.
    118 changes: 118 additions & 0 deletions main.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,118 @@
    import 'package:flutter/material.dart';

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

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

    @override
    Widget build(BuildContext context) {
    return MaterialApp(home: const ZoomableReorderableList());
    }
    }

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

    @override
    _ZoomableReorderableListState createState() =>
    _ZoomableReorderableListState();
    }

    class _ZoomableReorderableListState extends State<ZoomableReorderableList> {
    final List<String> _items = List.generate(10, (index) => 'Item $index');
    double _zoomScale = 1.0; // Initial zoom level

    @override
    Widget build(BuildContext context) {
    return Scaffold(
    appBar: AppBar(
    title: const Text('Zoomable Reorderable List'),
    actions: [
    IconButton(
    icon: const Icon(Icons.zoom_in),
    onPressed: () {
    setState(() {
    _zoomScale = (_zoomScale + 0.2).clamp(0.5, 2.0);
    });
    },
    ),
    IconButton(
    icon: const Icon(Icons.zoom_out),
    onPressed: () {
    setState(() {
    _zoomScale = (_zoomScale - 0.2).clamp(0.5, 2.0);
    });
    },
    ),
    ],
    ),
    body: SingleChildScrollView(
    child: Center(
    child: Transform.scale(
    scale: _zoomScale,
    // Align the transform to prevent offset issues
    alignment: Alignment.topCenter,
    child: Container(
    // Constrain width to prevent overflow
    constraints: BoxConstraints(
    maxWidth: MediaQuery.of(context).size.width / _zoomScale,
    ),
    child: ReorderableListView(
    shrinkWrap:
    true, // Ensure the list fits within the scaled container
    physics:
    const NeverScrollableScrollPhysics(), // Disable inner scroll
    onReorder: (oldIndex, newIndex) {
    setState(() {
    if (newIndex > oldIndex) {
    newIndex -= 1;
    }
    final item = _items.removeAt(oldIndex);
    _items.insert(newIndex, item);
    });
    },
    proxyDecorator: (child, index, animation) {
    // Customize the dragged item's appearance
    return AnimatedBuilder(
    animation: animation,
    builder: (context, child) {
    final animValue = Curves.easeInOut.transform(
    animation.value,
    );
    return Transform.scale(
    scale: 1.0, // Keep dragged item at normal scale
    child: Material(
    elevation: 4.0,
    borderRadius: BorderRadius.circular(8),
    shadowColor: Colors.black54,
    child: ClipRRect(
    borderRadius: BorderRadius.circular(8),
    child: child,
    ),
    ),
    );
    },
    child: child,
    );
    },
    children: _items.map((item) {
    return Card(
    key: ValueKey(item), // Unique key for each item
    margin: const EdgeInsets.symmetric(
    vertical: 4,
    horizontal: 8,
    ),
    child: ListTile(title: Text(item)),
    );
    }).toList(),
    ),
    ),
    ),
    ),
    ),
    );
    }
    }