15
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

the sense advent calendarAdvent Calendar 2021

Day 15

【Flutter】アニメーションを使ったリッチなボタン6種類をまとめて紹介!

Posted at

こんにちは!現在スタートアップでFlutterエンジニアをしているAosanoriと申します!
アプリをおしゃれにしたり、UI/UXを上げたりするためにはアニメーションが不可欠ですが作るのは手間がかかりますよね!
そこで今回は皆さんの手間を少しでも減らせればと思い、リッチなアニメーションを使ったボタンをまとめて紹介していこうと思います!

1. 王道!ローディングボタン

最初はSNSの認証などで使えそうなローデングボタンです!
また認証が成功したかどうか一目でわかるのでUXは向上すると思います!処理が進んでいるかがユーザーにとってわかるので離脱ユーザーを減らせることができます!

ezgif.com-gif-maker-2.gif

import 'package:progress_state_button/iconed_button.dart';
import 'package:progress_state_button/progress_button.dart';

ProgressButton.icon(
  iconedButtons: {
    ButtonState.idle: const IconedButton(
        text: "Login",
        icon: Icon(Icons.send,color:Colors.white),
        color: Colors.black),
    ButtonState.loading: const IconedButton(
        text: "Loading", color: Colors.black),
    ButtonState.fail: IconedButton(
        text: "Failed",
        icon: const Icon(Icons.cancel, color: Colors.white),
        color: Colors.red.shade300),
    ButtonState.success: IconedButton(
        text: "Success",
        icon: const Icon(
          Icons.check_circle,
          color: Colors.white,
        ),
        color: Colors.green.shade400)
  },
  onPressed: () async => onPressedProgressButton(),
  state: progressButtonState,
)

2. 心が満ちる!いいねボタン

2つ目はいいねボタンです!
ハートに動きがあるので思わず押したくなりますね!またここで使われているLottieというパッケージはアニメーションを用いたコンポーネントの作成を容易にしてくれるのでおすすめです!

ezgif.com-gif-maker-2.gif

import 'package:lottie/lottie.dart';

class FavoriteButton extends StatefulWidget {
  const FavoriteButton({Key? key}) : super(key: key);
  @override
  FavoriteButtonState createState() => FavoriteButtonState();
}

class FavoriteButtonState extends State<FavoriteButton>
    with TickerProviderStateMixin {
  late final AnimationController _controller;

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(vsync: this);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () {
        if (_controller.isCompleted) {
          _controller.reset();
        } else {
          _controller.forward();
        }
      },
      backgroundColor: Colors.white,
      child: Lottie.network(
        'https://assets1.lottiefiles.com/packages/lf20_ADzN7G.json',
        controller: _controller,
        repeat: true,
        onLoaded: (composition) {
          _controller.duration = composition.duration;
        },
      ),
    );
  }
}

3. 柔らかそう!弾むボタン

お次は弾むボタンです!
HapticFeedbackと組み合わせることでユーザーのアクションに対してフィードバックがあるのでUXの向上につながると思います!

ezgif.com-gif-maker-3.gif

import 'package:spring_button/spring_button.dart';

Container(
  height: 60,
  child: SpringButton(
    SpringButtonType.WithOpacity,
    Padding(
      padding: const EdgeInsets.all(12.5),
      child: Container(
        decoration: const BoxDecoration(
          color: Colors.green,
          borderRadius: BorderRadius.all(Radius.circular(10.0)),
        ),
        child: const Center(
          child: Text(
            'Push',
            style: TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.bold,
              fontSize: 12.5,
            ),
          ),
        ),
      ),
    ),
    onTapDown: (_) {},
    onLongPress: null,
    onLongPressEnd: null,
  ),
)

4. 縦に伸びるFloatingActionButton

今度は縦に伸びるFloatingActionButtonです!
画面を必要な時にしか占有しないのがいいですね!

ezgif.com-gif-maker-6.gif

class FancyFab extends StatefulWidget {
  final Function() onPressed;
  final String tooltip;
  final IconData icon;

  const FancyFab({required this.onPressed, required this.tooltip, required this.icon,Key? key,}):super(key: key);

  @override
  _FancyFabState createState() => _FancyFabState();
}

class _FancyFabState extends State<FancyFab>
    with SingleTickerProviderStateMixin {
  bool isOpened = false;
  late AnimationController _animationController;
  late Animation<Color?> _buttonColor;
  late Animation<double> _animateIcon;
  late Animation<double> _translateButton;
  final _curve = Curves.easeOut;
  final _fabHeight = 56.0;

  @override
  initState() {
    /* アニメーション初期化 */
    _animationController =
        AnimationController(vsync: this, duration: const Duration(milliseconds: 500))
          ..addListener(() {
            setState(() {});
          });
    _animateIcon =
        Tween<double>(begin: 0.0, end: 1.0).animate(_animationController);
    /* ボタンの色の変わり方を定義 */
    _buttonColor = ColorTween(
      begin: Colors.blue,
      end: Colors.red,
    ).animate(CurvedAnimation(
      parent: _animationController,
      curve: const Interval(
        0.00,
        1.00,
        curve: Curves.linear,
      ),
    ),);
    /* 浮き出てくるボタン浮き出方を定義 */
    _translateButton = Tween<double>(
      begin: _fabHeight,
      end: -14.0,
    ).animate(CurvedAnimation(
      parent: _animationController,
      curve: Interval(
        0.0,
        0.75,
        curve: _curve,
      ),
    ));
    super.initState();
  }

  @override
  dispose() {
    _animationController.dispose();
    super.dispose();
  }
  
  // アニメーションを走らす
  void animate() {
    if (!isOpened) {
      _animationController.forward();
    } else {
      _animationController.reverse();
    }
    isOpened = !isOpened;
  }

  Widget add() {
    return const FloatingActionButton(
      onPressed: null,
      tooltip: 'Add',
      child: Icon(Icons.add),
    );
  }

  Widget image() {
    return const FloatingActionButton(
      onPressed: null,
      tooltip: 'Image',
      child: Icon(Icons.image),
    );
  }

  Widget inbox() {
    return const FloatingActionButton(
      onPressed: null,
      tooltip: 'Inbox',
      child: Icon(Icons.inbox),
    );
  }

  Widget toggle() {
    return FloatingActionButton(
      backgroundColor: _buttonColor.value,
      onPressed: animate,
      tooltip: 'Toggle',
      child: AnimatedIcon(
        icon: AnimatedIcons.menu_close,
        progress: _animateIcon,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.end,
      children: <Widget>[
        Transform(
          transform: Matrix4.translationValues(
            0.0,
            _translateButton.value * 3.0,
            0.0,
          ),
          child: add(),
        ),
        Transform(
          transform: Matrix4.translationValues(
            0.0,
            _translateButton.value * 2.0,
            0.0,
          ),
          child: image(),
        ),
        Transform(
          transform: Matrix4.translationValues(
            0.0,
            _translateButton.value,
            0.0,
          ),
          child: inbox(),
        ),
        toggle(),
      ],
    );
  }
}

5. 伸びる!共有ボタン

5つ目は横に伸びる共有ボタンです!
これもボタンで画面を占有させたくない時に使えそうです!

ezgif.com-gif-maker-4.gif

class ShareButton extends StatefulWidget {
  const ShareButton({Key? key}) : super(key: key);
  @override
  _ShareButtonState createState() => _ShareButtonState();
}

class _ShareButtonState extends State<ShareButton> {
  bool isOpen = false;

  _toggleShare() {
    setState(() {
      isOpen = !isOpen;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        AnimatedContainer(
          duration: const Duration(milliseconds: 350),
          curve: Curves.fastOutSlowIn,
          width: isOpen ? 240 : 48,
          height: 48,
          decoration: ShapeDecoration(
            color: Colors.grey[400],
            shape: const StadiumBorder(),
          ),
        ),
        Container(
          width: 40,
          margin: const EdgeInsets.only(left: 4),
          decoration: const BoxDecoration(
            color: Colors.white,
            shape: BoxShape.circle,
          ),
          child: AnimatedCrossFade(
            duration: const Duration(milliseconds: 450),
            firstChild: IconButton(
              icon: const Icon(Icons.share),
              onPressed: () => _toggleShare(),
            ),
            secondChild: IconButton(
              icon: const Icon(Icons.close),
              onPressed: () => _toggleShare(),
            ),
            crossFadeState:
                !isOpen ? CrossFadeState.showFirst : CrossFadeState.showSecond,
          ),
        ),
        AnimatedOpacity(
          duration: const Duration(milliseconds: 450),
          opacity: isOpen ? 1 : 0,
          child: Container(
            width: 240,
            padding: const EdgeInsets.only(left: 40),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                IconButton(
                  icon: const Icon(Icons.copy),
                  onPressed: () {},
                ),
                IconButton(
                  icon: const Icon(Icons.ios_share),
                  onPressed: () {},
                ),
                IconButton(
                  icon: const Icon(Icons.iso_sharp),
                  onPressed: () {},
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

6. トグルスイッチ

最後はトグルスイッチです!
スイッチなのでユーザーに二択で選ばせる場合や設定画面に用いるとよさそうです。また直感的でかつ操作が簡単なのでユーザーの操作ミスを減らせると思います!

ezgif.com-gif-maker-5.gif

FlutterSwitch(
  width: 125.0,
  height: 55.0,
  valueFontSize: 25.0,
  toggleSize: 45.0,
  value: status,
  borderRadius: 30.0,
  padding: 8.0,
  showOnOff: true,
  onToggle: (val) {
    setState(
      () {
        status = val;
      },
    );
  },
)

#最後に
いかがだったでしょうか!アニメーションを使ったコンポーネントはアプリにアクセント加えることができアプリをよりおしゃれにできるだけでなくUI/UXも上がるのでアプリの活性化にも役に立つのでしつこくない程度に積極的に使っていきたい所です!
質問やアドバイス等ありましたらコメントしていただけると幸いです!
あとTwitterもやってるのでフォローお願いします!
駄文ですがここまで読んでいただきありがとうございました!

#参考

15
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?