Ver código fonte

添加了自定义键盘

Kevin 6 anos atrás
pai
commit
f01f9f8eb9

+ 4 - 0
CHANGELOG.md

@@ -1,3 +1,7 @@
+## [0.1.7] - TODO:添加了自定义键盘
+* TODO:添加了自定义键盘
+* TODO:修改了下文档结构
+
 ## [0.1.6] - TODO:修改了Toast的位置计算方式
 * TODO: 修改了Toast的位置计算方式
 * TODO: 开放了Toast的位置设置

+ 9 - 167
README.md

@@ -7,177 +7,19 @@ Usage
 Add this to your package's pubspec.yaml file:
 ``` yaml
 dependencies:
-  cool_ui: "^0.1.6"
+  cool_ui: "^0.1.7"
 ```
 
 # 控件
 
-- [CupertinoPopoverButton](#CupertinoPopoverButton)
-- [CupertinoPopoverMenuList](#CupertinoPopoverMenuList)
-- [CupertinoPopoverMenuItem](#CupertinoPopoverMenuItem)
-- [showWeuiToast](#showWeuiToast)
-- [showWeuiSuccessToast](#showWeuiSuccessToast)
-- [showWeuiLoadingToast](#showWeuiLoadingToast)
+- [CupertinoPopoverButton](documents/popover.md#CupertinoPopoverButton)
+- [CupertinoPopoverMenuList](documents/popover.md#CupertinoPopoverMenuList)
+- [CupertinoPopoverMenuItem](documents/popover.md#CupertinoPopoverMenuItem)
+- [showWeuiToast](documents/weui_toast.md#showWeuiToast)
+- [showWeuiSuccessToast](documents/weui_toast.md#showWeuiSuccessToast)
+- [showWeuiLoadingToast](documents/weui_toast.md#showWeuiLoadingToast)
 
 
-## CupertinoPopoverButton
-仿iOS的UIPopover效果的
+# 自定义键盘
 
-用于弹窗的按钮
-```dart
-CupertinoPopoverButton({
-    this.child,
-    this.popoverBuild,
-    this.popoverColor=Colors.white,
-    @required this.popoverWidth,
-    @required this.popoverHeight,
-    BoxConstraints popoverConstraints,
-    this.onTap,
-    this.transitionDuration=const Duration(milliseconds: 200),
-    this.radius=8.0});
-```
-
-
-| Param | Type | Default | Description |
-| --- | --- | --- | --- |
-| child | <code>Widget</code> |  | 按钮的内容 |
-| popoverBuild | <code>WidgetBuilder</code> |  | 生成弹出框的内容 |
-| [popoverWidth] | <code>double</code> |  | 弹出框的宽度 |
-| [popoverHeight] | <code>double</code> |  | 弹出框的高度 |
-| [popoverConstraints] | <code>BoxConstraints</code> | maxHeight:123.0  maxWidth:150.0 | 弹出框的最大最小高宽|
-| [onTap] | <code>BoolCallback</code> |  | 按钮点击事件,返回true取消默认反应(不打开Popover) |
-| [popoverColor] | <code>Color</code> | 白色 | 弹出框的背景颜色 |
-| [transitionDuration] | <code>Duration</code> | 0.2s  | 过度动画时间 |
-| [radius] | <code>double</code> |  8.0 | 弹出框的圆角弧度 |
-
-
-**Example**
-
-```dart
-CupertinoPopoverButton(
-        child: Container(
-          margin: EdgeInsets.all(20.0),
-          width: 80.0,
-          height: 40.0,
-          decoration: BoxDecoration(
-              color: Colors.white,
-              borderRadius: BorderRadius.all(Radius.circular(5.0)),
-              boxShadow: [BoxShadow(color: Colors.black12,blurRadius: 5.0)]
-          ),
-          child: Center(child:Text('左上角')),
-        ),
-        popoverBuild:(BuildContext context){
-              return  Container(
-                          width: 100.0,
-                          height: 100.0,
-                          child: Text('左上角内容'),
-                        )
-        });
-```
-
-
-<img width="38%" height="38%" src="./images/popover_demo.gif"/>
-
-## CupertinoPopoverMenuList
-Popover弹出的菜单样式列表,一般与[CupertinoPopoverMenuItem](#CupertinoPopoverMenuItem)一起用,会给两个Item加间隔线
-```dart
-CupertinoPopoverMenuList({this.children})
-```
-| Param | Type | Description |
-| --- | --- | --- |
-| children | <code>List<Widget></code>  | 子元素,一般是CupertinoPopoverMenuItem |
-
-
-## CupertinoPopoverMenuItem
-单个菜单项
-
-```dart
-const CupertinoPopoverMenuItem({
-    this.leading,
-    this.child,
-    this.onTap,
-    this.isTapClosePopover=true
-  });
-```
-| Param | Type |  Default | Description |
-| --- | --- | --- | --- |
-| [leading] | <code>Widget<Widget></code>  | 菜单左边,一般放图标 |
-| [child] | <code>Widget<Widget></code>  | 菜单内容 |
-| [onTap] | <code>BoolCallback</code> |  | 按钮点击事件,返回true取消默认反应(不关闭Popover) |
-| [isTapClosePopover] | <code>bool<Widget></code>  | 是否点击关闭 |
-
-#### 案例核心代码
-```dart
-    CupertinoPopoverMenuList(
-                    children: <Widget>[
-                      CupertinoPopoverMenuItem(leading: Icon(Icons.add),child: Text("新增"),),
-                      CupertinoPopoverMenuItem(leading: Icon(Icons.edit),child: Text("修改"),),
-                      CupertinoPopoverMenuItem(leading: Icon(Icons.delete),child: Text("删除"),)
-                    ],
-                  )
-```
-
-
-## showWeuiToast
-仿Weui的Toast效果
-```dart
-VoidCallback showWeuiToast({
-  @required BuildContext context,
-  @required Widget message,
-  @required Widget icon,
-  Alignment alignment = const Alignment(0.0,-0.2),
-  RouteTransitionsBuilder transitionBuilder})
-```
-| Param | Type | Default | Description |
-| --- | --- | --- | --- |
-| [context] | <code>BuildContext<Widget></code> | | 上下文 |
-| [message] | <code>Widget<Widget></code>  | | 提示消息 |
-| [alignment] | <code>Alignment<Widget></code>| 默认是居中偏上 | Toast的位置 |
-| [icon] | <code>Widget<Widget></code>  | | 图标 |
-| [transitionBuilder] | <code>RouteTransitionsBuilder<Widget></code> | | 自定义过度动画 |
-
-返回参数:VoidCallback,用于关闭Toast
-
-
-<img width="38%" height="38%" src="./images/toast_demo.gif"/>
-
-## showWeuiSuccessToast
-仿Weui的SuccessToast效果
-```dart
-Future showWeuiSuccessToast({
-  @required BuildContext context,
-  @required Widget message=const Text("成功"),
-  Alignment alignment = const Alignment(0.0,-0.2),
-  RouteTransitionsBuilder transitionBuilder,
-  Duration closeDuration = const Duration(seconds: 3)
-  })
-```
-| Param | Type | Default | Description |
-| --- | --- | --- | --- |
-| [context] | <code>BuildContext<Widget></code>  | | 上下文 |
-| [transitionBuilder] | <code>RouteTransitionsBuilder<Widget></code>  | | 自定义过度动画 |
-| [alignment] | <code>Alignment<Widget></code>| 默认是居中偏上 | Toast的位置 |
-| [message] | <code>Widget<Widget></code> | 成功| 提示消息 |
-| [closeDuration] | <code>Duration<Widget></code>  | 3s | 关闭时间 |
-
-返回参数:Future dart 异步操作,代表Toast已关闭
-
-
-## showWeuiLoadingToast
-仿Weui的LoadingToast效果
-```dart
-VoidCallback showWeuiToast({
-  @required BuildContext context,
-  @required Widget message,
-  Alignment alignment = const Alignment(0.0,-0.2),
-  RouteTransitionsBuilder transitionBuilder
-  })
-```
-| Param | Type | Default | Description |
-| --- | --- |  --- |  --- |
-| [context] | <code>BuildContext<Widget></code> | | 上下文 |
-| [message] | <code>Widget<Widget></code> | | 提示消息 |
-| [alignment] | <code>Alignment<Widget></code>| 默认是居中偏上 | Toast的位置 |
-| [transitionBuilder] | <code>RouteTransitionsBuilder<Widget></code> | | 自定义过度动画 |
-
-返回参数:VoidCallback,用于关闭Toast
+暂时未编写文档,具体请查看Demo与NumberKeyboard的实现

+ 98 - 0
documents/popover.md

@@ -0,0 +1,98 @@
+
+## CupertinoPopoverButton
+仿iOS的UIPopover效果的
+
+用于弹窗的按钮
+```dart
+CupertinoPopoverButton({
+    this.child,
+    this.popoverBuild,
+    this.popoverColor=Colors.white,
+    @required this.popoverWidth,
+    @required this.popoverHeight,
+    BoxConstraints popoverConstraints,
+    this.onTap,
+    this.transitionDuration=const Duration(milliseconds: 200),
+    this.radius=8.0});
+```
+
+
+| Param | Type | Default | Description |
+| --- | --- | --- | --- |
+| child | <code>Widget</code> |  | 按钮的内容 |
+| popoverBuild | <code>WidgetBuilder</code> |  | 生成弹出框的内容 |
+| [popoverWidth] | <code>double</code> |  | 弹出框的宽度 |
+| [popoverHeight] | <code>double</code> |  | 弹出框的高度 |
+| [popoverConstraints] | <code>BoxConstraints</code> | maxHeight:123.0  maxWidth:150.0 | 弹出框的最大最小高宽|
+| [onTap] | <code>BoolCallback</code> |  | 按钮点击事件,返回true取消默认反应(不打开Popover) |
+| [popoverColor] | <code>Color</code> | 白色 | 弹出框的背景颜色 |
+| [transitionDuration] | <code>Duration</code> | 0.2s  | 过度动画时间 |
+| [radius] | <code>double</code> |  8.0 | 弹出框的圆角弧度 |
+
+
+**Example**
+
+```dart
+CupertinoPopoverButton(
+        child: Container(
+          margin: EdgeInsets.all(20.0),
+          width: 80.0,
+          height: 40.0,
+          decoration: BoxDecoration(
+              color: Colors.white,
+              borderRadius: BorderRadius.all(Radius.circular(5.0)),
+              boxShadow: [BoxShadow(color: Colors.black12,blurRadius: 5.0)]
+          ),
+          child: Center(child:Text('左上角')),
+        ),
+        popoverBuild:(BuildContext context){
+              return  Container(
+                          width: 100.0,
+                          height: 100.0,
+                          child: Text('左上角内容'),
+                        )
+        });
+```
+
+
+<img width="38%" height="38%" src="./images/popover_demo.gif"/>
+
+## CupertinoPopoverMenuList
+Popover弹出的菜单样式列表,一般与[CupertinoPopoverMenuItem](#CupertinoPopoverMenuItem)一起用,会给两个Item加间隔线
+```dart
+CupertinoPopoverMenuList({this.children})
+```
+| Param | Type | Description |
+| --- | --- | --- |
+| children | <code>List<Widget></code>  | 子元素,一般是CupertinoPopoverMenuItem |
+
+
+## CupertinoPopoverMenuItem
+单个菜单项
+
+```dart
+const CupertinoPopoverMenuItem({
+    this.leading,
+    this.child,
+    this.onTap,
+    this.isTapClosePopover=true
+  });
+```
+| Param | Type |  Default | Description |
+| --- | --- | --- | --- |
+| [leading] | <code>Widget<Widget></code>  | 菜单左边,一般放图标 |
+| [child] | <code>Widget<Widget></code>  | 菜单内容 |
+| [onTap] | <code>BoolCallback</code> |  | 按钮点击事件,返回true取消默认反应(不关闭Popover) |
+| [isTapClosePopover] | <code>bool<Widget></code>  | 是否点击关闭 |
+
+#### 案例核心代码
+```dart
+    CupertinoPopoverMenuList(
+                    children: <Widget>[
+                      CupertinoPopoverMenuItem(leading: Icon(Icons.add),child: Text("新增"),),
+                      CupertinoPopoverMenuItem(leading: Icon(Icons.edit),child: Text("修改"),),
+                      CupertinoPopoverMenuItem(leading: Icon(Icons.delete),child: Text("删除"),)
+                    ],
+                  )
+```
+

+ 64 - 0
documents/weui_toast.md

@@ -0,0 +1,64 @@
+
+## showWeuiToast
+仿Weui的Toast效果
+```dart
+VoidCallback showWeuiToast({
+  @required BuildContext context,
+  @required Widget message,
+  @required Widget icon,
+  Alignment alignment = const Alignment(0.0,-0.2),
+  RouteTransitionsBuilder transitionBuilder})
+```
+| Param | Type | Default | Description |
+| --- | --- | --- | --- |
+| [context] | <code>BuildContext<Widget></code> | | 上下文 |
+| [message] | <code>Widget<Widget></code>  | | 提示消息 |
+| [alignment] | <code>Alignment<Widget></code>| 默认是居中偏上 | Toast的位置 |
+| [icon] | <code>Widget<Widget></code>  | | 图标 |
+| [transitionBuilder] | <code>RouteTransitionsBuilder<Widget></code> | | 自定义过度动画 |
+
+返回参数:VoidCallback,用于关闭Toast
+
+
+<img width="38%" height="38%" src="./images/toast_demo.gif"/>
+
+## showWeuiSuccessToast
+仿Weui的SuccessToast效果
+```dart
+Future showWeuiSuccessToast({
+  @required BuildContext context,
+  @required Widget message=const Text("成功"),
+  Alignment alignment = const Alignment(0.0,-0.2),
+  RouteTransitionsBuilder transitionBuilder,
+  Duration closeDuration = const Duration(seconds: 3)
+  })
+```
+| Param | Type | Default | Description |
+| --- | --- | --- | --- |
+| [context] | <code>BuildContext<Widget></code>  | | 上下文 |
+| [transitionBuilder] | <code>RouteTransitionsBuilder<Widget></code>  | | 自定义过度动画 |
+| [alignment] | <code>Alignment<Widget></code>| 默认是居中偏上 | Toast的位置 |
+| [message] | <code>Widget<Widget></code> | 成功| 提示消息 |
+| [closeDuration] | <code>Duration<Widget></code>  | 3s | 关闭时间 |
+
+返回参数:Future dart 异步操作,代表Toast已关闭
+
+
+## showWeuiLoadingToast
+仿Weui的LoadingToast效果
+```dart
+VoidCallback showWeuiToast({
+  @required BuildContext context,
+  @required Widget message,
+  Alignment alignment = const Alignment(0.0,-0.2),
+  RouteTransitionsBuilder transitionBuilder
+  })
+```
+| Param | Type | Default | Description |
+| --- | --- |  --- |  --- |
+| [context] | <code>BuildContext<Widget></code> | | 上下文 |
+| [message] | <code>Widget<Widget></code> | | 提示消息 |
+| [alignment] | <code>Alignment<Widget></code>| 默认是居中偏上 | Toast的位置 |
+| [transitionBuilder] | <code>RouteTransitionsBuilder<Widget></code> | | 自定义过度动画 |
+
+返回参数:VoidCallback,用于关闭Toast

+ 43 - 31
example/lib/main.dart

@@ -1,10 +1,16 @@
 import 'package:cool_ui_example/cool_u_i_example_icons.dart';
+import 'package:cool_ui_example/pages/custom_keyboard.dart';
 import 'package:cool_ui_example/pages/paint_event_demo.dart';
 import 'package:cool_ui_example/pages/popover_demo.dart';
 import 'package:cool_ui_example/pages/weui_toast_demo.dart';
+import 'package:cool_ui/cool_ui.dart';
 import 'package:flutter/material.dart';
 
-void main() => runApp(MyApp());
+void main(){
+  NumberKeyboard.register();
+  runApp(MyApp());
+}
+
 
 class MyApp extends StatelessWidget {
   // This widget is the root of your application.
@@ -21,9 +27,9 @@ class MyApp extends StatelessWidget {
         // "hot reload" (press "r" in the console where you ran "flutter run",
         // or press Run > Flutter Hot Reload in IntelliJ). Notice that the
         // counter didn't reset back to zero; the application is not restarted.
-        primarySwatch: Colors.blue
+          primarySwatch: Colors.blue
       ),
-      home: MyHomePage(title: 'Flutter Demo Home Page'),
+      home: MyHomePage(title: 'Flutter Demo Home Page')
     );
   }
 }
@@ -69,34 +75,40 @@ class _MyHomePageState extends State<MyHomePage> {
     // fast, so that you can just rebuild anything that needs updating rather
     // than having to individually change instances of widgets.
     return Scaffold(
-      appBar: AppBar(
-        // Here we take the value from the MyHomePage object that was created by
-        // the App.build method, and use it to set our appbar title.
-        title: Text(widget.title),
-      ),
-      body: ListView(
-        children: <Widget>[
-          ListTile(
-            leading: Icon(CoolUIExampleIcon.popover),
-            title: Text("Popover"),
-            onTap: (){
-              Navigator.of(context).push(MaterialPageRoute(builder: (context)=>PopoverDemo()));
-            },
-          ),
-          ListTile(
-            title: Text("PaintEvent"),
-            onTap: (){
-              Navigator.of(context).push(MaterialPageRoute(builder: (context)=>PaintEventDemo()));
-            },
-          ),
-          ListTile(
-            title: Text("WeuiToastEvent"),
-            onTap: (){
-              Navigator.of(context).push(MaterialPageRoute(builder: (context)=>WeuiToastDemo()));
-            },
-          )
-        ],
-      )
+        appBar: AppBar(
+          // Here we take the value from the MyHomePage object that was created by
+          // the App.build method, and use it to set our appbar title.
+          title: Text(widget.title),
+        ),
+        body: ListView(
+          children: <Widget>[
+            ListTile(
+              leading: Icon(CoolUIExampleIcon.popover),
+              title: Text("Popover"),
+              onTap: (){
+                Navigator.of(context).push(MaterialPageRoute(builder: (context)=>PopoverDemo()));
+              },
+            ),
+            ListTile(
+              title: Text("PaintEvent"),
+              onTap: (){
+                Navigator.of(context).push(MaterialPageRoute(builder: (context)=>PaintEventDemo()));
+              },
+            ),
+            ListTile(
+              title: Text("WeuiToastEvent"),
+              onTap: (){
+                Navigator.of(context).push(MaterialPageRoute(builder: (context)=>WeuiToastDemo()));
+              },
+            ),
+            ListTile(
+              title: Text("CustomKeyboardEvent"),
+              onTap: (){
+                Navigator.of(context).push(MaterialPageRoute(builder: (context)=>CustomKeyboardDemo()));
+              },
+            )
+          ],
+        )
     );
   }
 }

+ 46 - 0
example/lib/pages/custom_keyboard.dart

@@ -0,0 +1,46 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:cool_ui/cool_ui.dart';
+
+
+class CustomKeyboardDemo extends StatefulWidget{
+  @override
+  State<StatefulWidget> createState() {
+    // TODO: implement createState
+    return CustomKeyboardDemoState();
+  }
+
+}
+
+class CustomKeyboardDemoState extends State<CustomKeyboardDemo>{
+
+
+  @override
+  Widget build(BuildContext context) {
+    // TODO: implement build
+    return KeyboardMediaQuery(
+        child: Builder(builder: (ctx) {
+          CoolKeyboard.init(ctx);
+          return Scaffold(
+              appBar: AppBar(
+                title: Text("Custom Keyboard Demo"),
+              ),
+              body: ListView(
+                children: <Widget>[
+                  TextField(
+                    keyboardType: TextInputType.text,
+                  ),
+                  Container(
+                    height: 300.0,
+                  ),
+                  TextField(
+                    decoration: InputDecoration(labelText: '演示键盘弹出后滚动'),
+                    keyboardType: NumberKeyboard.inputType,
+                  )
+                ],
+              )
+          );
+        })
+    );
+  }
+}

BIN
images/custom_keyboard.gif


+ 12 - 1
lib/cool_ui.dart

@@ -1,8 +1,14 @@
 library cool_ui;
+
+import 'dart:async';
+import 'dart:ui' as ui;
+import 'dart:core';
+
+import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flustars/flustars.dart';
-import 'dart:async';
 
 part 'icons/cool_ui_icons.dart';
 
@@ -12,3 +18,8 @@ part 'widgets/popover/cupertino_popover_menu_item.dart';
 part 'widgets/utils/paint_event.dart';
 
 part 'dialogs/weui_toast.dart';
+
+part 'keyboards/keyboard_manager.dart';
+part 'keyboards/number_keyboard.dart';
+part 'keyboards/keyboard_controller.dart';
+part 'keyboards/keyboard_media_query.dart';

+ 104 - 0
lib/keyboards/keyboard_controller.dart

@@ -0,0 +1,104 @@
+part of cool_ui;
+
+class KeyboardController extends ValueNotifier<TextEditingValue>{
+  final InputClient client;
+
+  KeyboardController({TextEditingValue value,this.client})
+      : super(value == null ? TextEditingValue.empty : value);
+
+
+  /// The current string the user is editing.
+  String get text => value.text;
+  /// Setting this will notify all the listeners of this [TextEditingController]
+  /// that they need to update (it calls [notifyListeners]). For this reason,
+  /// this value should only be set between frames, e.g. in response to user
+  /// actions, not during the build, layout, or paint phases.
+  set text(String newText) {
+    value = value.copyWith(text: newText,
+        selection: const TextSelection.collapsed(offset: -1),
+        composing: TextRange.empty);
+  }
+
+  /// The currently selected [text].
+  ///
+  /// If the selection is collapsed, then this property gives the offset of the
+  /// cursor within the text.
+  TextSelection get selection => value.selection;
+  /// Setting this will notify all the listeners of this [TextEditingController]
+  /// that they need to update (it calls [notifyListeners]). For this reason,
+  /// this value should only be set between frames, e.g. in response to user
+  /// actions, not during the build, layout, or paint phases.
+  set selection(TextSelection newSelection) {
+    if (newSelection.start > text.length || newSelection.end > text.length)
+      throw FlutterError('invalid text selection: $newSelection');
+    value = value.copyWith(selection: newSelection, composing: TextRange.empty);
+  }
+
+  /// Set the [value] to empty.
+  ///
+  /// After calling this function, [text] will be the empty string and the
+  /// selection will be invalid.
+  ///
+  /// Calling this will notify all the listeners of this [TextEditingController]
+  /// that they need to update (it calls [notifyListeners]). For this reason,
+  /// this method should only be called between frames, e.g. in response to user
+  /// actions, not during the build, layout, or paint phases.
+  void clear() {
+    value = TextEditingValue.empty;
+  }
+
+  /// Set the composing region to an empty range.
+  ///
+  /// The composing region is the range of text that is still being composed.
+  /// Calling this function indicates that the user is done composing that
+  /// region.
+  ///
+  /// Calling this will notify all the listeners of this [TextEditingController]
+  /// that they need to update (it calls [notifyListeners]). For this reason,
+  /// this method should only be called between frames, e.g. in response to user
+  /// actions, not during the build, layout, or paint phases.
+  clearComposing() {
+    value = value.copyWith(composing: TextRange.empty);
+  }
+
+  deleteOne(){
+    if(selection.baseOffset == 0)
+      return;
+    String newText = '';
+    if(selection.baseOffset != selection.extentOffset)
+    {
+      newText = selection.textBefore(text)  + selection.textAfter(text);
+      value = TextEditingValue(
+          text: newText,
+          selection: selection.copyWith(
+              baseOffset:selection.baseOffset,
+              extentOffset: selection.baseOffset)
+      );
+    }else{
+      newText = text.substring(0,selection.baseOffset - 1) + selection.textAfter(text);
+      value = TextEditingValue(
+          text: newText,
+          selection: selection.copyWith(
+              baseOffset:selection.baseOffset - 1,
+              extentOffset: selection.baseOffset - 1)
+      );
+    }
+  }
+
+  /// 在光标位置添加文字,一般用于键盘输入
+  addText(String insertText){
+    String newText = selection.textBefore(text) + insertText + selection.textAfter(text);
+    value = TextEditingValue(
+        text: newText,
+        selection: selection.copyWith(
+            baseOffset:selection.baseOffset + insertText.length,
+            extentOffset: selection.baseOffset + insertText.length)
+    );
+  }
+
+  /// 完成
+  doneAction(){
+    CoolKeyboard.sendPerformAction(TextInputAction.done);
+  }
+
+}

+ 336 - 0
lib/keyboards/keyboard_manager.dart

@@ -0,0 +1,336 @@
+part of cool_ui;
+
+
+typedef GetKeyboardHeight = double Function(BuildContext context);
+typedef KeyboardBuilder = Widget Function(BuildContext context,KeyboardController controller);
+
+class CoolKeyboard {
+  static JSONMethodCodec _codec = const JSONMethodCodec();
+  static KeyboardConfig _currentKeyboard;
+  static Map<CKTextInputType,KeyboardConfig> _keyboards = {};
+  static BuildContext _context;
+  static OverlayEntry _keyboardEntry;
+  static KeyboardController _keyboardController;
+  static GlobalKey<KeyboardPageState> _pageKey;
+  static bool isInterceptor = false;
+
+  static double get keyboardHeight =>_keyboardHeight;
+  static double _keyboardHeight;
+
+
+  static init(BuildContext context){
+    _context = context;
+    interceptorInput();
+  }
+
+  static interceptorInput(){
+    if(isInterceptor)
+      return;
+    isInterceptor = true;
+    BinaryMessages.setMockMessageHandler("flutter/textinput", (ByteData data) async{
+      var methodCall = _codec.decodeMethodCall(data);
+      switch(methodCall.method){
+        case 'TextInput.show':
+          if(_currentKeyboard != null){
+            openKeyboard();
+            return _codec.encodeSuccessEnvelope(null);
+          }else{
+            return await _sendPlatformMessage("flutter/textinput", data);
+          }
+          break;
+        case 'TextInput.hide':
+          if(_currentKeyboard != null){
+            hideKeyboard();
+            return _codec.encodeSuccessEnvelope(null);
+          }else{
+            return await _sendPlatformMessage("flutter/textinput", data);
+          }
+          break;
+        case 'TextInput.setEditingState':
+          var editingState =  TextEditingValue.fromJSON(methodCall.arguments);
+          if(editingState != null && _keyboardController != null){
+            _keyboardController.value = editingState;
+            return _codec.encodeSuccessEnvelope(null);
+          }
+          break;
+        case 'TextInput.clearClient':
+          hideKeyboard(animation:true);
+          clearKeyboard();
+          break;
+        case 'TextInput.setClient':
+          var setInputType = methodCall.arguments[1]['inputType'];
+          InputClient client;
+          _keyboards.forEach((inputType,keyboardConfig){
+            if(inputType.name == setInputType['name']){
+              client = InputClient.fromJSON(methodCall.arguments);
+              clearKeyboard();
+              _currentKeyboard = keyboardConfig;
+              _keyboardController = KeyboardController(client:client)..addListener((){
+                var callbackMethodCall = MethodCall("TextInputClient.updateEditingState",[_keyboardController.client.connectionId,_keyboardController.value.toJSON()]);
+                BinaryMessages.handlePlatformMessage("flutter/textinput", _codec.encodeMethodCall(callbackMethodCall), (data){});
+              });
+            }
+          });
+          if(client != null){
+            await _sendPlatformMessage("flutter/textinput", _codec.encodeMethodCall(MethodCall('TextInput.hide')));
+            return _codec.encodeSuccessEnvelope(null);
+          }else{
+            hideKeyboard(animation:false);
+            clearKeyboard();
+          }
+          break;
+      }
+      ByteData response =  await _sendPlatformMessage("flutter/textinput", data);
+      return response;
+    });
+  }
+
+  static Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
+    final Completer<ByteData> completer = Completer<ByteData>();
+    ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
+      try {
+        completer.complete(reply);
+      } catch (exception, stack) {
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          library: 'services library',
+          context: 'during a platform message response callback',
+        ));
+      }
+    });
+    return completer.future;
+  }
+
+  static addKeyboard(CKTextInputType inputType,KeyboardConfig config){
+    _keyboards[inputType] = config;
+  }
+
+  static openKeyboard(){
+    if(_keyboardEntry != null)
+      return;
+    _pageKey = GlobalKey<KeyboardPageState>();
+    _keyboardHeight = _currentKeyboard.getHeight(_context);
+    _context.ancestorStateOfType(const TypeMatcher<KeyboardMediaQueryState>()).setState((){});
+    var tempKey = _pageKey;
+    _keyboardEntry = OverlayEntry(builder: (ctx) {
+      if(_currentKeyboard != null && _keyboardHeight != null)
+      {
+        return  KeyboardPage(
+            key: tempKey,
+            child: Builder(builder: (ctx){
+              return _currentKeyboard.builder(ctx,_keyboardController);
+            }),
+            height:_keyboardHeight
+        );
+      }else{
+        return Container();
+      }
+    });
+
+    Overlay.of(_context).insert(_keyboardEntry);
+  }
+
+  static hideKeyboard({bool animation=true}){
+    if(_keyboardEntry != null) {
+      _keyboardHeight = null;
+      _pageKey.currentState.animationController.addStatusListener((status) {
+        if (status == AnimationStatus.dismissed ||
+            status == AnimationStatus.completed) {
+          if (_keyboardEntry != null) {
+            _keyboardEntry.remove();
+            _keyboardEntry = null;
+          }
+        }
+      });
+      if (animation)
+      {
+        _pageKey.currentState.exitKeyboard();
+      }
+      else{
+        _keyboardEntry.remove();
+        _keyboardEntry = null;
+      }
+    }
+    _pageKey = null;
+    _context.ancestorStateOfType(const TypeMatcher<KeyboardMediaQueryState>()).setState((){});
+  }
+
+  static clearKeyboard(){
+    _currentKeyboard = null;
+    if(_keyboardController != null){
+      _keyboardController.dispose();
+      _keyboardController = null;
+    }
+  }
+
+  static sendPerformAction(TextInputAction action){
+    var callbackMethodCall = MethodCall("TextInputClient.performAction",
+        [
+          _keyboardController.client.connectionId,
+          action.toString()
+        ]);
+    BinaryMessages.handlePlatformMessage(
+        "flutter/textinput", _codec.encodeMethodCall(callbackMethodCall), (
+        data) {});
+  }
+
+}
+
+class KeyboardConfig{
+  final KeyboardBuilder builder;
+  final GetKeyboardHeight getHeight;
+  const KeyboardConfig({this.builder,this.getHeight});
+}
+
+class InputClient{
+  final int connectionId;
+  final  TextInputConfiguration configuration;
+  const InputClient({this.connectionId,this.configuration});
+
+  factory InputClient.fromJSON(List<dynamic> encoded) {
+    return InputClient(connectionId: encoded[0],configuration: TextInputConfiguration(
+        inputType:CKTextInputType.fromJSON(encoded[1]['inputType']),
+        obscureText:encoded[1]['obscureText'],
+        autocorrect:encoded[1]['autocorrect'],
+        actionLabel:encoded[1]['actionLabel'],
+        inputAction:_toTextInputAction(encoded[1]['inputAction']),
+        textCapitalization:_toTextCapitalization(encoded[1]['textCapitalization']),
+        keyboardAppearance:_toBrightness(encoded[1]['keyboardAppearance'])
+
+    ));
+  }
+
+
+  static TextInputAction _toTextInputAction(String action) {
+    switch (action) {
+      case 'TextInputAction.none':
+        return TextInputAction.none;
+      case 'TextInputAction.unspecified':
+        return TextInputAction.unspecified;
+      case 'TextInputAction.go':
+        return TextInputAction.go;
+      case 'TextInputAction.search':
+        return TextInputAction.search;
+      case 'TextInputAction.send':
+        return TextInputAction.send;
+      case 'TextInputAction.next':
+        return TextInputAction.next;
+      case 'TextInputAction.previuos':
+        return TextInputAction.previous;
+      case 'TextInputAction.continue_action':
+        return TextInputAction.continueAction;
+      case 'TextInputAction.join':
+        return TextInputAction.join;
+      case 'TextInputAction.route':
+        return TextInputAction.route;
+      case 'TextInputAction.emergencyCall':
+        return TextInputAction.emergencyCall;
+      case 'TextInputAction.done':
+        return TextInputAction.done;
+      case 'TextInputAction.newline':
+        return TextInputAction.newline;
+    }
+    throw FlutterError('Unknown text input action: $action');
+  }
+
+
+  static TextCapitalization _toTextCapitalization(String capitalization){
+    switch(capitalization){
+      case 'TextCapitalization.none':
+        return TextCapitalization.none;
+      case 'TextCapitalization.characters':
+        return TextCapitalization.characters;
+      case 'TextCapitalization.sentences':
+        return TextCapitalization.sentences;
+      case 'TextCapitalization.words':
+        return TextCapitalization.words;
+    }
+
+    throw FlutterError('Unknown text capitalization: $capitalization');
+  }
+
+  static Brightness _toBrightness(String brightness){
+    switch(brightness){
+      case 'Brightness.dark':
+        return Brightness.dark;
+      case 'Brightness.light':
+        return Brightness.light;
+    }
+
+    throw FlutterError('Unknown Brightness: $brightness');
+  }
+}
+
+class CKTextInputType extends TextInputType{
+  final String name;
+
+  const CKTextInputType({this.name,bool signed,bool decimal}) : super.numberWithOptions(signed:signed,decimal:decimal);
+
+  @override
+  Map<String, dynamic> toJson() {
+    return <String, dynamic>{
+      'name': name,
+      'signed': signed,
+      'decimal': decimal,
+    };
+  }
+
+  factory CKTextInputType.fromJSON(Map<String,dynamic> encoded) {
+    return CKTextInputType(
+        name: encoded['name'],
+        signed: encoded['signed'],
+        decimal: encoded['decimal']
+    );
+  }
+}
+
+class KeyboardPage extends StatefulWidget{
+  final Widget child;
+  final double height;
+  const KeyboardPage({this.child,this.height,Key key}):super(key:key);
+
+  @override
+  State<StatefulWidget> createState() =>KeyboardPageState();
+
+}
+
+class KeyboardPageState extends State<KeyboardPage> with SingleTickerProviderStateMixin{
+  AnimationController animationController;
+  Animation<double> doubleAnimation;
+  double bottom;
+
+  @override
+  void initState() {
+    // TODO: implement initState
+    super.initState();
+    animationController = new AnimationController(duration: new Duration(milliseconds: 100),vsync: this)
+      ..addListener(()=>setState((){}));
+    doubleAnimation =
+    new Tween(begin: 0.0, end: widget.height).animate(animationController)..addListener(()=>setState((){}));
+    animationController.forward(from:0.0);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Positioned(
+        child: IntrinsicHeight(
+            child: widget.child
+        ),
+        bottom: (widget.height - doubleAnimation.value) * -1
+    );
+  }
+
+
+  @override
+  void dispose() {
+    super.dispose();
+    animationController.dispose();
+  }
+
+  exitKeyboard(){
+    animationController.reverse();
+  }
+}
+
+

+ 27 - 0
lib/keyboards/keyboard_media_query.dart

@@ -0,0 +1,27 @@
+part of cool_ui;
+
+class KeyboardMediaQuery extends StatefulWidget{
+  final Widget child;
+
+  KeyboardMediaQuery({this.child})
+      : assert(child != null);
+
+  @override
+  State<StatefulWidget> createState() =>KeyboardMediaQueryState();
+
+}
+
+class KeyboardMediaQueryState extends State<KeyboardMediaQuery >{
+  @override
+  Widget build(BuildContext context) {
+    // TODO: implement build
+    var data = MediaQuery.of(context);
+    print('KeyboardMediaQuery${CoolKeyboard.keyboardHeight}');
+    // TODO: implement build
+    return MediaQuery(
+        child: widget.child,
+        data:data.copyWith(viewInsets: data.viewInsets.copyWith(bottom: CoolKeyboard.keyboardHeight))
+    );;
+  }
+
+}

+ 88 - 0
lib/keyboards/number_keyboard.dart

@@ -0,0 +1,88 @@
+part of cool_ui;
+
+class NumberKeyboard extends StatelessWidget{
+  static const CKTextInputType inputType = const CKTextInputType(name:'CKNumberKeyboard');
+  static double getHeight(BuildContext ctx){
+    MediaQueryData mediaQuery = MediaQuery.of(ctx);
+    return mediaQuery.size.width / 3 / 2 * 4;
+  }
+  final KeyboardController controller ;
+  const NumberKeyboard({this.controller});
+
+  static register(){
+    CoolKeyboard.addKeyboard(NumberKeyboard.inputType,KeyboardConfig(builder: (context,controller){
+      return NumberKeyboard(controller: controller);
+    },getHeight: NumberKeyboard.getHeight));
+  }
+
+
+  @override
+  Widget build(BuildContext context) {
+    MediaQueryData mediaQuery = MediaQuery.of(context);
+    return Material(
+      child: DefaultTextStyle(style: TextStyle(fontWeight: FontWeight.w500,color: Colors.black,fontSize: 23.0), child: Container(
+        height:getHeight(context),
+        width: mediaQuery.size.width,
+        decoration: BoxDecoration(
+          color: Color(0xffafafaf),
+        ),
+        child: GridView.count(
+            childAspectRatio: 2/1,
+            mainAxisSpacing:0.5,
+            crossAxisSpacing:0.5,
+            padding: EdgeInsets.all(0.0),
+            crossAxisCount: 3,
+            children: <Widget>[
+              buildButton('1'),
+              buildButton('2'),
+              buildButton('3'),
+              buildButton('4'),
+              buildButton('5'),
+              buildButton('6'),
+              buildButton('7'),
+              buildButton('8'),
+              buildButton('9'),
+              Container(
+                color: Color(0xFFd3d6dd),
+                child: GestureDetector(
+                  behavior: HitTestBehavior.translucent,
+                  child: Center(child: Icon(Icons.expand_more),),
+                  onTap: (){
+                    controller.doneAction();
+                  },
+                ),
+              ),
+              buildButton('0'),
+              Container(
+                color: Color(0xFFd3d6dd),
+                child: GestureDetector(
+                  behavior: HitTestBehavior.translucent,
+                  child: Center(child: Text('X'),),
+                  onTap: (){
+                    controller.deleteOne();
+                  },
+                ),
+              ),
+            ]),
+      )),
+    );
+  }
+
+  Widget buildButton(String title,{String value}){
+    if(value == null){
+      value = title;
+    }
+    return Container(
+      color: Colors.white,
+      child: GestureDetector(
+        behavior: HitTestBehavior.translucent,
+        child: Center(child: Text(title),),
+        onTap: (){
+          controller.addText(value);
+        },
+      ),
+    );
+  }
+}
+
+

+ 8 - 246
pubspec.lock

@@ -1,20 +1,6 @@
 # Generated by pub
 # See https://www.dartlang.org/tools/pub/glossary#lockfile
 packages:
-  analyzer:
-    dependency: transitive
-    description:
-      name: analyzer
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.32.4"
-  args:
-    dependency: transitive
-    description:
-      name: args
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "1.5.0"
   async:
     dependency: transitive
     description:
@@ -43,27 +29,13 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.14.11"
-  convert:
-    dependency: transitive
-    description:
-      name: convert
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "2.0.2"
-  crypto:
-    dependency: transitive
-    description:
-      name: crypto
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "2.0.6"
-  csslib:
-    dependency: transitive
+  cupertino_icons:
+    dependency: "direct main"
     description:
-      name: csslib
+      name: cupertino_icons
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "0.14.5"
+    version: "0.1.2"
   flustars:
     dependency: "direct main"
     description:
@@ -81,83 +53,6 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
-  front_end:
-    dependency: transitive
-    description:
-      name: front_end
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.1.4"
-  glob:
-    dependency: transitive
-    description:
-      name: glob
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "1.1.7"
-  html:
-    dependency: transitive
-    description:
-      name: html
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.13.3+3"
-  http:
-    dependency: transitive
-    description:
-      name: http
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.11.3+17"
-  http_multi_server:
-    dependency: transitive
-    description:
-      name: http_multi_server
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "2.0.5"
-  http_parser:
-    dependency: transitive
-    description:
-      name: http_parser
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "3.1.3"
-  io:
-    dependency: transitive
-    description:
-      name: io
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.3.3"
-  js:
-    dependency: transitive
-    description:
-      name: js
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.6.1+1"
-  json_rpc_2:
-    dependency: transitive
-    description:
-      name: json_rpc_2
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "2.0.9"
-  kernel:
-    dependency: transitive
-    description:
-      name: kernel
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.3.4"
-  logging:
-    dependency: transitive
-    description:
-      name: logging
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.11.3+2"
   matcher:
     dependency: transitive
     description:
@@ -172,41 +67,6 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.1.6"
-  mime:
-    dependency: transitive
-    description:
-      name: mime
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.9.6+2"
-  multi_server_socket:
-    dependency: transitive
-    description:
-      name: multi_server_socket
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "1.0.2"
-  node_preamble:
-    dependency: transitive
-    description:
-      name: node_preamble
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "1.4.4"
-  package_config:
-    dependency: transitive
-    description:
-      name: package_config
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "1.0.5"
-  package_resolver:
-    dependency: transitive
-    description:
-      name: package_resolver
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "1.0.4"
   path:
     dependency: transitive
     description:
@@ -214,34 +74,13 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.6.2"
-  plugin:
-    dependency: transitive
-    description:
-      name: plugin
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.2.0+3"
-  pool:
-    dependency: transitive
-    description:
-      name: pool
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "1.3.6"
-  pub_semver:
-    dependency: transitive
-    description:
-      name: pub_semver
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "1.4.2"
   quiver:
     dependency: transitive
     description:
       name: quiver
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "2.0.0+1"
+    version: "2.0.1"
   shared_preferences:
     dependency: transitive
     description:
@@ -249,53 +88,11 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "0.4.3"
-  shelf:
-    dependency: transitive
-    description:
-      name: shelf
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.7.3+3"
-  shelf_packages_handler:
-    dependency: transitive
-    description:
-      name: shelf_packages_handler
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "1.0.4"
-  shelf_static:
-    dependency: transitive
-    description:
-      name: shelf_static
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.2.8"
-  shelf_web_socket:
-    dependency: transitive
-    description:
-      name: shelf_web_socket
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.2.2+4"
   sky_engine:
     dependency: transitive
     description: flutter
     source: sdk
     version: "0.0.99"
-  source_map_stack_trace:
-    dependency: transitive
-    description:
-      name: source_map_stack_trace
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "1.1.5"
-  source_maps:
-    dependency: transitive
-    description:
-      name: source_maps
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.10.7"
   source_span:
     dependency: transitive
     description:
@@ -338,13 +135,13 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.0.1"
-  test:
+  test_api:
     dependency: transitive
     description:
-      name: test
+      name: test_api
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.3.0"
+    version: "0.2.1"
   typed_data:
     dependency: transitive
     description:
@@ -352,13 +149,6 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.1.6"
-  utf:
-    dependency: transitive
-    description:
-      name: utf
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.9.0+5"
   vector_math:
     dependency: transitive
     description:
@@ -366,34 +156,6 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.0.8"
-  vm_service_client:
-    dependency: transitive
-    description:
-      name: vm_service_client
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.2.6"
-  watcher:
-    dependency: transitive
-    description:
-      name: watcher
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.9.7+10"
-  web_socket_channel:
-    dependency: transitive
-    description:
-      name: web_socket_channel
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "1.0.9"
-  yaml:
-    dependency: transitive
-    description:
-      name: yaml
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "2.1.15"
 sdks:
   dart: ">=2.0.0 <3.0.0"
   flutter: ">=0.1.4 <2.0.0"

+ 1 - 2
pubspec.yaml

@@ -1,6 +1,6 @@
 name: cool_ui
 description: 用flutter实现一些我认为好看的UI控件,目前暂时只有Popover,Weui,不过有什么觉得好看的可以提Issue
-version: 0.1.6
+version: 0.1.7
 author: Kevin <liangkaikevin@gmail.com>
 homepage: https://github.com/Im-Kevin/cool_ui
 
@@ -11,7 +11,6 @@ dependencies:
   flutter:
     sdk: flutter
 
-
   flustars: 0.1.3
 
 dev_dependencies: