想实现一个带文字 IOS 风格的 Switcher,Flutter 自己有一个 IOS 风格的 CupertinoSwitch 控件,可是添加不了文字,想继承 CupertinoSwitch 重写,但又无从下手,有思路的求安利我。还好控件逻辑不是很复杂。我本身手写了一个,效果以下:web
效果图
实现思路
先画控件
画一个背景控件(Container), 一个圆形滑动控件(由于我看有阴影,因此我用 Card),一个 Text 展现文字的控件,还有装这些控件的 Stack 控件。微信
加入动画控制控件滑动
点击控件,启用动画,还有额外一些点击回调。ide
控件被我稍微封装了一下,代码很差拆开说,其实逻辑也很简单。感兴趣的直接复制代码运行,贴一下控件代码:学习
用法代码
HclSwitcher(
height: 60,
width: 120,
label: "ZZZ",
activeColor: Colors.orange,
dissColor: Colors.grey,
isOpen: true,
onChange: (flag) {},
)
HclSwitcher 控件代码
import 'package:flutter/material.dart';
//暂时还未添加白板缩放动画,仿IOS的Switcher控件
class HclSwitcher extends StatefulWidget {
//传入的高
final double height;
//传入的宽
final double width;
//开着的颜色
final Color activeColor;
//关闭的颜色
final Color dissColor;
//显示的文字
final String label;
//开关标识
final bool isOpen;
//原生IOS控件关闭时候有一个白色背景板
final isShowWhiteBg;
//回调
final ValueChanged<bool> onChange;
//构造方法,我这里规定传入宽必须比高大,目的就是画圆的时候用的是最小的宽或 //高的值(此处最小的值就是高的值)
HclSwitcher({
Key key,
this.height,
this.width,
this.label,
this.activeColor,
this.dissColor,
this.isOpen,
this.onChange,
this.isShowWhiteBg = false,
}) : super(key: key) {
if (this.height >= this.width) throw "宽必须必高大";
}
@override
_HclSwitcherState createState() => _HclSwitcherState();
}
class _HclSwitcherState extends State<HclSwitcher>
with TickerProviderStateMixin {
//控制开关
bool _isOpen;
//动画控制器,动画原理就是经过animation值的变化, //控制Positioned的左右边距
AnimationController controller;
//动画值
Animation<double> animation;
bool isInit = true; //第一次初始化不用调用动画
@override
void initState() {
// TODO: implement initState
super.initState();
_isOpen = widget.isOpen;
controller = new AnimationController(
vsync: this, duration: Duration(milliseconds: 200));
animation = Tween(begin: 0.0, end: 1.0)
.animate(CurvedAnimation(parent: controller, curve: Curves.linear))
..addListener(() {
setState(() {
print("value${animation.value}");
});
})
..addStatusListener((status) {
print(status);
});
print("init${animation.value}");
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
controller.reset();
controller.forward();
isInit = false;
setState(() {
_isOpen = !_isOpen;
if (null != widget.onChange) widget.onChange(_isOpen);
});
},
child: Container(
//大背景控件
height: widget.height,
width: widget.width,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(widget.height / 2)),
color: _isOpen ? widget.activeColor : widget.dissColor,
),
child: Stack(
children: <Widget>[
_isOpen
? Container()
: widget.isShowWhiteBg
? Positioned(
top: 2,
left: 2,
right: 2,
child: Container(
alignment: Alignment.center,
height: widget.height - 4,
width: widget.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(widget.height / 2)),
color: Colors.white,
)),
)
: Container(),
Container(
margin: EdgeInsets.only(left: 12),
alignment: Alignment.centerLeft,
child: _isOpen
? Text(
widget.label,
style: TextStyle(fontSize: 18, color: Colors.white),
)
: Container(),
),
isInit
? Positioned(
right: _isOpen ? 0 : widget.width - widget.height,
child: Container(
height: widget.height,
width: widget.height,
child: Card(
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(widget.height / 2))),
),
))
: Positioned(
left: !_isOpen
? null
: (widget.width - widget.height) * animation.value,
right: _isOpen
? null
: (widget.width - widget.height) * animation.value,
child: Container(
height: widget.height,
width: widget.height,
child: Card(
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(widget.height / 2))),
),
))
],
),
),
);
}
@override
void deactivate() {
// TODO: implement deactivate
super.deactivate();
controller?.stop();
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
controller?.dispose();
}
}
本文分享自微信公众号 - Flutter学习簿(gh_d739155d3b2c)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。动画