Flutter-實(shí)現(xiàn)仿微信群頭像功能

Flutter實(shí)現(xiàn)仿微信群頭像功能

需求

在Flutter項(xiàng)目中,實(shí)現(xiàn)一個(gè)類似于微信群頭像的控件。該控件能夠顯示多個(gè)頭像圖片,并且根據(jù)圖片的數(shù)量,自動(dòng)調(diào)整布局,確保每個(gè)圖片都是正方形,且有統(tǒng)一的邊框和間距。

效果

image.png

該控件能夠?qū)崿F(xiàn)以下效果:

  1. 根據(jù)圖片數(shù)量,動(dòng)態(tài)調(diào)整圖片布局。
  2. 每個(gè)圖片都有統(tǒng)一的邊框和間距。
  3. 圖片顯示為正方形,并支持圓角效果。
  4. 使用網(wǎng)絡(luò)圖片,并支持緩存和占位符。

實(shí)現(xiàn)思路

  1. 控件設(shè)計(jì):創(chuàng)建一個(gè)AvatarGroup控件,接受圖片URL列表和一些布局參數(shù)。
  2. 布局計(jì)算:根據(jù)圖片數(shù)量,動(dòng)態(tài)計(jì)算每個(gè)圖片的尺寸和位置。
  3. 圖片顯示:使用CachedNetworkImage加載網(wǎng)絡(luò)圖片,支持緩存、占位符和錯(cuò)誤顯示。
  4. 邊框和圓角:使用ContainerBoxDecoration實(shí)現(xiàn)統(tǒng)一的邊框和圓角效果。

實(shí)現(xiàn)代碼

import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';

class AvatarGroup extends StatelessWidget {
  final List<String> imageUrls;
  final double size;
  final double space;
  final Color color;
  final Color borderColor;
  final double borderWidth;
  final double borderRadius;

  const AvatarGroup({
    Key? key,
    required this.imageUrls,
    this.size = 150.0,
    this.space = 4.0,
    this.color = Colors.grey,
    this.borderWidth = 3.0,
    this.borderColor = Colors.grey,
    this.borderRadius = 4.0,
  }) : super(key: key);

  double get width {
    return size - borderWidth * 2;
  }

  int get itemCount {
    return imageUrls.length;
  }

  double get itemWidth {
    if (itemCount == 1) {
      return width;
    } else if (itemCount >= 2 && itemCount <= 4) {
      return (width - space) / 2;
    } else {
      return (width - 2 * space) / 3;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: color,
        border: Border.all(color: borderColor, width: borderWidth),
        borderRadius: BorderRadius.circular(borderRadius),
      ),
      width: size,
      height: size,
      child: Stack(
        children: _buildAvatarStack(),
      ),
    );
  }

  List<Widget> _buildAvatarStack() {
    List<Widget> avatars = [];
    for (int i = 0; i < imageUrls.length; i++) {
      double left = 0;
      double top = 0;
      if (itemCount == 1) {
        left = 0;
        top = 0;
      } else if (itemCount == 2) {
        left = i * itemWidth + i * space;
        top = (width - itemWidth) / 2;
      } else if (itemCount == 3) {
        if (i == 0) {
          left = (width - itemWidth) / 2;
          top = 0;
        } else {
          left = (i - 1) * itemWidth + (i - 1) * space;
          top = itemWidth + space;
        }
      } else if (itemCount == 4) {
        if (i == 0 || i == 1) {
          left = i * itemWidth + i * space;
          top = 0;
        } else {
          left = (i - 2) * itemWidth + (i - 2) * space;
          top = itemWidth + space;
        }
      } else if (itemCount == 5) {
        if (i == 0 || i == 1) {
          left =
              (width - itemWidth * 2 - space) / 2 + i * itemWidth + i * space;
          top = (width - itemWidth * 2 - space) / 2;
        } else {
          left = (i - 2) * itemWidth + (i - 2) * space;
          top = (width - itemWidth * 2 - space) / 2 + itemWidth + space;
        }
      } else if (itemCount == 6) {
        var topOffset = (width - 2 * itemWidth - space) / 2;
        left = (i % 3) * itemWidth + (i % 3) * space;
        top = topOffset + (i / 3).floor() * itemWidth + (i / 3).floor() * space;
      } else if (itemCount == 7) {
        if (i == 0) {
          left = (width - itemWidth) / 2;
          top = 0;
        } else {
          left = ((i - 1) % 3) * itemWidth + ((i - 1) % 3) * space;
          top = itemWidth +
              space +
              ((i - 1) / 3).floor() * itemWidth +
              ((i - 1) / 3).floor() * space;
        }
      } else if (itemCount == 8) {
        if (i == 0 || i == 1) {
          left =
              (width - itemWidth * 2 - space) / 2 + i * itemWidth + i * space;
          top = 0;
        } else {
          left = ((i - 2) % 3) * itemWidth + ((i - 2) % 3) * space;
          top = itemWidth +
              space +
              ((i - 2) / 3).floor() * itemWidth +
              ((i - 2) / 3).floor() * space;
        }
      } else if (itemCount == 9) {
        left = (i % 3) * itemWidth + (i % 3) * space;
        top = (i / 3).floor() * itemWidth + (i / 3).floor() * space;
      }

      avatars.add(Positioned(
        left: left,
        top: top,
        child: ClipRect(
          child: CachedNetworkImage(
            imageUrl: imageUrls[i],
            placeholder: (context, url) => const CircularProgressIndicator(),
            errorWidget: (context, url, error) => const Icon(Icons.error),
            width: itemWidth,
            height: itemWidth,
            fit: BoxFit.cover,
          ),
        ),
      ));
    }

    return avatars;
  }
}

使用

import 'package:flutter/material.dart';
import 'package:flutter_xy/xydemo/image/avatar/avatar_grid.dart';

import '../../../widgets/xy_app_bar.dart';

class AvatarGridPage extends StatelessWidget {
  AvatarGridPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: XYAppBar(
        title: "微信群頭像",
        onBack: () {
          Navigator.pop(context);
        },
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: GridView.builder(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            crossAxisSpacing: 8.0,
            mainAxisSpacing: 8.0,
          ),
          itemCount: 9,
          itemBuilder: (context, index) {
            return LayoutBuilder(builder: (context, constraints) {
              return AvatarGroup(
                size: constraints.maxWidth,
                color: Colors.grey.withAlpha(50),
                borderColor: Colors.redAccent.withAlpha(80),
                borderWidth: 2,
                imageUrls: List.generate(
                    index + 1, (i) => _imageUrls[i % _imageUrls.length]),
              );
            });
          },
        ),
      ),
    );
  }

  final List<String> _imageUrls = [
    'https://files.mdnice.com/user/34651/0d938792-603e-4945-a1d8-e53e605693d8.jpeg',
    'https://files.mdnice.com/user/34651/a3b1fd72-ef80-4e31-8a33-2a57c4d115ce.jpeg',
    'https://files.mdnice.com/user/34651/06de6046-bf3a-454c-a75b-6beeba78408b.jpeg',
    'https://files.mdnice.com/user/34651/010ac7cb-9aa9-4a4d-93bb-14dc2cc0d994.jpeg',
    'https://files.mdnice.com/user/34651/d88604e3-0dae-46f0-ab3f-d9e016516401.jpeg',
    'https://files.mdnice.com/user/34651/91a53974-7bf7-47ba-a303-40d5fb61e31f.jpeg',
    'https://files.mdnice.com/user/34651/6b7ca51c-65d0-4f35-b5c1-2c17ef494fd8.jpeg',
    'https://files.mdnice.com/user/34651/0fbdd801-66d8-487c-bcdd-9afcaa611541.jpeg',
    'https://files.mdnice.com/user/34651/0fbdd801-66d8-487c-bcdd-9afcaa611541.jpeg',
  ];
}

結(jié)束語(yǔ)

通過上述實(shí)現(xiàn),我們成功地在Flutter中創(chuàng)建了一個(gè)仿微信群頭像的控件。這個(gè)控件不僅可以動(dòng)態(tài)調(diào)整布局,還支持網(wǎng)絡(luò)圖片的緩存和占位符顯示。希望這篇文章對(duì)你在Flutter項(xiàng)目中實(shí)現(xiàn)類似功能有所幫助。如果你有任何問題或建議,歡迎訪問我的GitHub項(xiàng)目:github.com/yixiaolunhui/flutter_xy與我交流。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 目前學(xué)習(xí)開發(fā)flutter已經(jīng)有快1年的時(shí)間了,大大小小也做了七七八八個(gè)項(xiàng)目。項(xiàng)目從大到小都有,之前公司開發(fā)的項(xiàng)目...
    iOS超級(jí)洋閱讀 1,674評(píng)論 0 6
  • Flutter頁(yè)面-基礎(chǔ)Widget [TOC] Widget StatelessWidget和StatefulW...
    喂_balabala閱讀 244評(píng)論 0 1
  • Drat語(yǔ)法 1、基本語(yǔ)法 返回修飾詞 main (){} 2、基本數(shù)據(jù)類型 Drat是強(qiáng)類型語(yǔ)言var 代表不確...
    十年之后_b94a閱讀 1,206評(píng)論 0 4
  • 規(guī)則 (1)為了提高閱讀星,組件嵌套不能多于3層 操作 當(dāng)有多個(gè)模擬器的時(shí)候,可以指定某個(gè)模擬器運(yùn)行flutter...
    MR_詹閱讀 471評(píng)論 0 1
  • Jetpack Compose出來有一段時(shí)間了,一直都沒有去嘗試,這次有點(diǎn)想法去玩一玩這個(gè)聲明性界面工具,就以“原...
    付十一v閱讀 1,377評(píng)論 0 5

友情鏈接更多精彩內(nèi)容