我正在尝试克隆股票android计算器应用程序。我不知道如何实现在右侧打开的可拉式抽屉。
下面是一个gif,它显示了我在说什么: https://imgur.com/a/hnYLTyA
答案 0 :(得分:5)
使用Stack
将抽屉放置在计算器屏幕顶部。
使用Positioned
作为抽屉,并根据其拉出的数量设置其left
参数。
最初将抽屉的left
参数设置到屏幕末端。
在拉动GestureDetector
和onPanUpdate
时更改位置。
根据抽屉的位置更改抽屉图标。
对于计算器屏幕上的暗淡效果,请使用ModalBarrier
。用Opacity
小部件包裹它,并根据抽屉的拉出量设置其opacity
参数。
static double _offset = 30;
double _drawerLeft = 400;
IconData _drawerIcon = Icons.arrow_back_ios;
bool _init = true;
@override
Widget build(BuildContext context) {
if (_init) {
_drawerLeft = MediaQuery.of(context).size.width - _offset;
_init = false;
}
return Scaffold(
body: Align(
alignment: Alignment.bottomCenter,
child: FractionallySizedBox(
widthFactor: 1,
heightFactor: 0.5,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Positioned.fill(
child: Container(
color: Colors.grey[200],
child: Center(
child: Text(
'text',
style: TextStyle(fontSize: 32),
)),
),
),
Positioned.fill(
right: 0,
child: Opacity(
opacity: 1 -
_drawerLeft /
(MediaQuery.of(context).size.width - _offset),
child:
ModalBarrier(dismissible: false, color: Colors.black87),
),
),
Positioned(
width: MediaQuery.of(context).size.width * 3 / 4,
top: 0,
height: MediaQuery.of(context).size.height / 2,
left: _drawerLeft,
child: GestureDetector(
onPanUpdate: (details) {
_drawerLeft += details.delta.dx;
if (_drawerLeft <= MediaQuery.of(context).size.width / 4)
_drawerLeft = MediaQuery.of(context).size.width / 4;
if (_drawerLeft >=
MediaQuery.of(context).size.width - _offset)
_drawerLeft =
MediaQuery.of(context).size.width - _offset;
if (_drawerLeft <= MediaQuery.of(context).size.width / 3)
_drawerIcon = Icons.arrow_forward_ios;
if (_drawerLeft >=
MediaQuery.of(context).size.width - 2 * _offset)
_drawerIcon = Icons.arrow_back_ios;
setState(() {});
},
child: Container(
decoration: BoxDecoration(color: Colors.blue),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: _offset),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Icon(
_drawerIcon,
color: Colors.white,
),
],
),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'text',
style: TextStyle(
color: Colors.white, fontSize: 32),
)
],
)
],
),
)),
),
],
),
),
),
);
}
结果:
答案 1 :(得分:2)
Flutter默认使用本机键盘,而您要做的是制作自己的自定义键盘。
您需要做什么:
我已经举了一个简单的例子。
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: SafeArea(
child: MyStatefulWidget(),
),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
TextEditingController _controller;
NoKeyboardEditableTextFocusNode focusNode;
bool isKeyboardOpen = false;
void initState() {
super.initState();
focusNode = NoKeyboardEditableTextFocusNode();
focusNode.addListener(() {
setState(() {
isKeyboardOpen = focusNode.hasFocus;
});
});
_controller = TextEditingController(text: 'tap here');
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraintes) {
var maxHeight = constraintes.maxHeight;
return Column(
children: [
AnimatedContainer(
height: isKeyboardOpen ? maxHeight - 300 : maxHeight,
duration: Duration(milliseconds: 300),
child: Center(
child: GestureDetector(
onTap: () {
setState(() {
isKeyboardOpen = true;
});
},
child: NoKeyboardEditableText(
noKeyboardEditableTextFocusNode: focusNode,
controller: _controller,
cursorColor: Colors.green,
selectionColor: Colors.red,
style: TextStyle(
fontStyle: FontStyle.normal,
fontSize: 30.0,
color: Colors.black),
),
),
),
),
AnimatedContainer(
height: isKeyboardOpen ? 300 : 0,
duration: Duration(milliseconds: 300),
color: Colors.red,
child: _CustomKeybord(
onAdd: (v) => _controller.value =
TextEditingValue(text: _controller.value.text + v)),
),
],
);
},
),
);
}
}
class _CustomKeybord extends StatefulWidget {
_CustomKeybord({Key key, this.onAdd}) : super(key: key);
final Function(String value) onAdd;
@override
__CustomKeybordState createState() => __CustomKeybordState();
}
class __CustomKeybordState extends State<_CustomKeybord> {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return Stack(
children: [
Positioned(
left: 0,
top: 0,
bottom: 0,
child: Container(
width: constraints.maxWidth * 0.9,
height: constraints.maxHeight,
child: _FirstLayerKeybord(
onAdd: widget.onAdd,
),
),
),
Positioned(
right: 0,
top: 0,
bottom: 0,
child: _SecondLayerKeybord(
onAdd: widget.onAdd,
),
),
],
);
},
);
}
}
class _SecondLayerKeybord extends StatefulWidget {
const _SecondLayerKeybord({
Key key,
@required this.onAdd,
}) : super(key: key);
final Function(String value) onAdd;
@override
__SecondLayerKeybordState createState() => __SecondLayerKeybordState();
}
class __SecondLayerKeybordState extends State<_SecondLayerKeybord>
with TickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
)..addListener(_listener);
}
void _listener() {
if (!_controller.isAnimating) {
setState(() {
isOpen = _controller.isCompleted && _controller.value == 1;
});
}
}
void onTap() {
_controller.isCompleted ? _controller.reverse() : _controller.forward();
}
bool isOpen = false;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
double _currentX;
_onDrag(details) {
var maxWidth = MediaQuery.of(context).size.width;
var x = details.globalPosition.dx;
_currentX = x;
var v = math.max(0.0, 1 - (_currentX / maxWidth - 0.5) * 2);
_controller.value = v;
}
_onDragEnd(_) {
if (_controller.value > .5) {
_controller.animateTo(1);
} else {
_controller.animateTo(0);
}
}
@override
Widget build(BuildContext context) {
var maxWidth = MediaQuery.of(context).size.width;
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
width: maxWidth,
child: Stack(
children: [
Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: IgnorePointer(
child: Opacity(
opacity: 0.3 * _controller.value,
child: Container(
color: Colors.black,
),
),
),
),
Positioned(
left: maxWidth * 0.9 - (_controller.value * maxWidth * 0.45),
bottom: 0,
top: 0,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.blueAccent,
),
),
width: maxWidth * 0.6,
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
GestureDetector(
onTap: onTap,
onPanDown: (details) {
_currentX = details.globalPosition.dx;
},
onPanStart: _onDrag,
onPanUpdate: _onDrag,
onPanEnd: _onDragEnd,
child: Container(
alignment: Alignment.center,
color: Colors.blue,
width: maxWidth * 0.1,
child: Icon(
isOpen
? Icons.keyboard_arrow_right
: Icons.keyboard_arrow_left,
color: Colors.white,
),
),
),
buildColumn(['INV', 'sin', 'ln', 'π', '(']),
buildColumn(['Deg', 'cos', 'log', 'e', ')']),
],
),
),
),
],
),
);
},
);
}
Column buildColumn(List<String> listBtns) {
return Column(
children: listBtns
.map((btnText) => Expanded(
child: RaisedButton(
color: Colors.blue,
onPressed: () => widget.onAdd(btnText),
child: Text(
btnText,
style: TextStyle(color: Colors.white),
),
),
))
.toList(),
);
}
}
class _FirstLayerKeybord extends StatelessWidget {
const _FirstLayerKeybord({
Key key,
@required this.onAdd,
}) : super(key: key);
final Function(String value) onAdd;
final primaryButtons = const [
['1', '2', '3'],
['4', '5', '6'],
['7', '8', '9'],
['0', '.', '='],
];
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: primaryButtons
.map((row) => Expanded(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: row
.map((e) => Expanded(
child: RaisedButton(
onPressed: () => onAdd(e),
child: Text(e),
),
))
.toList(),
),
))
.toList());
}
}
class NoKeyboardEditableText extends EditableText {
NoKeyboardEditableText({
@required TextEditingController controller,
@required TextStyle style,
@required Color cursorColor,
bool autofocus = false,
Color selectionColor,
@required NoKeyboardEditableTextFocusNode noKeyboardEditableTextFocusNode,
}) : super(
controller: controller,
focusNode: noKeyboardEditableTextFocusNode,
style: style,
cursorColor: cursorColor,
autofocus: autofocus,
selectionColor: selectionColor,
backgroundCursorColor: Colors.black,
);
@override
EditableTextState createState() {
return NoKeyboardEditableTextState();
}
}
class NoKeyboardEditableTextState extends EditableTextState {
@override
void requestKeyboard() {
FocusScope.of(context).requestFocus(widget.focusNode);
}
}
class NoKeyboardEditableTextFocusNode extends FocusNode {
@override
bool consumeKeyboardToken() {
return false;
}
}