Articles in this section
Category / Section

How to design a custom border shape for messages in Flutter SfChat widget?

7 mins read

In this article, we will learn how to design custom border shapes for incoming and outgoing chat bubbles using the contentShape property in the SfChat widget. By following the steps outlined below, you can personalize the appearance of your chat interface, making it more visually appealing and user-friendly.

Step 1: Define the List of Chat Messages

Define a list of chat messages that will be displayed in the SfChat widget. Each message will include information such as the text, the time the message was sent, the author, and an optional image in the avatar. We will use the ChatMessage class to define these messages.

In the following code snippet, we define two initial messages: one from the outgoing user and another from the incoming user.

late List<ChatMessage> _messages;

 @override
 void initState() {
   _messages = <ChatMessage>[
     ChatMessage(
       text: 'Hi! How are you?',
       time: DateTime.now(),
       author: const ChatAuthor(
         id: '8ob3-b720-g9s6-25s8',
         name: 'Outgoing user name',
       ),
     ),
     ChatMessage(
       text: 'Fine, how about you?',
       time: DateTime.now(),
       author: const ChatAuthor(
         id: 'a2c4-56h8-9x01-2a3d',
         name: 'Incoming user name',
         avatar: AssetImage('images/People_Circle2.png'),
       ),
     ),
     ChatMessage(
       text:
           'I’ve been doing well. I’ve started working on a new project recently.',
       time: DateTime.now(),
       author: const ChatAuthor(
         id: '8ob3-b720-g9s6-25s8',
         name: 'Outgoing user name',
       ),
     ),
     ChatMessage(
       text:
           'That sounds great! I’ve been exploring a few new frameworks myself.',
       time: DateTime.now(),
       author: const ChatAuthor(
         id: 'a2c4-56h8-9x01-2a3d',
         name: 'Incoming user name',
         avatar: AssetImage('images/People_Circle2.png'),
       ),
     ),
   ];
   super.initState();
 } 

Step 2: Display Messages Using the SfChat Widget

Add the SfChat widget to display messages, specifying the outgoingUser and a composer for input.

SfChat(
  messages: _messages,
  outgoingUser: '8ob3-b720-g9s6-25s8',
  composer: const ChatComposer(
     decoration: InputDecoration(
        hintText: 'Type a message',
     ),
  ),
), 

Step 3: Adjust Bubble Padding and Background Color

Set the contentPadding property in ChatBubbleSettings to define appropriate space inside the bubble. As our custom shape includes a tail extending below the bubble, this padding ensures there is enough space to accommodate the tail within the bubble’s boundaries. And also adjust the background color for the bubbles using contentBackgroundColor and texStyle to enhance the overall visual design.

SfChat(
 ....
 incomingBubbleSettings: ChatBubbleSettings(
   textStyle: TextStyle(color: Colors.white),
   contentPadding: EdgeInsets.only(top: 15, bottom: 30, left: 28, right: 30),
   contentBackgroundColor: Colors.green[600],
 ),
 outgoingBubbleSettings: ChatBubbleSettings(
   textStyle: TextStyle(color: Colors.white),
   contentPadding: EdgeInsets.only(top: 15, bottom: 30, left: 28, right: 30),
   contentBackgroundColor: Colors.deepPurple[600],
 ),
...
), 

Step 4: Create Custom Border Shapes for Chat Bubbles

Define the CustomBorderShape class, which extends ShapeBorder. This allows us to customize the chat bubble’s border design. Additionally, define three key properties in the extended class:

borderRadius: Controls how rounded the bubble’s corners are. Higher values make the corners more curved.

tailHeight: Determines the height of the “tail” at the bottom of the bubble.

isOutgoing: Indicates whether the bubble is for outgoing (right-aligned) or incoming (left-aligned) messages. This helps decide the alignment and shape of the tail.

class CustomBorderShape extends ShapeBorder {
 final bool isOutgoing;
 final double borderRadius;
 final double tailHeight;

 const CustomBorderShape({
   required this.isOutgoing,
   this.borderRadius = 30.0,
   this.tailHeight = 15.0,
 });
} 

Prepare the Custom Border Shape

To customize the border, we override the getOuterPath method. In the method, we subtract tailHeight from rect.bottom to allocate space for the tail within the bubble’s boundaries. This ensures the tail seamlessly integrates with the bubble shape. And to ensure the corners of the bubble are proportionally rounded, we calculate an adjusted radius that is constrained within safe limits based on the rectangle’s dimensions.

class CustomBorderShape extends ShapeBorder {
....
   @override
   Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
      final double bottom = rect.bottom - tailHeight;
      final double adjustedRadius =
         borderRadius.clamp(0, (rect.width / 3).clamp(0, rect.height / 3));
   }
} 

Using the Path class, we create the main bubble shape as a rounded rectangle with the adjusted radius. To visually connect the bubble to the sender, we draw a tail at the bottom. The placement depends on the isOutgoing property:

class CustomBorderShape extends ShapeBorder {
 final bool isOutgoing;
 final double borderRadius;
 final double tailHeight;

 const CustomBorderShape({
   required this.isOutgoing,
   this.borderRadius = 30.0,
   this.tailHeight = 15.0,
 });

 @override
 EdgeInsetsGeometry get dimensions => EdgeInsets.zero;

 @override
 Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
   
   final double bottom = rect.bottom - tailHeight;
   final double adjustedRadius =
       borderRadius.clamp(0, (rect.width / 3).clamp(0, rect.height / 3));
   final double left = rect.left;
   final double right = rect.right;
   final Path path = Path();

   path.addRRect(
     RRect.fromLTRBAndCorners(
       left,
       rect.top,
       right,
       bottom,
       topLeft: Radius.circular(adjustedRadius),
       bottomLeft: Radius.circular(adjustedRadius),
       topRight: Radius.circular(adjustedRadius),
       bottomRight: Radius.circular(adjustedRadius),
     ),
   );

   final double tailStartX =
       isOutgoing ? right - adjustedRadius : left + adjustedRadius;
   final double tailEndX = isOutgoing
       ? right - adjustedRadius * 1.4
       : left + adjustedRadius * 1.5;

   path.moveTo(tailStartX, bottom);
   path.quadraticBezierTo(
     isOutgoing
         ? right - adjustedRadius * 1.2
         : left + adjustedRadius * 1.2,
     bottom + tailHeight * 0.5,
     tailStartX,
     bottom + tailHeight,
   );
   path.lineTo(tailEndX, bottom);
   path.close();

   return path;
 }

 @override
 void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}

 @override
 ShapeBorder scale(double t) => this;

 @override
 Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
   // This method is not used in this implementation.
      return Path();
 }
} 

Step 5: Implement the Custom Border Shape in SfChat

Now that we have the CustomBorderShape class, we’ll use it in the SfChat widget by setting the contentShape property in the ChatBubbleSettings class.

SfChat(
  .....
   incomingBubbleSettings: ChatBubbleSettings(
       contentShape: CustomBorderShape(isOutgoing: false),
   ),
   outgoingBubbleSettings: ChatBubbleSettings(
        contentShape: CustomBorderShape(isOutgoing: true)),
    ),
), 

By following the above-provided steps, you can design custom border shapes for chat bubbles in the SfChat widget. The contentShape property enables you to implement your own border design, allowing for distinct styles for both incoming and outgoing messages.

View in Github

Here is the output demo:

image.png

Conclusion

I hope you enjoyed learning about how to customize the border shapes of chat bubbles in the SfChat widget.

You can refer to our Flutter Chat feature tour page to know about its other groundbreaking feature representations and documentation and how to quickly get started for configuration specifications.

For current customers, you can check out our components from the License and Downloads page. If you are new to Syncfusion, you can try our 30-day free trial to check out our other controls.

If you have any queries or require clarifications, please let us know in the comments section below. You can also contact us through our support forums, Direct-Trac, or feedback portal. We are always happy to assist you!

Did you find this information helpful?
Yes
No
Help us improve this page
Please provide feedback or comments
Comments (0)
Please  to leave a comment
Access denied
Access denied