颤振:CircleAvatar与后备文本

时间:2017-10-18 11:32:53

标签: dart flutter

我正在学习Flutter,并希望像内置的Widget一样制作CircleAvatar。但是,我希望行为

  • 同时指定图像(NetworkImage)和缩写(即BB)
  • 在未加载图片时显示缩写
  • 如果图像加载,则显示图像并删除缩写

以下代码有效,但在聊天演示中使用时,随着多个MyAvatars的添加而分崩离析。 在initState上进行断点显示始终使用输入的第一个消息文本调用它 - 而不是我所期望的。 它也像图像“重新加载”一样闪烁。似乎小部件正在以我不理解的方式重用。

class MyAvatar extends StatefulWidget {                     
  NetworkImage image;
  MyAvatar({this.text}) {
    debugPrint("MyAvatar " + this.text);
    if (text.contains('fun')) {
      this.image = new NetworkImage("https://cdn3.iconfinder.com/data/icons/minicons-for-web-sites/24/minicons2-14-512.png");
    }
  }
  final String text;
  @override                                                        
  MyAvatarState createState() {
    return new MyAvatarState();
  }                    
}

class MyAvatarState extends State<MyAvatar> {
  bool showImage = false;
  @override
  initState() {
    super.initState();
    if (widget.image != null) {
      var completer = widget.image.load(widget.image);
      completer.addListener((info, sync) {
        setState(() {
          showImage = true;
        });
      });
    }
  }

  @override 
  Widget build(BuildContext context) {
    return !showImage ? new CircleAvatar(radius: 40.0, child: new Text(widget.text[0]))
        : new CircleAvatar(radius: 40.0, backgroundImage: widget.image);
  }
}

我仍然遇到麻烦 - 完整代码

import 'package:flutter/material.dart';
// Modify the ChatScreen class definition to extend StatefulWidget.

class ChatScreen extends StatefulWidget {                     //modified
  ChatScreen() {
    debugPrint("ChatScreen - called on hot reload");
  }
  @override                                                        //new
  State createState() {
    debugPrint("NOT on hot reload");
    return new ChatScreenState();
  }                    //new
}

// Add the ChatScreenState class definition in main.dart.

class ChatScreenState extends State<ChatScreen> {
  final List<ChatMessage> _messages = <ChatMessage>[];
  final TextEditingController _textController = new TextEditingController(); //new
  ChatScreenState() {
    debugPrint("ChatScreenState - not called on hot reload");
  }

  @override //new
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: new Text("Friendlychat")),
      body: new Column(                                        //modified
          children: <Widget>[                                         //new
            new Flexible(                                               //new
                child: new ListView.builder(                              //new
                  padding: new EdgeInsets.all(8.0),                       //new
                  reverse: true,                                          //new
                  itemBuilder: (_, int index) => _messages[index],        //new
                  itemCount: _messages.length,                            //new
                )                                                         //new
            ),                                                          //new
            new Divider(height: 1.0),                                   //new
            new Container(                                              //new
              decoration: new BoxDecoration(
                  color: Theme.of(context).cardColor),                   //new
              child: _buildTextComposer(),                         //modified
            ),                                                          //new
          ]                                                            //new
      ),                                                             //new
    );
  }

  Widget _buildTextComposer() {

    return new IconTheme(
        data: new IconThemeData(color: Theme
            .of(context)
            .accentColor),
        child:
        new Container(
            margin: const EdgeInsets.symmetric(horizontal: 8.0),
            child: new Row(
                children: <Widget>[
                  new Container( //new
                    margin: new EdgeInsets.symmetric(horizontal: 4.0), //new
                    child: new IconButton( //new
                        icon: new Icon(Icons.send),
                        onPressed: () =>
                            _handleSubmitted(_textController.text)), //new
                  ),
                  new Flexible(
                      child: new TextField(
                        controller: _textController,
                        onSubmitted: _handleSubmitted,
                        decoration: new InputDecoration.collapsed(
                            hintText: "Send a message"),
                      )
                  ),
                ])
        )
    );
  }

  void _handleSubmitted(String text) {


    _textController.clear();
    ChatMessage message = new ChatMessage(text: text);
    setState(() {
      _messages.insert(0, message);
    });
  }
}

const String _name = "Hardcoded Name";

class ChatMessage extends StatelessWidget {
  ChatMessage({this.text, this.image, this.useImage});
  final String text;
  final NetworkImage image;
  final Map useImage;
  @override
  Widget build(BuildContext context) {
    var use = true; //useImage != null && useImage['use'];

    var image = new NetworkImage("https://cdn3.iconfinder.com/data/icons/minicons-for-web-sites/24/minicons2-14-512.png");
    if (text.contains('bad')) {
      image = new NetworkImage("https://cdn3.iconfinder.com/data/icons/minicons-for-web-sites/24/minicons2-14-512.pngz");
    }
    return new Container(
      margin: const EdgeInsets.symmetric(vertical: 10.0),
      child: new Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          new Container(
            margin: const EdgeInsets.only(right: 16.0),
            child : new CustomCircleAvatar(initials: text[0], myImage: image)
          ),
          new Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              new Text(_name, style: Theme.of(context).textTheme.subhead),
              new Container(
                margin: const EdgeInsets.only(top: 5.0),
                child: new Text(text),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class CustomCircleAvatar extends StatefulWidget {
  NetworkImage myImage;

  String initials;


  CustomCircleAvatar({this.myImage, this.initials}) {
    debugPrint(initials);
  }

  @override
  _CustomCircleAvatarState createState() => new _CustomCircleAvatarState();
}

class _CustomCircleAvatarState extends State<CustomCircleAvatar>{

  bool _checkLoading = true;

  @override
  void initState() {
    if (widget.myImage != null) {
      widget.myImage.resolve(new ImageConfiguration()).addListener((image, sync) {
        if (mounted && image != null) {
          setState(() {
            _checkLoading = false;
          });
        }
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return _checkLoading == true ? new CircleAvatar(child: new Text(widget.initials))
        : new CircleAvatar(backgroundImage: widget.myImage);
  }
}

输入'fun'作为消息,然后'bad'作为第二个 - image 我们的想法是,根据您输入的内容,可能会加载(或不加载)不同的图像。在“无法加载”的情况下,首字母应该保留。

5 个答案:

答案 0 :(得分:3)

您可以通过向ImageStream添加一个可以从ImageConfiguration获取的侦听器来实现此功能,

在这里,我将相同的数据提供给我的ListView您当然可以通过在任何课程中添加List图像和缩写作为字段来自定义,然后使用ListView.builder而是能够通过索引循环它们。

enter image description here

class CustomCircleAvatar extends StatefulWidget {
  NetworkImage myImage;

  String initials;


  CustomCircleAvatar({this.myImage, this.initials});

  @override
  _CustomCircleAvatarState createState() => new _CustomCircleAvatarState();
}

class _CustomCircleAvatarState extends State<CustomCircleAvatar>{

  bool _checkLoading = true;

  @override
  void initState() {
    widget.myImage.resolve(new ImageConfiguration()).addListener((_, __) {
      if (mounted) {
        setState(() {
          _checkLoading = false;
        });
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return _checkLoading == true ? new CircleAvatar(
        child: new Text(widget.initials)) : new CircleAvatar(
      backgroundImage: widget.myImage,);
  }
}

现在您可以像这样使用它:

void main() {
  runApp(new MaterialApp (home: new MyApp()));
}

    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => new _MyAppState();
    }

    class _MyAppState extends State<MyApp> {
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          appBar: new AppBar(title: new Text("Custom Circle Avatar"),),
          body: new ListView(children: new List.generate(20, (int index) {
            return new Container(
              height: 100.0,
              width: 100.0,
              child: new CustomCircleAvatar(myImage: new NetworkImage(
                  "https://www.doginni.cz/front_path/images/dog_circle.png"),
                initials: "Dog",
              ),
            );
          }),),
        );
      }
    }

答案 1 :(得分:1)

这确实非常容易。使用Usage: OCPlatform11.3.2_21REL [-f <path_to_installer_properties_file> | -options] (to execute the installer) where options include: -? show this help text -i [swing | console | silent] specify the user interface mode for the installer -D<name>=<value> specify installer properties -r <path_to_generate_response_file> Generates response file. 并构建适当的CachetNetworkImage

CircleAvatar

答案 2 :(得分:0)

@aziza的答案确实是我一段时间以来才能找到的唯一答案,并且花了我一段时间才能阅读并理解。我尝试实施它,尽管最终确实使它起作用,但仍然存在一些问题。我认为我有一个更具可读性的(至少对我来说是这样!)/最新的答案,这可能有助于某人绊倒这个问题:

class FallBackAvatar extends StatefulWidget {
  final AssetImage image;
  final String initials;
  final TextStyle textStyle;
  final Color circleBackground;

  FallBackAvatar({@required this.image, @required this.initials, @required this.circleBackground, @required this.textStyle});

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

class _FallBackAvatarState extends State<FallBackAvatar> {

  bool _checkLoading = true;

  @override
  initState() {
    super.initState();
    // Add listeners to this class
    ImageStreamListener listener = ImageStreamListener(_setImage, onError: _setError);

    widget.image.resolve(ImageConfiguration()).addListener(listener);
  }

  void _setImage(ImageInfo image, bool sync) {
    setState(() => _checkLoading = false);
    //DO NOT DISPOSE IF IT WILL REBUILD (e.g. Sliver/Builder ListView)
    dispose();
  }

  void _setError(dynamic dyn, StackTrace st) {
    setState(() => _checkLoading = true);
    dispose();
  }

  @override
  Widget build(BuildContext context) {
    return _checkLoading == true ? new CircleAvatar(
        backgroundColor: widget.circleBackground,
        child: new Text(widget.initials, style: widget.textStyle)) : new CircleAvatar(
      backgroundImage: widget.image,
      backgroundColor: widget.circleBackground,);
  }
}

我要手动处理两点,因为我知道在此之后应该不再进行重建(您是否获得了图像?好!除非您是条子或其他东西,否则图像就不会重建)加载?那么就是这样-不再进行重建)。这也可以处理由于某些原因而没有出现AssetImage(在我的情况下是其Asset图像,但您可以使用任何类型的图像提供程序)的错误情况。

第二次编辑,因为我个人的问题最好不在此答案之列。因此,我注意到加载个人资料图像时稍有延迟(例如一秒钟)。但是随后图像泛滥成灾。不喜欢这种过渡,所以这里是一个带有AnimatedSwitcher的过渡:

class FallBackAvatar extends StatefulWidget {
  final AssetImage image;
  final String initials;
  final TextStyle textStyle;
  final Color circleBackground;
  final double radius;
  final int msAnimationDuration;

  FallBackAvatar({@required this.image, @required this.initials, @required this.circleBackground, @required this.textStyle, @required this.radius, this.msAnimationDuration});

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

class _FallBackAvatarState extends State<FallBackAvatar> {

  bool _imgSuccess = false;

  @override
  initState() {
    super.initState();
    // Add listeners to this class
    ImageStreamListener listener = ImageStreamListener(_setImage, onError: _setError);

    widget.image.resolve(ImageConfiguration()).addListener(listener);
  }

  void _setImage(ImageInfo image, bool sync) {
    setState(() => _imgSuccess = true);
  }

  void _setError(dynamic dyn, StackTrace st) {
    setState(() => _imgSuccess = false);
    dispose();
  }

  Widget _fallBackAvatar() {
    return Container(
      height: widget.radius*2,
      width: widget.radius*2,
      decoration: BoxDecoration(
        color: widget.circleBackground,
        borderRadius: BorderRadius.all(Radius.circular(widget.radius))
      ),
      child: Center(child: Text(widget.initials, style: widget.textStyle))
    );
  }


  Widget _avatarImage() {
    return CircleAvatar(
      backgroundImage: widget.image,
      backgroundColor: widget.circleBackground
    );
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedSwitcher(
      duration: Duration(milliseconds: widget.msAnimationDuration ?? 500),
      child: _imgSuccess ? _avatarImage() : _fallBackAvatar(),
    );
  }
}

答案 3 :(得分:0)

这里是具有堆叠体系结构的示例,其中回退是人员图标。 ViewBuilderViewModel只是堆叠体系结构替代中的扩展小部件。 @swidget是功能性小部件。您可以通过StatefulWidget实现相同的功能。

@swidget
Widget avatarView({String userId, double radius = 24}) =>
    ViewBuilder<AvatarViewModel>(
      viewModelBuilder: () => AvatarViewModel(),
      builder: (model) => CircleAvatar(
        radius: radius,
        backgroundColor: CColors.blackThird,
        backgroundImage: NetworkImage(
          Config.photoUrl + userId ?? userService.id,
        ),
        child: model.isFailed ? Icon(EvaIcons.person, size: radius) : null,
        onBackgroundImageError: (e, _) => model.isFailed = e != null,
      ),
    );

class AvatarViewModel extends ViewModel {
  bool _isFailed = false;

  bool get isFailed => _isFailed;

  set isFailed(bool isFailed) {
    _isFailed = isFailed;
    notifyListeners();
  }
}

答案 4 :(得分:0)

其实代码可以更简单:

如果您想在图像不可用时放置文本,您只需使用 foregroundImage 而不是 backgroundImage。

默认会显示文字,图片加载时会覆盖文字,无需处理图片加载状态等

如果您需要知道图像是否有错误,您可以使用 onForegroundImageError 拦截它。

示例函数:

Widget CircleAvatarTest(
    {String? imageUrl,
    String? text,
    double radius = 35,
    Color? backgroundColor}) {
  return CircleAvatar(
    radius: radius,
    child: (text != null)
        ? Center(
            child: Text(text,
                style: TextStyle(
                  color: Colors.white,
                  fontSize: radius * 2 / text.length - 10,
                )),
          )
        : null,
    foregroundImage: imageUrl == null ? null : NetworkImage(imageUrl),
    backgroundColor: backgroundColor,
    //onForegroundImageError: (e,trace){/*....*/},
  );
}