Flutter 类似钉钉群头像

序言

本篇文章主要是介绍如何使用Flutter实现类似钉钉群组头像的效果,使用的技术比较简单。由于是第一次在csdn上写文章,不好之处请多多指教

正文

实现效果

钉钉群组头像主要是取群成员里面的头像和姓名进行拼接的,当成员头像存在的时候,取成员头像,当群成员头像不存在的时候,取名称中的一个字(此处偷懒,取的姓),最终显示的效果如下
在这里插入图片描述

实现思路

数据封装

将传递进来的数据进行从新组装,组装成满足自己需求的对象,这样就能兼容各种不同数据来源的处理了。自定义数据源对象是Clipper,里面包括字段如下
在这里插入图片描述
此处name 就是上面指的名称,backgroundColor 是名称的背景颜色,url 是链接地址,image 是链接加载的图片对象

头像加载

采用Canvas 进行绘制,由于存在头像的情况,需要先把头像全部加载成功以后,才使用Canvas 进行绘画,那么加载头像使用的方法是NetworkImage(url).resolve(ImageConfiguration())
如下
在这里插入图片描述
先将数据组装成Clipper 对象,然后进行循环判断,找出需要加载的头像的Clipper 对象集合,然后定义loadNum变量记录下有多少个需要加载头像的Clipper,每次调用loadImage方法进行加载头像,当成功调用then的时候,返回ui.Image对象的时候,将数据存储到Clipper的image字段里面,如果调用catchError时候,则说明头像加载失败,则需要将Clipper 的type 修改成功 0,当成文字进行处理。当调用whenComplete时候,表示加载完成(无论成功和失败都会调用),此处将loadNum变量-1,判断loadNum是否为0,如果loadNum =0,表示头像已经完成加载了,然后通过Canvas 进行绘画

实现绘画

首先介绍下Painter 和Canvas 的知识点

本文由于不需要使用头像本地缓存技术,所以使用的是CustomPainter 进行绘制的,CustomPainter 是Flutter 自带的Widget对象,里面有两个方法实现

  • void paint(Canvas canvas, Size size)
  • bool shouldRepaint(covariant CustomPainter oldDelegate)
    paint 方法是主要方法,主要是在Canvas 上进行绘制
    shouldRepaint 方法是判断是否需要重新绘制,只需要返回return this != oldDelegate; 就行

由于要整体是一个圆形图片,所以先需要将Canvas 绘制范围设置成一个圆形(此处CustomPainter 的宽高是相同的),通过如下来设置
在这里插入图片描述
然后开始定义Painter 进行画布上进行绘画。
由于有四种摆放效果:

  • 1个Clipper 对象是一整个大圆
  • 2个Clipper 对象是两个半圆
  • 3个Clipper 对象是1个半圆,两个四分一圆
  • 4个Clipper 对象是四个四分之一圆
    这些其实都是与Painter 进行绘画的位置有关,所以可以合并成一个方法进行,只需要传入不同的left,top,right,bottom告诉painter 如果绘画就行,真正需要处理的就是画图片还有绘制文字
    方法如下
 void drawSpliceContent(Canvas canvas, double left, double top, double right,
      double bottom, Clipper clipper) {
    if (clipper.type == 0) {
      Paint paint = Paint()
        ..color = clipper.backgroundColor
        ..isAntiAlias = true
        ..strokeCap = StrokeCap.round
        ..style = PaintingStyle.fill
        ..strokeWidth = 1;
      canvas.drawRect(Rect.fromLTRB(left, top, right, bottom), paint);
      //绘画文字
      ParagraphBuilder _pb = ParagraphBuilder(ParagraphStyle(
          textAlign: TextAlign.center, maxLines: 1, fontSize: 14))
        ..pushStyle(ui.TextStyle(color: Colors.white))
        ..addText(clipper.name);
      ParagraphConstraints constraints =
          ParagraphConstraints(width: (right - left));
      Paragraph paragraph = _pb.build()..layout(constraints);
      canvas.drawParagraph(
          paragraph, Offset(left, (bottom + top - paragraph.height) / 2));
    } else {
      Paint paint = Paint()
        ..color = clipper.backgroundColor
        ..isAntiAlias = true
        ..strokeCap = StrokeCap.round
        ..style = PaintingStyle.fill
        ..strokeWidth = 1;
      canvas.drawImageRect(
          clipper.image,
          //此次直接把图片塞入到目标矩形里面,不然没办法显示完整图片
          Rect.fromCenter(
              center: Offset(clipper.image.width / 2, clipper.image.height / 2),
              width: clipper.image.width.toDouble(),
              height: clipper.image.height.toDouble()),
          Rect.fromLTRB(left, top, right, bottom),
          paint);
    }
  }

自定义CustomPainter全部代码

import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:ui' as ui show Image, TextStyle;
import 'package:flutter_study/custom/customfield/headersplice/Clipper.dart';

class HeaderClipper extends CustomPainter {
 ///要处理的数据
 List<Clipper> clippers;

 HeaderClipper({required this.clippers});

 @override
 void paint(Canvas canvas, Size size) {
   //clipRRect 可以在规定的矩形内进行绘制,超出范围不绘制
   canvas.clipRRect(RRect.fromRectAndRadius(
       Rect.fromCircle(
           center: Offset(size.width / 2, size.height / 2),
           radius: size.width / 2),
       Radius.circular(size.width / 2)));

   double width = size.width;
   double height = size.height;
   if (clippers.length == 1) {
     Clipper clipper = clippers[0];
     // _drawCircle(canvas, size.width, size.height, clipper);
     drawSpliceContent(canvas, 0, 0, width, height, clipper);
   } else if (clippers.length == 2) {
     Clipper clipper0 = clippers[0];
     Clipper clipper1 = clippers[1];
     drawSpliceContent(canvas, 0, 0, width / 2, height, clipper0);
     drawSpliceContent(canvas, width / 2, 0, width, height, clipper1);
   } else if (clippers.length == 3) {
     Clipper clipper0 = clippers[0];
     Clipper clipper1 = clippers[1];
     Clipper clipper2 = clippers[2];
     drawSpliceContent(canvas, 0, 0, width / 2, height, clipper0);
     drawSpliceContent(canvas, width / 2, 0, width, height / 2, clipper1);
     drawSpliceContent(canvas, width / 2, height / 2, width, height, clipper2);
   } else if (clippers.length == 4) {
     Clipper clipper0 = clippers[0];
     Clipper clipper1 = clippers[1];
     Clipper clipper2 = clippers[2];
     Clipper clipper3 = clippers[3];
     drawSpliceContent(canvas, 0, 0, width / 2, height / 2, clipper0);
     drawSpliceContent(canvas, width / 2, 0, width, height / 2, clipper1);
     drawSpliceContent(canvas, 0, height / 2, width / 2, height, clipper2);
     drawSpliceContent(canvas, width / 2, height / 2, width, height, clipper3);
   }
 }

 @override
 bool shouldRepaint(covariant CustomPainter oldDelegate) {
   // TODO: implement shouldRepaint
   return this != oldDelegate;
 }

 void drawSpliceContent(Canvas canvas, double left, double top, double right,
     double bottom, Clipper clipper) {
   if (clipper.type == 0) {
     Paint paint = Paint()
       ..color = clipper.backgroundColor
       ..isAntiAlias = true
       ..strokeCap = StrokeCap.round
       ..style = PaintingStyle.fill
       ..strokeWidth = 1;
     canvas.drawRect(Rect.fromLTRB(left, top, right, bottom), paint);
     //绘画文字
     ParagraphBuilder _pb = ParagraphBuilder(ParagraphStyle(
         textAlign: TextAlign.center, maxLines: 1, fontSize: 14))
       ..pushStyle(ui.TextStyle(color: Colors.white))
       ..addText(clipper.name);
     ParagraphConstraints constraints =
         ParagraphConstraints(width: (right - left));
     Paragraph paragraph = _pb.build()..layout(constraints);
     canvas.drawParagraph(
         paragraph, Offset(left, (bottom + top - paragraph.height) / 2));
   } else {
     Paint paint = Paint()
       ..color = clipper.backgroundColor
       ..isAntiAlias = true
       ..strokeCap = StrokeCap.round
       ..style = PaintingStyle.fill
       ..strokeWidth = 1;
     canvas.drawImageRect(
         clipper.image,
         //此次直接把图片塞入到目标矩形里面,不然没办法显示完整图片
         Rect.fromCenter(
             center: Offset(clipper.image.width / 2, clipper.image.height / 2),
             width: clipper.image.width.toDouble(),
             height: clipper.image.height.toDouble()),
         Rect.fromLTRB(left, top, right, bottom),
         paint);
   }
 }
}

结束语

由于是第一次写csdn 文章,很多地方写的不是很好,而且这个处理头像的思路也不是很牛,只希望能帮到一些人。这也是我自己锻炼的一种方式
下载地址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值