【问题标题】:Add typing indicator on bot (flutter)在机器人上添加打字指示器(颤振)
【发布时间】:2020-12-11 09:10:41
【问题描述】:

我正在开发一个聊天机器人,我想在机器人回复用户之前添加一个打字指示器,我曾尝试在用户使用未来延迟输入消息后在小部件之间切换,但它根本不起作用。

以下代码演示了我如何尝试使用延迟的未来:

  bool _nextWidget = false;
  @override
  void initState() {
    super.initState();
  }

  void myMethod() {
    Future.delayed(
        const Duration(
          seconds: 5,
          milliseconds: 500,
        ),
            () {
          if (this.mounted) {
            setState(() {
              _nextWidget = true;
            });
          }
        });
  }

  Widget bot(String message)  {
    myMethod();
    return _nextWidget ?  botMessage(message) : botInd();
  }

  Widget botInd() {
    return Container(
        alignment: Alignment.bottomLeft,
        margin: EdgeInsets.only(top: 20),
        child: Container(
            constraints: BoxConstraints(maxWidth: 75, maxHeight: 100),
            child: JumpingDotsProgressIndicator(fontSize: 50.0, color: Colors.white)
        )
    );
  }

  Widget botMessage(String message) {
    return ChatBubble(
        clipper: ChatBubbleClipper2(type: BubbleType.receiverBubble),
        alignment: Alignment.bottomLeft,
        margin: EdgeInsets.only(top: 20),
        backGroundColor: Colors.white,
        child: Container(
            constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.7),
            child: Text(
                message,
                style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold)
            )
        )
    );
  }

【问题讨论】:

    标签: flutter dart flutter-layout flutter-animation


    【解决方案1】:

    查看https://flutter.dev/docs/cookbook/effects/typing-indicator,它解释了如何使用代码示例创建打字指示器的所有必要步骤。

    import 'dart:math';
    
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter/rendering.dart';
    
    void main() {
      runApp(
        const MaterialApp(
          home: ExampleIsTyping(),
          debugShowCheckedModeBanner: false,
        ),
      );
    }
    
    const _backgroundColor = Color(0xFF333333);
    
    class ExampleIsTyping extends StatefulWidget {
      const ExampleIsTyping({
        Key? key,
      }) : super(key: key);
    
      @override
      _ExampleIsTypingState createState() => _ExampleIsTypingState();
    }
    
    class _ExampleIsTypingState extends State<ExampleIsTyping> {
      bool _isSomeoneTyping = false;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: _backgroundColor,
          appBar: AppBar(
            title: const Text('Typing Indicator'),
          ),
          body: Column(
            children: [
              Expanded(
                child: _buildMessages(),
              ),
              Align(
                alignment: Alignment.bottomLeft,
                child: TypingIndicator(
                  showIndicator: _isSomeoneTyping,
                ),
              ),
              _buildIsTypingSimulator(),
            ],
          ),
        );
      }
    
      Widget _buildMessages() {
        return ListView.builder(
          padding: const EdgeInsets.symmetric(vertical: 8.0),
          itemCount: 25,
          reverse: true,
          itemBuilder: (context, index) {
            return Padding(
              padding: const EdgeInsets.only(left: 100.0),
              child: FakeMessage(isBig: index.isOdd),
            );
          },
        );
      }
    
      Widget _buildIsTypingSimulator() {
        return Container(
          color: Colors.grey,
          padding: const EdgeInsets.all(16),
          child: Center(
            child: CupertinoSwitch(
              onChanged: (newValue) {
                setState(() {
                  _isSomeoneTyping = newValue;
                });
              },
              value: _isSomeoneTyping,
            ),
          ),
        );
      }
    }
    
    class TypingIndicator extends StatefulWidget {
      const TypingIndicator({
        Key? key,
        this.showIndicator = false,
        this.bubbleColor = const Color(0xFF646b7f),
        this.flashingCircleDarkColor = const Color(0xFF333333),
        this.flashingCircleBrightColor = const Color(0xFFaec1dd),
      }) : super(key: key);
    
      final bool showIndicator;
      final Color bubbleColor;
      final Color flashingCircleDarkColor;
      final Color flashingCircleBrightColor;
    
      @override
      _TypingIndicatorState createState() => _TypingIndicatorState();
    }
    
    class _TypingIndicatorState extends State<TypingIndicator>
        with TickerProviderStateMixin {
      late AnimationController _appearanceController;
    
      late Animation<double> _indicatorSpaceAnimation;
    
      late Animation<double> _smallBubbleAnimation;
      late Animation<double> _mediumBubbleAnimation;
      late Animation<double> _largeBubbleAnimation;
    
      late AnimationController _repeatingController;
      final List<Interval> _dotIntervals = const [
        Interval(0.25, 0.8),
        Interval(0.35, 0.9),
        Interval(0.45, 1.0),
      ];
    
      @override
      void initState() {
        super.initState();
    
        _appearanceController = AnimationController(
          vsync: this,
        )..addListener(() {
            setState(() {});
          });
    
        _indicatorSpaceAnimation = CurvedAnimation(
          parent: _appearanceController,
          curve: const Interval(0.0, 0.4, curve: Curves.easeOut),
          reverseCurve: const Interval(0.0, 1.0, curve: Curves.easeOut),
        ).drive(Tween<double>(
          begin: 0.0,
          end: 60.0,
        ));
    
        _smallBubbleAnimation = CurvedAnimation(
          parent: _appearanceController,
          curve: const Interval(0.0, 0.5, curve: Curves.elasticOut),
          reverseCurve: const Interval(0.0, 0.3, curve: Curves.easeOut),
        );
        _mediumBubbleAnimation = CurvedAnimation(
          parent: _appearanceController,
          curve: const Interval(0.2, 0.7, curve: Curves.elasticOut),
          reverseCurve: const Interval(0.2, 0.6, curve: Curves.easeOut),
        );
        _largeBubbleAnimation = CurvedAnimation(
          parent: _appearanceController,
          curve: const Interval(0.3, 1.0, curve: Curves.elasticOut),
          reverseCurve: const Interval(0.5, 1.0, curve: Curves.easeOut),
        );
    
        _repeatingController = AnimationController(
          vsync: this,
          duration: const Duration(milliseconds: 1500),
        );
    
        if (widget.showIndicator) {
          _showIndicator();
        }
      }
    
      @override
      void didUpdateWidget(TypingIndicator oldWidget) {
        super.didUpdateWidget(oldWidget);
    
        if (widget.showIndicator != oldWidget.showIndicator) {
          if (widget.showIndicator) {
            _showIndicator();
          } else {
            _hideIndicator();
          }
        }
      }
    
      @override
      void dispose() {
        _appearanceController.dispose();
        _repeatingController.dispose();
        super.dispose();
      }
    
      void _showIndicator() {
        _appearanceController
          ..duration = const Duration(milliseconds: 750)
          ..forward();
        _repeatingController.repeat();
      }
    
      void _hideIndicator() {
        _appearanceController
          ..duration = const Duration(milliseconds: 150)
          ..reverse();
        _repeatingController.stop();
      }
    
      @override
      Widget build(BuildContext context) {
        return AnimatedBuilder(
          animation: _indicatorSpaceAnimation,
          builder: (context, child) {
            return SizedBox(
              height: _indicatorSpaceAnimation.value,
              child: child,
            );
          },
          child: Stack(
            children: [
              _buildAnimatedBubble(
                animation: _smallBubbleAnimation,
                left: 8,
                bottom: 8,
                bubble: _buildCircleBubble(8),
              ),
              _buildAnimatedBubble(
                animation: _mediumBubbleAnimation,
                left: 10,
                bottom: 10,
                bubble: _buildCircleBubble(16),
              ),
              _buildAnimatedBubble(
                animation: _largeBubbleAnimation,
                left: 12,
                bottom: 12,
                bubble: _buildStatusBubble(),
              ),
            ],
          ),
        );
      }
    
      Widget _buildAnimatedBubble({
        required Animation<double> animation,
        required double left,
        required double bottom,
        required Widget bubble,
      }) {
        return Positioned(
          left: left,
          bottom: bottom,
          child: AnimatedBuilder(
            animation: animation,
            builder: (context, child) {
              return Transform.scale(
                scale: animation.value,
                alignment: Alignment.bottomLeft,
                child: child,
              );
            },
            child: bubble,
          ),
        );
      }
    
      Widget _buildCircleBubble(double size) {
        return Container(
          width: size,
          height: size,
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            color: widget.bubbleColor,
          ),
        );
      }
    
      Widget _buildStatusBubble() {
        return Container(
          width: 85,
          height: 44,
          padding: const EdgeInsets.symmetric(horizontal: 8),
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(27),
            color: widget.bubbleColor,
          ),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              _buildFlashingCircle(0),
              _buildFlashingCircle(1),
              _buildFlashingCircle(2),
            ],
          ),
        );
      }
    
      Widget _buildFlashingCircle(int index) {
        return AnimatedBuilder(
          animation: _repeatingController,
          builder: (context, child) {
            final circleFlashPercent =
                _dotIntervals[index].transform(_repeatingController.value);
            final circleColorPercent = sin(pi * circleFlashPercent);
    
            return Container(
              width: 12,
              height: 12,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: Color.lerp(widget.flashingCircleDarkColor,
                    widget.flashingCircleBrightColor, circleColorPercent),
              ),
            );
          },
        );
      }
    }
    
    @immutable
    class FakeMessage extends StatelessWidget {
      const FakeMessage({
        Key? key,
        required this.isBig,
      }) : super(key: key);
    
      final bool isBig;
    
      @override
      Widget build(BuildContext context) {
        return Container(
          margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 24.0),
          height: isBig ? 128.0 : 36.0,
          decoration: BoxDecoration(
            borderRadius: const BorderRadius.all(Radius.circular(8.0)),
            color: Colors.grey.shade300,
          ),
        );
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-14
      • 1970-01-01
      • 1970-01-01
      • 2019-01-10
      • 2016-08-21
      • 1970-01-01
      • 2019-03-01
      • 1970-01-01
      相关资源
      最近更新 更多