diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 8e30808..047d7ce 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 30 + compileSdkVersion 31 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/example/android/build.gradle b/example/android/build.gradle index c11f0f8..2f16470 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.6.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:7.1.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 571984a..7ecc583 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip diff --git a/example/lib/custom_separator.dart b/example/lib/custom_separator.dart new file mode 100644 index 0000000..5d42ecf --- /dev/null +++ b/example/lib/custom_separator.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:resizable_widget/resizable_widget.dart'; + +class DefaultSeparatorWidget extends StatelessWidget { + final SeparatorArgsInfo info; + final SeparatorController controller; + + const DefaultSeparatorWidget({ + Key? key, + required this.info, + required this.controller, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Stack(children: [ + const Center(child: Text("Default behavior with custom widget")), + DefaultSeparator(info: info, controller: controller), + ]); + } +} + +class CustomSeparatorWidget extends StatefulWidget { + final SeparatorArgsInfo info; + final SeparatorController controller; + + const CustomSeparatorWidget({ + Key? key, + required this.info, + required this.controller, + }) : super(key: key); + + @override + State createState() => _SeparatorWidgetState(); +} + +class _SeparatorWidgetState extends State { + refresh() => setState(() {}); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: widget.info.size, + child: Row(children: [ + Expanded( + child: Container( + height: widget.info.size, + color: Colors.black, + child: const Center(child: Text("Non draggable area")), + )), + Expanded( + child: Container( + height: widget.info.size, + color: Colors.grey, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + child: const Center(child: Text("Draggable area")), + onPanDown: (details) => + widget.controller.onPanStart(details), + onPanUpdate: (details) => + widget.controller.onPanUpdate(details, context), + onPanEnd: (details) => widget.controller.onPanEnd(details), + ))) + ])); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index 3400ddb..e298384 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:resizable_widget/resizable_widget.dart'; +import 'custom_separator.dart'; + void main() { runApp(const MyApp()); } @@ -11,55 +13,116 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Resizable Widget Example', - theme: ThemeData.dark(), - home: const MyPage(), + title: 'Resizable Widget Example', + theme: ThemeData.dark(), + home: DefaultTabController( + length: 3, + child: Scaffold( + appBar: AppBar( + title: const Text("Resizable Widget Example"), + bottom: const TabBar(tabs: [ + Tab(text: "Simple"), + Tab(text: "Default Separator"), + Tab(text: "Custom Separator"), + ])), + body: const TabBarView( + physics: NeverScrollableScrollPhysics(), + children: [ + SimpleExamplePage(), + DefaultSeparatorExamplePage(), + CustomSeparatorExamplePage(), + ])))); + } +} + +class SimpleExamplePage extends StatelessWidget { + const SimpleExamplePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ResizableWidget( + isHorizontalSeparator: false, + isDisabledSmartHide: false, + separatorColor: Colors.white12, + separatorSize: 4, + onResized: _printResizeInfo, + children: [ + Container(color: Colors.greenAccent), + ResizableWidget( + isHorizontalSeparator: true, + separatorColor: Colors.blue, + separatorSize: 10, + children: [ + Container(color: Colors.greenAccent), + ResizableWidget( + children: [ + Container(color: Colors.greenAccent), + Container(color: Colors.yellowAccent), + Container(color: Colors.redAccent), + ], + percentages: const [0.2, 0.5, 0.3], + ), + Container(color: Colors.redAccent), + ], + ), + Container(color: Colors.redAccent), + ], ); } + + _printResizeInfo(List dataList) { + debugPrint(dataList.map((x) => '(${x.size}, ${x.percentage}%)').join(", ")); + } } -class MyPage extends StatelessWidget { - const MyPage({Key? key}) : super(key: key); +class DefaultSeparatorExamplePage extends StatelessWidget { + const DefaultSeparatorExamplePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Resizable Widget Example'), + return ResizableWidget( + isHorizontalSeparator: true, + separatorSize: 16, + separatorBuilder: (info, controller) => DefaultSeparatorWidget( + info: info, + controller: controller, ), - body: ResizableWidget( - isHorizontalSeparator: false, - isDisabledSmartHide: false, - separatorColor: Colors.white12, - separatorSize: 4, - onResized: _printResizeInfo, - children: [ - Container(color: Colors.greenAccent), - ResizableWidget( - isHorizontalSeparator: true, - separatorColor: Colors.blue, - separatorSize: 10, - children: [ - Container(color: Colors.greenAccent), - ResizableWidget( - children: [ - Container(color: Colors.greenAccent), - Container(color: Colors.yellowAccent), - Container(color: Colors.redAccent), - ], - percentages: const [0.2, 0.5, 0.3], - ), - Container(color: Colors.redAccent), - ], - ), - Container(color: Colors.redAccent), - ], + onResized: _printResizeInfo, + children: [ + Container(color: Colors.greenAccent), + Container(color: Colors.redAccent), + Container(color: Colors.blueAccent), + ], + ); + } + + _printResizeInfo(List dataList) { + debugPrint(dataList.map((x) => '(${x.size}, ${x.percentage}%)').join(", ")); + } +} + +class CustomSeparatorExamplePage extends StatelessWidget { + const CustomSeparatorExamplePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ResizableWidget( + isHorizontalSeparator: true, + separatorSize: 40, + separatorBuilder: (info, controller) => CustomSeparatorWidget( + info: info, + controller: controller, ), + onResized: _printResizeInfo, + children: [ + Container(color: Colors.greenAccent), + Container(color: Colors.redAccent), + Container(color: Colors.blueAccent), + ], ); } - void _printResizeInfo(List dataList) { - // ignore: avoid_print - print(dataList.map((x) => '(${x.size}, ${x.percentage}%)').join(", ")); + _printResizeInfo(List dataList) { + debugPrint(dataList.map((x) => '(${x.size}, ${x.percentage}%)').join(", ")); } } diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc index 4bfa0f3..8b6d468 100644 --- a/example/windows/flutter/generated_plugin_registrant.cc +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/example/windows/flutter/generated_plugin_registrant.h b/example/windows/flutter/generated_plugin_registrant.h index 9846246..dc139d8 100644 --- a/example/windows/flutter/generated_plugin_registrant.h +++ b/example/windows/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/lib/resizable_widget.dart b/lib/resizable_widget.dart old mode 100644 new mode 100755 index a26faae..f510411 --- a/lib/resizable_widget.dart +++ b/lib/resizable_widget.dart @@ -1,5 +1,8 @@ /// Provide [ResizableWidget]. library resizable_widget; -export 'src/resizable_widget.dart' show ResizableWidget, OnResizedFunc; +export 'src/resizable_widget.dart' show ResizableWidget, OnResizeBeginFunc, OnResizedFunc, OnResizeEndFunc, SeparatorBuilder; export 'src/widget_size_info.dart' show WidgetSizeInfo; +export 'src/separator_controller.dart' show SeparatorController; +export 'src/separator_args_info.dart' show SeparatorArgsInfo; +export 'src/separator.dart' show DefaultSeparator; diff --git a/lib/src/resizable_widget.dart b/lib/src/resizable_widget.dart old mode 100644 new mode 100755 index c1ecc43..ae5d841 --- a/lib/src/resizable_widget.dart +++ b/lib/src/resizable_widget.dart @@ -1,12 +1,17 @@ import 'package:flutter/material.dart'; -import 'package:resizable_widget/src/resizable_widget_args_info.dart'; +import 'resizable_widget_args_info.dart'; import 'resizable_widget_child_data.dart'; import 'resizable_widget_controller.dart'; import 'separator.dart'; +import 'separator_args_info.dart'; +import 'separator_controller.dart'; import 'widget_size_info.dart'; /// The callback argument type of [ResizableWidget.onResized]. typedef OnResizedFunc = void Function(List infoList); +typedef OnResizeBeginFunc = void Function(int separatorIndex, DragDownDetails details); +typedef OnResizeEndFunc = void Function(int separatorIndex, DragEndDetails details); +typedef SeparatorBuilder = Widget Function(SeparatorArgsInfo info, SeparatorController controller); /// Holds resizable widgets as children. /// Users can resize the internal widgets by dragging. @@ -42,29 +47,37 @@ class ResizableWidget extends StatefulWidget { /// Separator color. final Color separatorColor; + /// An optional builder to fully customize the separator + final SeparatorBuilder? separatorBuilder; + /// Callback of the resizing event. /// You can get the size and percentage of the internal widgets. /// /// Note that [onResized] is called every frame when resizing [children]. final OnResizedFunc? onResized; + final OnResizeBeginFunc? onResizeBegin; + + final OnResizeEndFunc? onResizeEnd; + /// Creates [ResizableWidget]. ResizableWidget({ Key? key, required this.children, this.percentages, - @Deprecated('Use [isHorizontalSeparator] instead') - this.isColumnChildren = false, + @Deprecated('Use [isHorizontalSeparator] instead') this.isColumnChildren = false, this.isHorizontalSeparator = false, this.isDisabledSmartHide = false, this.separatorSize = 4, this.separatorColor = Colors.white12, + this.separatorBuilder, this.onResized, + this.onResizeBegin, + this.onResizeEnd, }) : super(key: key) { assert(children.isNotEmpty); assert(percentages == null || percentages!.length == children.length); - assert(percentages == null || - percentages!.reduce((value, element) => value + element) == 1); + assert(percentages == null || percentages!.reduce((value, element) => value + element) == 1); } @override diff --git a/lib/src/resizable_widget_args_info.dart b/lib/src/resizable_widget_args_info.dart old mode 100644 new mode 100755 index 2e43b6b..772b7e6 --- a/lib/src/resizable_widget_args_info.dart +++ b/lib/src/resizable_widget_args_info.dart @@ -8,7 +8,10 @@ class ResizableWidgetArgsInfo { final bool isDisabledSmartHide; final double separatorSize; final Color separatorColor; + final SeparatorBuilder? separatorBuilder; + final OnResizeBeginFunc? onResizeBegin; final OnResizedFunc? onResized; + final OnResizeEndFunc? onResizeEnd; ResizableWidgetArgsInfo(ResizableWidget widget) : children = widget.children, @@ -20,5 +23,8 @@ class ResizableWidgetArgsInfo { isDisabledSmartHide = widget.isDisabledSmartHide, separatorSize = widget.separatorSize, separatorColor = widget.separatorColor, - onResized = widget.onResized; + separatorBuilder = widget.separatorBuilder, + onResized = widget.onResized, + onResizeBegin = widget.onResizeBegin, + onResizeEnd = widget.onResizeEnd; } diff --git a/lib/src/resizable_widget_child_data.dart b/lib/src/resizable_widget_child_data.dart old mode 100644 new mode 100755 diff --git a/lib/src/resizable_widget_controller.dart b/lib/src/resizable_widget_controller.dart old mode 100644 new mode 100755 index f0f86a3..5078aea --- a/lib/src/resizable_widget_controller.dart +++ b/lib/src/resizable_widget_controller.dart @@ -11,8 +11,7 @@ class ResizableWidgetController { final ResizableWidgetModel _model; List get children => _model.children; - ResizableWidgetController(ResizableWidgetArgsInfo info) - : _model = ResizableWidgetModel(info) { + ResizableWidgetController(ResizableWidgetArgsInfo info) : _model = ResizableWidgetModel(info) { _model.init(_separatorFactory); } @@ -21,6 +20,12 @@ class ResizableWidgetController { _model.callOnResized(); } + void resizeStart(int separatorIndex, DragDownDetails details) { + _model.resizeStart(separatorIndex, details); + eventStream.add(this); + _model.callResizedBegin(separatorIndex, details); + } + void resize(int separatorIndex, Offset offset) { _model.resize(separatorIndex, offset); @@ -28,6 +33,12 @@ class ResizableWidgetController { _model.callOnResized(); } + void resizeEnd(int separatorIndex, DragEndDetails details) { + _model.resizeEnd(separatorIndex, details); + eventStream.add(this); + _model.callResizedEnd(separatorIndex, details); + } + void tryHideOrShow(int separatorIndex) { final result = _model.tryHideOrShow(separatorIndex); diff --git a/lib/src/resizable_widget_model.dart b/lib/src/resizable_widget_model.dart old mode 100644 new mode 100755 index 3971178..cbc3ecd --- a/lib/src/resizable_widget_model.dart +++ b/lib/src/resizable_widget_model.dart @@ -20,11 +20,9 @@ class ResizableWidgetModel { void init(SeparatorFactory separatorFactory) { final originalChildren = _info.children; final size = originalChildren.length; - final originalPercentages = - _info.percentages ?? List.filled(size, 1 / size); + final originalPercentages = _info.percentages ?? List.filled(size, 1 / size); for (var i = 0; i < size - 1; i++) { - children.add(ResizableWidgetChildData( - originalChildren[i], originalPercentages[i])); + children.add(ResizableWidgetChildData(originalChildren[i], originalPercentages[i])); children.add(ResizableWidgetChildData( separatorFactory.call(SeparatorArgsBasicInfo( 2 * i + 1, @@ -32,6 +30,7 @@ class ResizableWidgetModel { _info.isDisabledSmartHide, _info.separatorSize, _info.separatorColor, + _info.separatorBuilder, )), null)); } @@ -62,6 +61,10 @@ class ResizableWidgetModel { } } + void resizeStart(int separatorIndex, DragDownDetails details) { + _info.onResizeBegin?.call(separatorIndex, details); + } + void resize(int separatorIndex, Offset offset) { final leftSize = _resizeImpl(separatorIndex - 1, offset); final rightSize = _resizeImpl(separatorIndex + 1, offset * (-1)); @@ -92,11 +95,21 @@ class ResizableWidgetModel { } } + void resizeEnd(int separatorIndex, DragEndDetails details) { + _info.onResizeEnd?.call(separatorIndex, details); + } + + void callResizedBegin(int separatorIndex, DragDownDetails details) { + _info.onResizeBegin?.call(separatorIndex, details); + } + void callOnResized() { - _info.onResized?.call(children - .where((x) => x.widget is! Separator) - .map((x) => WidgetSizeInfo(x.size!, x.percentage!)) - .toList()); + _info.onResized?.call( + children.where((x) => x.widget is! Separator).map((x) => WidgetSizeInfo(x.size!, x.percentage!)).toList()); + } + + void callResizedEnd(int separatorIndex, DragEndDetails details) { + _info.onResizeEnd?.call(separatorIndex, details); } bool tryHideOrShow(int separatorIndex) { diff --git a/lib/src/separator.dart b/lib/src/separator.dart old mode 100644 new mode 100755 index 210d465..73f13bd --- a/lib/src/separator.dart +++ b/lib/src/separator.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; + import 'separator_args_info.dart'; import 'separator_controller.dart'; @@ -15,31 +16,47 @@ class Separator extends StatefulWidget { } class _SeparatorState extends State { - late SeparatorArgsInfo _info; late SeparatorController _controller; @override void initState() { super.initState(); - _info = widget.info; _controller = SeparatorController(widget.info.index, widget.info.parentController); } @override - Widget build(BuildContext context) => GestureDetector( - child: MouseRegion( - cursor: _info.isHorizontalSeparator - ? SystemMouseCursors.resizeRow - : SystemMouseCursors.resizeColumn, - child: SizedBox( - child: Container(color: _info.color), - width: _info.isHorizontalSeparator ? double.infinity : _info.size, - height: _info.isHorizontalSeparator ? _info.size : double.infinity, - ), + Widget build(BuildContext context) => widget.info.separatorBuilder != null + ? widget.info.separatorBuilder!(widget.info, _controller) + : DefaultSeparator(info: widget.info, controller: _controller); +} + +class DefaultSeparator extends StatelessWidget { + final SeparatorArgsInfo info; + final SeparatorController controller; + + const DefaultSeparator( + {Key? key, required this.info, required this.controller}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + child: MouseRegion( + cursor: info.isHorizontalSeparator + ? SystemMouseCursors.resizeRow + : SystemMouseCursors.resizeColumn, + child: SizedBox( + child: Container(color: info.color), + width: info.isHorizontalSeparator ? double.infinity : info.size, + height: info.isHorizontalSeparator ? info.size : double.infinity, ), - onPanUpdate: (details) => _controller.onPanUpdate(details, context), - onDoubleTap: () => _controller.onDoubleTap(), - ); + ), + onPanDown: (details) => controller.onPanStart(details), + onPanUpdate: (details) => controller.onPanUpdate(details, context), + onPanEnd: (details) => controller.onPanEnd(details), + onDoubleTap: () => controller.onDoubleTap(), + ); + } } diff --git a/lib/src/separator_args_info.dart b/lib/src/separator_args_info.dart old mode 100644 new mode 100755 index 4f9932d..fc2feb5 --- a/lib/src/separator_args_info.dart +++ b/lib/src/separator_args_info.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'resizable_widget.dart'; import 'resizable_widget_controller.dart'; class SeparatorArgsInfo extends SeparatorArgsBasicInfo { @@ -14,14 +15,16 @@ class SeparatorArgsBasicInfo { final bool isDisabledSmartHide; final double size; final Color color; + final SeparatorBuilder? separatorBuilder; const SeparatorArgsBasicInfo(this.index, this.isHorizontalSeparator, - this.isDisabledSmartHide, this.size, this.color); + this.isDisabledSmartHide, this.size, this.color, this.separatorBuilder); SeparatorArgsBasicInfo.clone(SeparatorArgsBasicInfo info) : index = info.index, isHorizontalSeparator = info.isHorizontalSeparator, isDisabledSmartHide = info.isDisabledSmartHide, size = info.size, - color = info.color; + color = info.color, + separatorBuilder = info.separatorBuilder; } diff --git a/lib/src/separator_controller.dart b/lib/src/separator_controller.dart old mode 100644 new mode 100755 index 8dd0579..f976d22 --- a/lib/src/separator_controller.dart +++ b/lib/src/separator_controller.dart @@ -7,10 +7,18 @@ class SeparatorController { const SeparatorController(this._index, this._parentController); + void onPanStart(DragDownDetails details) { + _parentController.resizeStart(_index, details); + } + void onPanUpdate(DragUpdateDetails details, BuildContext context) { _parentController.resize(_index, details.delta); } + void onPanEnd(DragEndDetails details) { + _parentController.resizeEnd(_index, details); + } + void onDoubleTap() { _parentController.tryHideOrShow(_index); } diff --git a/lib/src/widget_size_info.dart b/lib/src/widget_size_info.dart old mode 100644 new mode 100755