Files
FlutterUnit/lib/components/drop_selectable_widget.dart
2022-03-26 21:25:40 +08:00

198 lines
5.3 KiB
Dart

import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
typedef OnDropSelected = void Function(int index);
class DropSelectableWidget extends StatefulWidget {
final List<String> data;
final OnDropSelected? onDropSelected;
final Color disableColor;
final double iconSize;
final double height;
final double width;
final double fontSize;
const DropSelectableWidget(
{Key? key,
this.data = const [],
this.onDropSelected,
this.disableColor = Colors.black,
this.iconSize = 24,
this.height = 30,
this.width = 200,
this.fontSize = 14,
})
: super(key: key);
@override
_DropSelectableWidgetState createState() => _DropSelectableWidgetState();
}
class _DropSelectableWidgetState extends State<DropSelectableWidget>
with SingleTickerProviderStateMixin {
late FocusNode _node;
bool _focused = false;
late FocusAttachment _nodeAttachment;
OverlayEntry? _overlayEntry;
late AnimationController _ctrl;
late Animation<double> animation;
final LayerLink layerLink = LayerLink();
int _selectedIndex = 0;
@override
void initState() {
super.initState();
_ctrl = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 200),
);
animation = Tween<double>(begin: 0, end: pi).animate(_ctrl);
_node = FocusNode()
..addListener(() {
if (_node.hasFocus != _focused) {
if (!_focused) {
_ctrl.forward();
_showOverlay();
} else {
_hideOverlay();
_ctrl.reverse();
}
setState(() {
_focused = _node.hasFocus;
});
}
});
_nodeAttachment = _node.attach(context);
}
@override
void dispose() {
_node.dispose();
_ctrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
_nodeAttachment.reparent();
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (_focused) {
_node.unfocus();
} else {
_node.requestFocus();
}
},
child: CompositedTransformTarget(
link: layerLink,
child: buildTarget(),
),
);
}
void _showOverlay() {
_overlayEntry = _createOverlayEntry();
Overlay.of(context)?.insert(_overlayEntry!);
}
void _hideOverlay() {
_overlayEntry?.remove();
}
Widget buildTarget() {
return Container(
width: widget.width,
height: widget.height,
padding: const EdgeInsets.only(left: 10, right: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: _focused ? Colors.blue : widget.disableColor,
)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.data.isNotEmpty ? widget.data[_selectedIndex] : "暂无数据",style: TextStyle(
height: 1,
fontSize: widget.fontSize
),),
AnimatedBuilder(
animation: animation,
builder: (_, child) => Transform.rotate(
angle: animation.value,
child: child,
),
child: Icon(
Icons.keyboard_arrow_down,
size: widget.iconSize,
),
),
],
),
);
}
OverlayEntry _createOverlayEntry() => OverlayEntry(
builder: (BuildContext context) => UnconstrainedBox(
child: CompositedTransformFollower(
link: layerLink,
targetAnchor: Alignment.bottomCenter,
followerAnchor: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Material(
shape: const RoundedRectangleBorder(
side: BorderSide.none,
borderRadius: BorderRadius.all(Radius.circular(5))),
elevation: 1,
child: ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Container(
height: 200,
// alignment: Alignment.center,
decoration: const BoxDecoration(
color: Color(0xffDAE3FF),
),
// padding: const EdgeInsets.only(top: 5),
width: widget.width,
child: CupertinoScrollbar(
child: ListView.builder(
padding: EdgeInsets.zero,
// shrinkWrap: true,
itemCount: widget.data.length,
itemBuilder: _buildItem),
),
),
),
),
),
),
),
);
Widget _buildItem(BuildContext context, int index) {
return Material(
child: InkWell(
onTap: () {
if (_selectedIndex != index) widget.onDropSelected?.call(index);
_selectedIndex = index;
_overlayEntry?.markNeedsBuild();
_node.unfocus();
},
child: Container(
padding: const EdgeInsets.all(8),
color: index == _selectedIndex
? Colors.blue.withOpacity(0.2)
: Colors.transparent,
child: Text(widget.data[index],style: TextStyle(fontSize: widget.fontSize),)),
),
);
}
}