Hey I try to create a custom widget like this, where I can change the text in the bottom part of the card.
I already create the shape but I have no idea on how to place Text('Aktif'), I tried using Stack or Column but none works, any Idea?
Here is my code:
#override
Widget build(BuildContext context) {
return Stack(
children: [
Card(
shape: CardShape(),
elevation: 18,
shadowColor: const Color.fromRGBO(51, 0, 0, 0.1),
color: CustomStyle.primaryRed,
child: Container(
decoration: BoxDecoration(
color: CustomStyle.white,
border: Border.all(color: CustomStyle.primaryRed),
borderRadius: BorderRadius.circular(8)),
padding: const EdgeInsets.symmetric(vertical: 8),
child: ListTile(
title: const Text('Rumah Tanggerang'),
subtitle: const Text('Primary - 1234567890'),
trailing: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'DEFAULT',
),
myPopMenu()
],
),
),
),
),
Positioned(left: 10, bottom: 0, child: Text('Aktif'))
],
);
}
class CardShape extends ShapeBorder {
const CardShape();
final BorderSide _side = BorderSide.none;
final BorderRadiusGeometry _borderRadius = BorderRadius.zero;
#override
EdgeInsetsGeometry get dimensions => EdgeInsets.all(_side.width);
#override
Path getInnerPath(
Rect rect, {
TextDirection? textDirection,
}) {
final Path path = Path();
path.addRRect(
_borderRadius.resolve(textDirection).toRRect(rect).deflate(_side.width),
);
return path;
}
#override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
final Path path = Path();
final RRect rrect = _borderRadius.resolve(textDirection).toRRect(rect);
path.moveTo(0, 10);
path.quadraticBezierTo(0, 0, 10, 0);
path.lineTo(rrect.width - 10, 0);
path.quadraticBezierTo(rrect.width, 0, rrect.width, 10);
path.lineTo(rrect.width, rrect.height - 10);
path.quadraticBezierTo(
rrect.width, rrect.height, rrect.width - 10, rrect.height);
path.lineTo(100, rrect.height);
path.lineTo(80, rrect.height + 20);
path.lineTo(10, rrect.height + 20);
path.quadraticBezierTo(0, rrect.height + 20, 0, rrect.height + 10);
return path;
}
#override
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}
#override
ShapeBorder scale(double t) => RoundedRectangleBorder(
side: _side.scale(t),
borderRadius: _borderRadius * t,
);
}
Card widget can't used stack because the red container is already outside the Card element so to put the text you just can wrap Stack inside of a Column and put the Text widget without position this is the example that I've already did
class Test extends StatelessWidget {
const Test({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
decoration: const BoxDecoration(color: Colors.blue),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
Card(
shape: const CardShape(),
elevation: 18,
shadowColor: const Color.fromRGBO(51, 0, 0, 0.1),
color: Colors.red,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.red),
borderRadius: BorderRadius.circular(8)),
padding: const EdgeInsets.symmetric(vertical: 8),
child: ListTile(
title: const Text('Rumah Tanggerang'),
subtitle: const Text('Primary - 1234567890'),
trailing: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: const <Widget>[
Text(
'DEFAULT',
),
],
),
),
),
),
],
),
const Padding(
padding: EdgeInsets.only(
left: 30,
),
child: Text(
'Aktif',
style: TextStyle(
color: Colors.white, fontWeight: FontWeight.w700),
),
)
],
),
),
),
);
}
}
if you copy my code just made sure to change back the color you've used.
Related
I'd like to draw the black container shape with flutter.
There are many ways to do this, the first time I thought it was a cut, I was thinking in a ClipPath widget, but now I see you have a Circle widget at the top of your black Container.
This is an example:
class FunnyContainer extends StatelessWidget {
const FunnyContainer({Key? key}) : super(key: key);
Widget _childContainer() {
return Padding(
padding: const EdgeInsets.all(20.0),
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.yellow,
width: 4,
),
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 40),
child: Container(
height: 400,
decoration: const BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 40),
Expanded(child: _childContainer()),
Expanded(child: _childContainer()),
Expanded(child: _childContainer()),
],
),
const Positioned(
left: 0,
right: 0,
top: -50,
child: CircleAvatar(
backgroundColor: Colors.white,
radius: 40,
),
)
],
),
),
),
);
}
}
Result:
UPDATE (Using your updated design)
Now the you updated your post, this could be done using Clippers, the problem is that clip needs Shadow, so I took this code : https://gist.github.com/coman3/e631fd55cd9cdf9bd4efe8ecfdbb85a7
And I used on this example:
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Center(
child: Padding(
padding: const EdgeInsets.all(40.0),
child: ClipShadowPath(
clipper: _MyClipper(100),
shadow: const Shadow(
blurRadius: 15,
color: Colors.grey,
offset: Offset(0, 10),
),
child: SizedBox(
height: 400,
child: Container(
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
Spacer(),
],
),
),
),
),
),
),
);
}
}
class _MyClipper extends CustomClipper<Path> {
final double space;
_MyClipper(this.space);
#override
Path getClip(Size size) {
final path = Path();
final halfWidth = size.width / 2;
final halfSpace = space / 2;
final curve = space / 6;
final height = halfSpace / 1.4;
path.lineTo(halfWidth - halfSpace, 0);
path.cubicTo(halfWidth - halfSpace, 0, halfWidth - halfSpace + curve,
height, halfWidth, height);
path.cubicTo(halfWidth, height, halfWidth + halfSpace - curve, height,
halfWidth + halfSpace, 0);
path.lineTo(size.width, 0);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.close();
return path;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) => true;
}
RESULT
You just need to update the Path in order to get the rounded corners of the Container.
You may use Stack which may write draw circle on top of the container like this:
class BlackContainer extends StatelessWidget {
const BlackContainer({
Key? key,
required this.child,
}) : super(key: key);
final Widget child;
#override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
Container(
child: child,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.black,
boxShadow: const [
BoxShadow(
color: Colors.grey,
spreadRadius: 5,
blurRadius: 10,
),
],
),
),
Positioned(
top: -25,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
width: 40,
height: 40,
),
),
],
);
}
}
And use it like that:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: BlackContainer(
child: Text(
'Content here',
style: Theme.of(context)
.textTheme
.bodyText2!
.copyWith(color: Colors.white),
),
),
),
);
}
}
Position a transparent png on top of your container.
child: Container(
width: 200,
height: 300,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Colors.black,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset: Offset(0, 3),
),
],
),
child: Container(
child: Image.asset('assets/images/half-circle.png', fit: BoxFit.contain, alignment: new Alignment(-1.0, -1.0)),
margin: EdgeInsets.only(left: 50.0, right: 50.0),
),
),
Image:
Live example
I want to achieve have a round icon for the profile image like in this example:
But is not working for me, this is what I achieved so far:
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
ClipPath(
clipper: _AppBarProfileClipper(childHeight),
child: Container(
padding: const EdgeInsets.only(top: 48.0),
color: TheBaseColors.lightBlue,
height: height,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
leading,
FlatButton(
onPressed: null,
child: Text(
title,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 18.0,
color: Colors.white,
),
),
),
trailing,
],
),
),
),
Positioned(
bottom: childHeight / 2, //50
left: 0,
right: 0,
child: Align(
alignment: Alignment.bottomCenter,
child: child,
),
),
],
);
}
}
All the parameters are represented in the constructor and used in the code. But I cant figure out how to make that nice effect with the rounded icon.
Someone knows why is not working?
You can achieve given example like this:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final double appBarHeight = 100.0;
final double spaceAroundRoundButton = 4.0;
final double roundedButtonSize = 64.0;
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return SafeArea(
child: Stack(
children: <Widget>[
Scaffold(
backgroundColor: Colors.white,
appBar: PreferredSize(
preferredSize:
Size(MediaQuery.of(context).size.width, appBarHeight),
child: ClipPath(
clipper: BottomClipper(),
child: Container(
alignment: Alignment.topLeft,
height: appBarHeight,
color: Colors.black,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: Icon(
Icons.menu,
color: Colors.white,
),
onPressed: () {}),
Text(
'User Profile',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
IconButton(
icon: Icon(
Icons.settings,
color: Colors.white,
),
onPressed: () {})
],
),
),
)),
body: Container(
alignment: Alignment.topCenter,
margin: EdgeInsets.only(
top:
roundedButtonSize * 0.75 + spaceAroundRoundButton + 16.0),
child: Text('Body'),
),
),
Positioned(
child: Container(
padding: EdgeInsets.all(spaceAroundRoundButton),
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: RawMaterialButton(
elevation: 0,
constraints: BoxConstraints.tightFor(
width: roundedButtonSize, height: roundedButtonSize),
shape: StadiumBorder(
side: BorderSide(width: 4, color: Colors.grey)),
child: Icon(Icons.person),
onPressed: () {},
fillColor: Colors.white,
),
),
left: (size.width / 2) -
(roundedButtonSize / 2) -
spaceAroundRoundButton,
top: appBarHeight -
(roundedButtonSize * 0.25) -
spaceAroundRoundButton,
)
],
),
);
}
}
class BottomClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path = Path();
path.lineTo(0.0, size.height * 0.75);
var firstControlPoint = Offset(size.width * 0.25, size.height);
var firstEndPoint = Offset(size.width * 0.5, size.height);
path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy,
firstEndPoint.dx, firstEndPoint.dy);
var secondControlPoint = Offset(size.width * 0.75, size.height);
var secondEndPoint = Offset(size.width, size.height * 0.75);
path.quadraticBezierTo(secondControlPoint.dx, secondControlPoint.dy,
secondEndPoint.dx, secondEndPoint.dy);
path.lineTo(size.width, 0.0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
Screenshot:
I am creating an app in flutter and in the portrait mode everything looks fine but when it comes to landscape mode it will be triggered with an error saying A RenderFlex overflowed by 86 pixels on the bottom. Below is my all code that I have tried and also I have shared the images, please refer to the same for more clarification.
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.dark
),
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
body: new Stack(
children: <Widget>[
Container(
decoration: ShapeDecoration(
gradient:LinearGradient(
begin: Alignment.bottomLeft,
end: Alignment.topRight,
colors: [Colors.yellow,Colors.yellow,Colors.orangeAccent,Colors.deepOrange,]
),
// color: Colors.deepOrangeAccent,
shape: CustomShapeBorder(),
),
),
Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 200.0,left: 50.0),
child: Text(
"SET, MATCH AND CHALLENGE",
style: TextStyle(
fontSize: 40.0,
),
),
),
Padding(
padding: const EdgeInsets.only(left: 30.0,right: 30.0, top: 30.0),
child: Text(
"A simple, fun, and a creative way to "
"set the bespoke challenges with "
"either a friend or a group of "
"friends anywhere in the world",
textAlign: TextAlign.justify,
style: TextStyle(
color: Colors.white
),
),
),
Container(
width: 300.0,
height: 60.0,
margin: const EdgeInsets.only(top: 140.0),
child: RaisedButton(
textColor: Colors.white,
color: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(18.0),
),
child: Text("Sign Up",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
),
),
),
),
],
),
Container(
margin: const EdgeInsets.only(bottom: 0.0, right: 0.0),
child: Align(
alignment: Alignment.bottomRight,
child: Transform.rotate(
angle: pi,
child: Container(
decoration: ShapeDecoration(
gradient:LinearGradient(
begin: Alignment.bottomLeft,
end: Alignment.topRight,
colors: [Colors.yellow,Colors.yellow,Colors.orangeAccent,Colors.deepOrange,]
),
//color: Colors.deepOrangeAccent,
shape: CustomShapeBorder(),
),
),
),
),
),
],
),
);
}
}
class CustomShapeBorder extends ShapeBorder {
final double distanceFromWall = 12;
final double controlPointDistanceFromWall = 5;
#override
// TODO: implement dimensions
EdgeInsetsGeometry get dimensions => null;
#override
Path getInnerPath(Rect rect, {TextDirection textDirection}) {
// TODO: implement getInnerPath
return null;
}
#override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
// TODO: implement getOuterPath
return getClip(Size(260.0,180.0));
}
#override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
// TODO: implement paint
}
#override
ShapeBorder scale(double t) {
// TODO: implement scale
return null;
}
Path getClip(Size size) {
Path clippedPath = new Path();
clippedPath.lineTo(0, size.height);
clippedPath.quadraticBezierTo(30, size.height + 10, size.width * 0.20, size.height - 50);
clippedPath.quadraticBezierTo(70, size.height - 120, size.width * 0.40, size.height * 0.35);
clippedPath.quadraticBezierTo(180, (size.height - (size.height* 0.6)), size.width - 40 , 32 );
clippedPath.quadraticBezierTo(250, 0, size.width, 0);
clippedPath.lineTo(size.width, 0);
clippedPath.close();
return clippedPath;
}
}
Refer to below image for Portrait Mode
Refer to below image for Landscape Mode (Where error occurs):
The problem is that you are using hardcode margins in a Column widget, that will give you problems in small devices too. You can fix that changing you Column Widget for a ListView Widget, but that is going to give you a Scroll Effect, I think you don't want that. So you can refactor that Column using Expanded Widget and a Stack can help in the first part of the Column, try the next:
Column(
children: <Widget>[
Expanded(
flex: 1,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 50),
child: Stack(
children: <Widget>[
Center(
child: Text(
"SET, MATCH AND CHALLENGE",
style: TextStyle(
fontSize: 40.0,
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Text(
"A simple, fun, and a creative way to "
"set the bespoke challenges with "
"either a friend or a group of "
"friends anywhere in the world",
textAlign: TextAlign.justify,
style: TextStyle(
color: Colors.white
),
),
)
],
),
),
),
Expanded(
flex: 1,
child: Center(
child: Container(
width: 300.0,
height: 60.0,
child: RaisedButton(
textColor: Colors.white,
color: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(18.0),
),
child: Text("Sign Up",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
),
),
),
),
),
)
],
)
I need a lil help to draw dotted line between 2 points without using google map's poly lines.
I tried with canvas, it does but not exactly start under and above location.
Right now you can see 2 points later it'll be more than 2 points.
It really appreciated if anyone help me to achieve.
I think drawing is more efficient than creating more containers like the one above. If you don't want to use the library, you can use my following method, simply adding a few lines:
Create class DashedLineVerticalPainter:
class DashedLineVerticalPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
double dashHeight = 5, dashSpace = 3, startY = 0;
final paint = Paint()
..color = Colors.grey[300]
..strokeWidth = size.width;
while (startY < size.height) {
canvas.drawLine(Offset(0, startY), Offset(0, startY + dashHeight), paint);
startY += dashHeight + dashSpace;
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
And use it:
CustomPaint(
size: Size(1, double.infinity),
painter: DashedLineVerticalPainter()
)
Result:
I have made almost same looking widget by using https://pub.dev/packages/flutter_dash, you can also customise this widget according to your style.
Here is the code,Hope it helps.
Column(children: <Widget>[
Container(
margin: EdgeInsets.only(top: 16),
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.circle,
border:
Border.all(width: 1.5, color: Colors.greenAccent)),
),
Dash(
direction: Axis.vertical,
length: 130,
dashLength: 15,
dashColor: grey),
Container(
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.rectangle,
border: Border.all(width: 2, color: red)),
child: Container(
height: 20,
),
),
],
),
class _MyWidgetState extends State<MyWidget> {
List<Model> list = [];
#override
void initState() {
super.initState();
list.add(Model("Hyderabad", Colors.red));
list.add(Model("Visakhapatnam", Colors.green));
list.add(Model("Vijayawada", Colors.blue));
}
void addNew() {
setState(() {
list.add(Model("Karnool", Colors.black));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
title:
Text('Custom Stepper', style: TextStyle(color: Colors.white)),
actions: [
IconButton(
icon: Icon(Icons.add_circle, color: Colors.white),
onPressed: addNew)
]),
body: Container(
padding: EdgeInsets.all(15),
color: Colors.white,
child: ListView.builder(
itemCount: list.length,
itemBuilder: (con, ind) {
return ind != 0
? Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
Column(
children: List.generate(
3,
(ii) => Padding(
padding: EdgeInsets.only(
left: 10, right: 10, top: 5, bottom: 5),
child: Container(
height: 3,
width: 2,
color: Colors.grey,
)),
),
),
Expanded(
child: Container(
color: Colors.grey.withAlpha(60),
height: 0.5,
padding: EdgeInsets.only(
left: 10,
right: 20,
),
))
]),
Row(children: [
Icon(Icons.location_on, color: list[ind].color),
Text(list[ind].address,
style: TextStyle(color: list[ind].color))
])
])
: Row(children: [
Icon(Icons.location_on, color: list[ind].color),
Text(list[ind].address,
style: TextStyle(color: list[ind].color))
]);
})));
}
}
class Model {
String address;
double lat;
double long;
Color color;
//Other fields if needed....
Model(this.address, this.color);
//initialise other fields so on....
}
return Container(
child: Row(
children: <Widget>[
Column(children: <Widget>[
Icon(Icons.radio_button_checked,color: Colors.orange,)
Dash(
direction: Axis.vertical,
length: 20,
dashLength: 5,
thickness:3.0,
dashColor: Colors.grey[400]),
Icon(Icons.location_on,color: Colors.blue,)
],
),
Column(children: <Widget>[
Text("Some text"),
Divider(),
Text("Some Text")
],
),
],
)
);
Current output:
Expected output:
Code:
Align(
alignment: Alignment.topRight,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 20),
margin: EdgeInsets.only(right: 12, top: 8),
decoration: BoxDecoration(
color: Color(0xFF486993),
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: Text("This is my message"),
),
)
Here is what exactly needed to create neat speech bubble in flutter.
first create a custom painter class that extends Painter class
class CustomChatBubble extends CustomPainter {
CustomChatBubble({this.color, #required this.isOwn});
final Color color;
final bool isOwn;
#override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color ?? Colors.blue;
Path paintBubbleTail() {
Path path;
if (!isOwn) {
path = Path()
..moveTo(5, size.height - 5)
..quadraticBezierTo(-5, size.height, -16, size.height - 4)
..quadraticBezierTo(-5, size.height - 5, 0, size.height - 17);
}
if (isOwn) {
path = Path()
..moveTo(size.width - 6, size.height - 4)
..quadraticBezierTo(
size.width + 5, size.height, size.width + 16, size.height - 4)
..quadraticBezierTo(
size.width + 5, size.height - 5, size.width, size.height - 17);
}
return path;
}
final RRect bubbleBody = RRect.fromRectAndRadius(
Rect.fromLTWH(0, 0, size.width, size.height), Radius.circular(16));
final Path bubbleTail = paintBubbleTail();
canvas.drawRRect(bubbleBody, paint);
canvas.drawPath(bubbleTail, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
then use it in your chat screen. please note that this screen for only demo purpose so you can modify it according to your needs.
class ChatSampleWidget extends StatefulWidget {
#override
_ChatSampleWidgetState createState() => _ChatSampleWidgetState();
}
class _ChatSampleWidgetState extends State<ChatSampleWidget> {
TextEditingController _editingController = TextEditingController();
FocusNode _focusNode = FocusNode();
final TextStyle textStyle = TextStyle(color: Colors.white);
#override
void initState() {
super.initState();
}
#override
dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Chatter'),
),
body: SafeArea(
child: Stack(
children: <Widget>[
Container(
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
CustomPaint(
painter: CustomChatBubble(isOwn: false),
child: Container(
padding: EdgeInsets.all(8),
child: Text(
'Message from someone else \n Says sometihngs',
style: textStyle,
))),
],
),
SizedBox(
height: 5,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
CustomPaint(
painter:
CustomChatBubble(color: Colors.grey, isOwn: false),
child: Container(
padding: EdgeInsets.all(10),
child: FlutterLogo())),
],
),
SizedBox(
height: 5,
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
CustomPaint(
painter:
CustomChatBubble(color: Colors.green, isOwn: true),
child: Container(
padding: EdgeInsets.all(8),
child: Text(
'Message from me',
style: textStyle,
))),
],
)
],
),
),
Positioned(
bottom: 0,
child: Container(
padding: EdgeInsets.all(8),
width: MediaQuery.of(context).size.width,
color: Colors.grey.withOpacity(0.1),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Flexible(
child: TextField(
controller: _editingController,
focusNode: _focusNode,
decoration:
InputDecoration(hintText: 'Say something...'),
),
),
IconButton(
icon: Icon(
Icons.send,
size: 30,
),
onPressed: () {
print(_editingController.text);
})
],
),
))
],
),
),
);
}
}
this is how it looks;
Here you are:
import 'package:flutter/material.dart';
void main() => runApp(
MyApp()
);
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Padding(
padding: EdgeInsets.all(7),
child: Align(
alignment: Alignment.centerRight,
child: Stack(
children: [
Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 20),
decoration: BoxDecoration(
color: Color(0xFF486993),
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: 'This is telegram message ',
style: TextStyle(
color: Colors.white,
fontSize: 14.0
),
),
TextSpan(
text: '3:16 PM',
style: TextStyle(
color: Colors.grey,
fontSize: 12.0,
fontStyle: FontStyle.italic
),
),
],
),
),
Icon(Icons.check, color: Color(0xFF7ABAF4), size: 16,)
]
),
),
Positioned(
bottom: 0,
right: 0,
child: CustomPaint(
painter: ChatBubbleTriangle(),
)
)
]
)
),
),
);
}
}
class ChatBubbleTriangle extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()..color = Color(0xFF486993);
var path = Path();
path.lineTo(-15, 0);
path.lineTo(0, -15);
path.lineTo(0, 0);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
You will have to change paint method, because now it's just a triangle.
Otherwise, it looks as you requested.
Trying to recreate Telegram layout myself I came up with this to make the shape;
enum _TailDirection { right, left }
class ChatBubblePainter extends CustomPainter {
ChatBubblePainter({this.color, this.isSentByMe});
final Color color;
final bool isSentByMe;
#override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color;
Path paintBubbleTail(_TailDirection direction) {
double startingPoint;
double point;
double endPoint;
double curvePoint;
if (direction == _TailDirection.right) {
startingPoint = size.width - 5;
point = size.width + 10;
endPoint = size.width + 3;
curvePoint = size.width;
}
if (direction == _TailDirection.left) {
startingPoint = 5;
point = -10;
endPoint = -3;
curvePoint = 0;
}
return Path()
..moveTo(startingPoint, size.height)
..lineTo(point, size.height)
..quadraticBezierTo(endPoint, size.height, curvePoint, size.height - 10);
}
final RRect bubbleBody = RRect.fromRectAndRadius(
Rect.fromLTWH(0, 0, size.width, size.height), Radius.circular(5.0));
final Path bubbleTail = isSentByMe
? paintBubbleTail(_TailDirection.right)
: paintBubbleTail(_TailDirection.left);
canvas.drawRRect(bubbleBody, paint);
canvas.drawPath(bubbleTail, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}