Selaa lähdekoodia

添加了Popover组件

Kevin 6 vuotta sitten
vanhempi
commit
2c03dc00eb

+ 1 - 0
.gitignore

@@ -8,3 +8,4 @@ build/
 ios/.generated/
 ios/Flutter/Generated.xcconfig
 ios/Runner/GeneratedPluginRegistrant.*
+/.idea

+ 7 - 0
lib/cool_ui.dart

@@ -0,0 +1,7 @@
+library cool_ui;
+import 'package:flutter/material.dart';
+
+part 'utils/screen_utils.dart';
+part 'utils/widget_utils.dart';
+
+part 'popover/cupertino_popover.dart';

+ 281 - 0
lib/popover/cupertino_popover.dart

@@ -0,0 +1,281 @@
+part of cool_ui;
+
+class CupertinoPopoverButton extends StatelessWidget{
+  final Widget child;
+  final Widget popoverBody;
+  final double popoverWidth;
+  final double popoverHeight;
+  final Color popoverColor;
+  final double radius;
+  final Duration transitionDuration;
+  const CupertinoPopoverButton({
+    @required this.child,
+    @required this.popoverBody,
+    this.popoverColor=Colors.white,
+    @required this.popoverWidth,
+    @required this.popoverHeight,
+    this.transitionDuration=const Duration(milliseconds: 200),
+    this.radius=13.0});
+
+
+  @override
+  Widget build(BuildContext context) {
+    // TODO: implement build
+    return GestureDetector(
+      behavior: HitTestBehavior.translucent,
+      onTap: (){
+        showGeneralDialog(
+          context: context,
+          pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) {
+            final ThemeData theme = Theme.of(context, shadowThemeOnly: true);
+            var offset = WidgetUtils.getWidgetLocalToGlobal(context);
+            var bounds = WidgetUtils.getWidgetBounds(context);
+            final Widget pageChild =  CupertinoPopover(
+                transitionDuration: transitionDuration,
+                attachRect:Rect.fromLTWH(offset.dx, offset.dy, bounds.width, bounds.height),
+                child: popoverBody,
+                width: popoverWidth,
+                height: popoverHeight,
+                color: popoverColor,
+                radius: radius,);
+            return Builder(
+                builder: (BuildContext context) {
+                  return theme != null
+                      ? Theme(data: theme, child: pageChild)
+                      : pageChild;
+                }
+            );
+
+          },
+          barrierDismissible: true,
+          barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
+          barrierColor: Colors.black54,
+          transitionDuration: transitionDuration,
+          transitionBuilder: _buildMaterialDialogTransitions,);
+      },
+      child: child,
+    );
+  }
+
+  Widget _buildMaterialDialogTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
+    return FadeTransition(
+      opacity: CurvedAnimation(
+        parent: animation,
+        curve: Curves.easeOut,
+      ),
+      child: child,
+    );
+  }
+}
+
+
+class CupertinoPopover extends StatefulWidget {
+  final Rect attachRect;
+  final Widget child;
+  final double width;
+  final double height;
+  final Color color;
+  final double radius;
+  final Duration transitionDuration;
+
+  const CupertinoPopover({
+    @required this.attachRect,
+    @required this.child,
+    @required this.width,
+    @required this.height,
+    this.color=Colors.white,
+    this.transitionDuration = const Duration(milliseconds: 150),
+    this.radius=13.0});
+
+  @override
+  CupertinoPopoverState createState() => new CupertinoPopoverState();
+}
+
+class CupertinoPopoverState extends State<CupertinoPopover>  with TickerProviderStateMixin{
+  static const double _arrowWidth = 26.0;
+  static const double _arrowHeight = 13.0;
+
+  Rect _arrowRect;
+  Rect _bodyRect;
+  Rect _currentArrowRect;
+  Rect _currentBodyRect;
+  double _currentRadius;
+  Animation<double> doubleAnimation;
+  AnimationController animation;
+
+  /// 是否箭头向上
+  bool get isArrowUp{
+    return ScreenUtil.screenHeight > widget.attachRect.bottom + widget.height + _arrowWidth;
+  }
+
+  @override
+  void initState() {
+    // TODO: implement initState
+    super.initState();
+    calcRect();
+    animation = new AnimationController(
+        duration: widget.transitionDuration,
+        vsync: this
+    );
+    // Tween({T begin, T end }):创建tween(补间)
+    doubleAnimation = new Tween<double>(begin: 0.0, end: 1.0).animate(animation)..addListener((){
+      setState(calcAnimationRect);
+    });
+    animation.forward(from: 0.0);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    var left = _bodyRect.left;
+    var top = isArrowUp?_arrowRect.top:_bodyRect.top;
+    return Stack(
+        children: <Widget>[
+          Positioned(
+            left:left,
+            top:top,
+            child: ClipPath(
+              clipper:ArrowCliper(
+                  arrowRect: _currentArrowRect,
+                  bodyRect: _currentBodyRect,
+                  isArrowUp: isArrowUp,
+                  radius: _currentRadius
+              ),
+              child: Container(
+//                padding: EdgeInsets.only(top:isArrowUp?_arrowHeight:0),
+                color: Colors.white,
+                width: widget.width,
+                height: _bodyRect.height + _arrowHeight,
+                child: widget.child
+              ),
+            ),
+          )
+        ]
+    );
+  }
+
+  calcRect(){
+    double arrowLeft = 0.0;
+    double arrowTop = 0.0;
+    double bodyTop = 0.0;
+    double bodyLeft = 0.0;
+    if(widget.attachRect.left > widget.width / 2 &&  ScreenUtil.screenWidth - widget.attachRect.right > widget.width / 2){ //判断是否可以在中间
+      arrowLeft = widget.attachRect.left + widget.attachRect.width / 2  - _arrowWidth / 2;
+      bodyLeft = widget.attachRect.left +  widget.attachRect.width / 2 - widget.width / 2;
+    }else if(widget.attachRect.left < widget.width / 2){ //靠左
+      bodyLeft = 10.0;
+      arrowLeft = bodyLeft + widget.radius;
+    }else{ //靠右
+      bodyLeft = ScreenUtil.screenWidth - 10.0 - widget.width;
+      arrowLeft = ScreenUtil.screenWidth - 10.0  - _arrowWidth - 5 - widget.radius;
+    }
+    if(isArrowUp){
+      arrowTop = widget.attachRect.bottom;
+      bodyTop = arrowTop + _arrowHeight;
+    }else{
+      arrowTop = widget.attachRect.top - _arrowHeight;
+      bodyTop = widget.attachRect.top - widget.height - _arrowHeight;
+    }
+    _arrowRect = Rect.fromLTWH(arrowLeft, arrowTop, _arrowWidth, _arrowHeight);
+    _bodyRect = Rect.fromLTWH(bodyLeft, bodyTop, widget.width, widget.height);
+  }
+
+  calcAnimationRect(){
+    var top = isArrowUp?_arrowRect.top:_bodyRect.top;
+
+    var middleX = (_arrowRect.left - _bodyRect.left) + _arrowRect.width /2;
+    var arrowLeft = middleX + ((_arrowRect.left - _bodyRect.left) - middleX) *  doubleAnimation.value;
+    var arrowTop = _arrowRect.top - top;
+    var bodyLeft = middleX + (0 - middleX) *  doubleAnimation.value;
+    _currentRadius = widget.radius * doubleAnimation.value;
+    var bodyTop = _bodyRect.top - top;
+
+    if(isArrowUp){
+      bodyTop = arrowTop +  _arrowRect.height * doubleAnimation.value ;
+    }else{
+      arrowTop += _arrowRect.height *(1 - doubleAnimation.value) ;
+      bodyTop = arrowTop -  _bodyRect.height * doubleAnimation.value ;
+    }
+
+    _currentArrowRect = Rect.fromLTWH(
+        arrowLeft,
+        arrowTop,
+        _arrowRect.width *   doubleAnimation.value,
+        _arrowRect.height  * doubleAnimation.value);
+    _currentBodyRect = Rect.fromLTWH(
+        bodyLeft,
+        bodyTop,
+        _bodyRect.width *   doubleAnimation.value,
+        _bodyRect.height *   doubleAnimation.value);
+  }
+}
+
+
+class ArrowCliper extends CustomClipper<Path>{
+  final bool isArrowUp;
+  final Rect arrowRect;
+  final Rect bodyRect;
+  final double radius;
+  const ArrowCliper({this.isArrowUp,this.arrowRect,this.bodyRect,this.radius = 13.0});
+
+  @override
+  Path getClip(Size size) {
+    Path path = new Path();
+
+    if(isArrowUp)
+    {
+
+      path.moveTo(arrowRect.left,arrowRect.bottom); //箭头
+      path.lineTo(arrowRect.left + arrowRect.width / 2, arrowRect.top);
+      path.lineTo(arrowRect.right, arrowRect.bottom);
+
+      path.lineTo(bodyRect.right - radius,bodyRect.top); //右上角
+      path.conicTo(bodyRect.right,bodyRect.top
+          ,bodyRect.right,bodyRect.top + radius,1);
+
+      path.lineTo(bodyRect.right,bodyRect.bottom - radius);  //右下角
+      path.conicTo(bodyRect.right,bodyRect.bottom
+          ,bodyRect.right -radius ,bodyRect.bottom,1);
+
+
+      path.lineTo(bodyRect.left + radius, bodyRect.bottom); //左下角
+      path.conicTo(bodyRect.left,bodyRect.bottom
+          ,bodyRect.left ,bodyRect.bottom - radius,1);
+
+      path.lineTo(bodyRect.left, bodyRect.top + radius); //左上角
+      path.conicTo(bodyRect.left,bodyRect.top
+          ,bodyRect.left + radius,bodyRect.top,1);
+    }else{
+
+      path.moveTo(bodyRect.left + radius,bodyRect.top);
+
+      path.lineTo(bodyRect.right - radius,bodyRect.top); //右上角
+      path.conicTo(bodyRect.right,bodyRect.top
+          ,bodyRect.right,bodyRect.top + radius,1);
+
+      path.lineTo(bodyRect.right,bodyRect.bottom - radius);  //右下角
+      path.conicTo(bodyRect.right,bodyRect.bottom
+          ,bodyRect.right -radius ,bodyRect.bottom,1);
+
+      path.lineTo(arrowRect.right, arrowRect.top); //箭头
+      path.lineTo(arrowRect.left + arrowRect.width / 2, arrowRect.bottom);
+      path.lineTo(arrowRect.left,arrowRect.top);
+
+      path.lineTo(bodyRect.left + radius, bodyRect.bottom); //左下角
+      path.conicTo(bodyRect.left,bodyRect.bottom
+          ,bodyRect.left ,bodyRect.bottom - radius,1);
+
+      path.lineTo(bodyRect.left, bodyRect.top + radius); //左上角
+      path.conicTo(bodyRect.left,bodyRect.top
+          ,bodyRect.left + radius,bodyRect.top,1);
+
+    }
+    path.close();
+    return path;
+  }
+
+  @override
+  bool shouldReclip(ArrowCliper oldClipper) {
+    return this.isArrowUp != oldClipper.isArrowUp || this.arrowRect != oldClipper.arrowRect || this.bodyRect != oldClipper.bodyRect;
+  }
+
+}

+ 59 - 0
lib/utils/screen_utils.dart

@@ -0,0 +1,59 @@
+part of cool_ui;
+
+
+class ScreenUtil {
+  static double _screenWidth;
+  static double _screenHeight;
+  static double _screenDensity;
+  static double _statusBarHeight;
+  static double _appBarHeight;
+  static MediaQueryData _mediaQueryData;
+
+  static ScreenUtil singleton = new ScreenUtil();
+
+  static ScreenUtil getInstance() {
+    return singleton;
+  }
+
+  void init(BuildContext context) {
+    MediaQueryData mediaQuery = MediaQuery.of(context);
+    _mediaQueryData = mediaQuery;
+    _screenWidth = mediaQuery.size.width;
+    _screenHeight = mediaQuery.size.height;
+    _screenDensity = mediaQuery.devicePixelRatio;
+    _statusBarHeight = mediaQuery.padding.top;
+    _appBarHeight = kToolbarHeight;
+  }
+
+  ///screen width
+  static double get screenWidth => _screenWidth;
+
+  ///screen height
+  static double get screenHeight => _screenHeight;
+
+  ///appBar height
+  static double get appBarHeight => _appBarHeight;
+
+  ///screen density
+  static double get screenDensity => _screenDensity;
+
+  ///status bar Height
+  static double get statusBarHeight => _statusBarHeight;
+
+  static MediaQueryData get mediaQueryData => _mediaQueryData;
+
+  static double getScreenWidth(BuildContext context) {
+    MediaQueryData mediaQuery = MediaQuery.of(context);
+    return mediaQuery.size.width;
+  }
+
+  static double getScreenHeight(BuildContext context) {
+    MediaQueryData mediaQuery = MediaQuery.of(context);
+    return mediaQuery.size.height;
+  }
+
+  static Orientation getOrientation(BuildContext context) {
+    MediaQueryData mediaQuery = MediaQuery.of(context);
+    return mediaQuery.orientation;
+  }
+}

+ 47 - 0
lib/utils/widget_utils.dart

@@ -0,0 +1,47 @@
+part of cool_ui;
+
+
+class WidgetUtils {
+  bool _hasMeasured = false;
+  double _width;
+  double _height;
+
+  /// Widget rendering listener.
+  /// Widget渲染监听
+  /// context: Widget context
+  /// isOnce: true,Continuous monitoring  false,Listen only once.
+  /// onCallBack: Widget Rect CallBack
+  void asyncPrepare(
+      BuildContext context, bool isOnce, ValueChanged<Rect> onCallBack) {
+    if (_hasMeasured) return;
+    WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) {
+      RenderBox box = context.findRenderObject();
+      if (box != null && box.semanticBounds != null) {
+        if (isOnce) _hasMeasured = true;
+        double width = box.semanticBounds.width;
+        double height = box.semanticBounds.height;
+        if (_width != width || _height != height) {
+          _width = width;
+          _height = height;
+          if (onCallBack != null) onCallBack(box.semanticBounds);
+        }
+      }
+    });
+  }
+
+  ///get Widget Bounds (width, height, left, top, right, bottom and so on).Widgets must be rendered completely.
+  ///获取widget Rect
+  static Rect getWidgetBounds(BuildContext context) {
+    RenderBox box = context.findRenderObject();
+    return (box != null && box.semanticBounds != null)
+        ? box.semanticBounds
+        : Rect.zero;
+  }
+
+  ///Get the coordinates of the widget on the screen.Widgets must be rendered completely.
+  ///获取widget在屏幕上的坐标,widget必须渲染完成
+  static Offset getWidgetLocalToGlobal(BuildContext context) {
+    RenderBox box = context.findRenderObject();
+    return box == null ? Offset.zero : box.localToGlobal(Offset.zero);
+  }
+}

+ 7 - 0
test/cool_ui_test.dart

@@ -0,0 +1,7 @@
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:cool_ui/cool_ui.dart';
+
+void main() {
+
+}