N.LanLuu
N.LanLuu

Reputation: 61

How to custom to display text overflow item's bound in scrollable list Flutter?

I want to display text below the item when the item is selected, however, it seems unusual, the text will be displayed centered relative to the image above and displayed full meaning it overflows the space of the 2 items next to it. So is there any way I can fulfill this request?

expected_can_display_text_overflow

Upvotes: 0

Views: 75

Answers (2)

N.LanLuu
N.LanLuu

Reputation: 61

I want to update my current solution, I'm using CustomPainter to draw a list of items and draw text below the item selected.

With the result I have attached, the problem I have when drawing the image is when the image data is successfully retrieved but the image is not displayed on UI until I click on the item to execute setState, the content is displayed after. Code to get image data and Draw Images on canvas:

// Load the network image using NetworkImage class
    NetworkImage(imageUrl).resolve(ImageConfiguration.empty).addListener(
          ImageStreamListener(
            (ImageInfo imageInfo, bool synchronousCall) {
              // Once the image is loaded, draw it onto the canvas
              if (synchronousCall) {
                paintImage(
                  canvas: canvas,
                  rect: rect,
                  image: imageInfo.image,
                  fit: BoxFit.cover, // Adjust the fit as needed
                );
              }
            },
            onError: (exception, stackTrace) {},
            onChunk: (event) {},
          ),
        );

enter image description here

Full code:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: BrandList(),
    );
  }
}

class BrandList extends StatelessWidget {
  final List<String> imageUrls = [
    'https://images.squarespace-cdn.com/content/v1/5ede2122e582b96630a4a73e/1609429780069-GXY5R5AZWUEO8LFIISZR/Chanel+Logo+2021.png',
    'https://99designs-blog.imgix.net/blog/wp-content/uploads/2022/05/image10-3-e1659037365697.png?auto=format&q=60&fit=max&w=930',
    'https://images.squarespace-cdn.com/content/v1/5ede2122e582b96630a4a73e/1609429780069-GXY5R5AZWUEO8LFIISZR/Chanel+Logo+2021.png',
    'https://99designs-blog.imgix.net/blog/wp-content/uploads/2022/05/image10-3-e1659037365697.png?auto=format&q=60&fit=max&w=930',
    'https://images.squarespace-cdn.com/content/v1/5ede2122e582b96630a4a73e/1609429780069-GXY5R5AZWUEO8LFIISZR/Chanel+Logo+2021.png',
    'https://99designs-blog.imgix.net/blog/wp-content/uploads/2022/05/image10-3-e1659037365697.png?auto=format&q=60&fit=max&w=930',
    'https://images.squarespace-cdn.com/content/v1/5ede2122e582b96630a4a73e/1609429780069-GXY5R5AZWUEO8LFIISZR/Chanel+Logo+2021.png',
    'https://99designs-blog.imgix.net/blog/wp-content/uploads/2022/05/image10-3-e1659037365697.png?auto=format&q=60&fit=max&w=930',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Custom Horizontal List'),
      ),
      body: BrandSelectableList(
        imageUrls: imageUrls,
        padding: const EdgeInsets.symmetric(horizontal: 16.0),
      ),
    );
  }
}

class BrandSelectableList extends StatefulWidget {
  final List<String> imageUrls;
  final EdgeInsets padding;
  final int itemSpacing;
  final int itemSize;
  final int borderWidth;
  final Color? borderColor;
  final Color? borderColorSelected;
  final double height;
  final TextStyle? textStyle;

  const BrandSelectableList({
    super.key,
    required this.imageUrls,
    this.borderColor,
    this.borderWidth = 1,
    this.padding = EdgeInsets.zero,
    this.itemSize = 64,
    this.itemSpacing = 24,
    this.height = 92,
    this.borderColorSelected,
    this.textStyle,
  });

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

class _BrandSelectableListState extends State<BrandSelectableList> {
  final ScrollController _scrollController = ScrollController();
  int selectedIndex = -1;

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: EdgeInsets.only(top: 2.0, left: widget.padding.left, right: widget.padding.right),
      scrollDirection: Axis.horizontal,
      controller: _scrollController,
      child: SizedBox(
        height: widget.height,
        child: GestureDetector(
          onTapDown: (details) {
            int index = (details.localPosition.dx / (widget.itemSize + widget.itemSpacing)).floor();
            setState(() {
              selectedIndex = index;
            });
          },
          child: CustomPaint(
            size: Size(
              widget.itemSize * widget.imageUrls.length +
                  widget.itemSpacing * (widget.imageUrls.length - 1),
              widget.height,
            ),
            painter: SingleSelectedListPainter(
              imageUrls: widget.imageUrls,
              selectedIndex: selectedIndex,
              itemWidth: widget.itemSize,
              itemSpacing: widget.itemSpacing,
              padding: widget.padding,
              borderWidth: widget.borderWidth,
              borderColor: widget.borderColor,
              borderColorSelected: widget.borderColorSelected,
              height: widget.height,
            ),
          ),
        ),
      ),
    );
  }
}

class SingleSelectedListPainter extends CustomPainter {
  final int selectedIndex;
  final List<String> imageUrls;
  final EdgeInsets padding;
  final int itemSpacing;
  final int itemWidth;
  final int borderWidth;
  final Color? borderColor;
  final Color? borderColorSelected;
  final double height;
  final TextStyle? textStyle;

  SingleSelectedListPainter({
    required this.imageUrls,
    required this.selectedIndex,
    required this.padding,
    required this.itemSpacing,
    required this.itemWidth,
    required this.borderWidth,
    this.borderColor,
    this.borderColorSelected,
    required this.height,
    this.textStyle,
  });

  @override
  void paint(Canvas canvas, Size size) {
    Path clipPath = Path();

    for (int i = 0; i < imageUrls.length; i++) {
      double startX = i * (itemWidth + itemSpacing).toDouble();
      Rect itemRect =
          Rect.fromPoints(Offset(startX, 0), Offset(startX + itemWidth, itemWidth.toDouble()));
      drawNetworkImage(canvas, clipPath, itemRect, imageUrls[i], i);
      if (selectedIndex == i) {
        drawTextBelowImage(canvas, itemRect, "Selected item at index $i", i);
      }
    }
  }

  void drawTextBelowImage(Canvas canvas, Rect rect, String text, int index) {
    final size = (TextPainter(
      maxLines: 1,
      text: TextSpan(
        text: text,
        style: const TextStyle(color: Colors.black, fontSize: 11.0, fontWeight: FontWeight.w600),
      ),
      textAlign: TextAlign.center,
      textDirection: TextDirection.ltr,
    )..layout())
        .size;

    TextPainter textPainter = TextPainter(
      text: TextSpan(
        text: text,
        style: const TextStyle(color: Colors.black, fontSize: 11.0, fontWeight: FontWeight.w600),
      ),
      textAlign: index == 0
          ? TextAlign.start
          : index == imageUrls.length - 1
              ? TextAlign.end
              : TextAlign.center,
      textDirection: index == imageUrls.length - 1 ? TextDirection.rtl : TextDirection.ltr,
    );

    textPainter.layout(maxWidth: size.width < 72 ? rect.width : size.width);

    // Position the text below the circle image
    double textY = height - textPainter.height;
    double textX = index == 0
        ? rect.left
        : index == imageUrls.length - 1
            ? rect.left - textPainter.width + itemWidth
            : rect.center.dx - textPainter.width / 2;

    textPainter.paint(canvas, Offset(textX, textY));
  }

  void drawNetworkImage(Canvas canvas, Path clipPath, Rect rect, String imageUrl, int index) {
    final size = Size(rect.width, rect.height);
    canvas.save();

    Paint borderPaint = Paint()
      ..color =
          index == selectedIndex ? borderColorSelected ?? Colors.black : borderColor ?? Colors.grey
      ..style = PaintingStyle.stroke
      ..strokeWidth = borderWidth.toDouble();

    canvas.drawCircle(
      Offset((rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2),
      size.width / 2,
      borderPaint,
    );

    /// Clip the canvas to draw the image within the circle
    clipPath.addOval(
      Rect.fromCircle(
        center: Offset((rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2),
        radius: size.width / 2,
      ),
    );

    canvas.clipPath(
      clipPath,
      doAntiAlias: false,
    );

    // Load the network image using NetworkImage class
    NetworkImage(imageUrl).resolve(ImageConfiguration.empty).addListener(
          ImageStreamListener(
            (ImageInfo imageInfo, bool synchronousCall) {
              // Once the image is loaded, draw it onto the canvas
              if (synchronousCall) {
                paintImage(
                  canvas: canvas,
                  rect: rect,
                  image: imageInfo.image,
                  fit: BoxFit.cover, // Adjust the fit as needed
                );
              }
            },
            onError: (exception, stackTrace) {},
            onChunk: (event) {},
          ),
        );
    canvas.restore();
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

Upvotes: 0

Ravindra S. Patil
Ravindra S. Patil

Reputation: 14885

Try below code it can hope it helps to you. Refer ValueNotifier and ValueListenableBuilder for changing the state. Change Text and color of container as per your need.

Note: You can use setState for changing the state as well.

class ExampleApp extends StatelessWidget {
  ExampleApp({Key? key}) : super(key: key);
  ValueNotifier<int> selectedContainer = ValueNotifier<int>(-1);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SizedBox(
          height: 100,
          child: ListView.builder(
            itemCount: 10,
            shrinkWrap: true,
            scrollDirection: Axis.horizontal,
            itemBuilder: (context, index) {
              return ValueListenableBuilder(
                  valueListenable: selectedContainer,
                  builder: (context, selectedIndex, child) {
                    return GestureDetector(
                      onTap: () {
                        selectedContainer.value = index;
                      },
                      child: Column(
                        children: [
                          Container(
                            height: 60,
                            width: 60,
                            margin: EdgeInsets.symmetric(horizontal: 5),
                            decoration: BoxDecoration(
                                color: Colors.grey,
                                shape: BoxShape.circle,
                                border: Border.all(
                                    color: selectedContainer.value == index
                                        ? Colors.red
                                        : Colors.transparent)),
                          ),
                          SizedBox(height: 15),
                          selectedContainer.value == index
                              ? Text(
                                  'Index - $index',
                                  textAlign: TextAlign.center,
                                )
                              : SizedBox(),
                        ],
                      ),
                    );
                  },
              );
            },
          ),
        ),
      ),
    );
  }
}

Result Screens: Before item selected

enter image description here

After Item selected

image1

enter image description here

Upvotes: 0

Related Questions