Flutter之自定義仿IOS控件Switcher

想實現一個帶文字 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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。動畫

相關文章
相關標籤/搜索