|
|
@@ -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'), |
|
|
), |
|
|
], |
|
|
); |
|
|
} |
|
|
} |