Related
I'm going to create a page that looks like Telegram's channel page. I tried to make a card:
ChannelPostCard.dart:
class ChannelPostCard extends StatelessWidget {
const ChannelPostCard({super.key, required this.channelPostModel});
final ChannelPostModel channelPostModel;
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.centerRight,
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width - 45, minWidth: 180),
child: Card(
elevation: 1,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
color: const Color.fromARGB(255, 204, 255, 230),
margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(
'assets/images/${channelPostModel.image}',
height: 200,
),
Padding(
padding: const EdgeInsets.only(
left: 25, right: 10, top: 5, bottom: 25),
child: Text(
channelPostModel.text,
style: const TextStyle(
fontSize: 18,
),
),
),
Row(
children: [
const SizedBox(
width: 5,
),
Text(
channelPostModel.uploadDate,
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
child: Icon(
Icons.remove_red_eye,
size: 20,
color: Colors.grey,
),
),
Text(
channelPostModel.seenCount.toString(),
style: const TextStyle(fontSize: 12, color: Colors.grey),
)
],
),
],
),
),
),
);
}
}
I want to set the card's width equal to the width of the image (if the width is bigger than the "Max width" of the card, the max width will be applied.) and the image to be fitted in the card. The top right corner has also missed its roundness.
How can I fix my UI?
For your first issue you need to set Row's mainAxisSize to min:
Row(
mainAxisSize: MainAxisSize.min, // add this
children: [
const SizedBox(
width: 5,
),
...
)
for your second issue you need set card's clipBehavior to antiAlias:
child: Card(
clipBehavior: Clip.antiAlias, // add this
elevation: 1,
...
)
sorry if the image is too fat, I don't know how to format image in stack, so, this is what I wanna achieve, and I've done this with the following code, but I get an error and my screen goes white(showing the scaffold, refusing to render my widgets coz of the errors), I know in some way I have to constrain some widget somewhere, but I don't know which to give a height, since I need my entire screen to be scrollable, I've even tried to not make entire screen scrollable or give the children of the lower container some fixed height like 900, but still, not working, how can I achieve this,
This is my code so far
#override
Widget build(BuildContext context) {
_animationController.forward();
return Consumer<ProductsModel>(
builder: (_, pModel, __) => Scaffold(
body: SingleChildScrollView(
child: Stack(
children: [
Positioned(
left: 0,
right: 0,
child: Container(
width: double.maxFinite,
height: Dimensions.height395,
color: const Color(0xffF8F9FA),
child: Stack(
children: [
PageView.builder(
itemCount: widget.product.productImages!.length,
controller: _pageController,
onPageChanged: (value) {
_currentPage = value;
_animationController.forward();
// setState(() {});
},
itemBuilder: (context, index) {
return Center(
child: Container(
margin: EdgeInsets.only(top: Dimensions.height50),
width: Dimensions.productDetailContainerWidth,
height: Dimensions.productDetailContainerHeight,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
widget.product.productImages![index],
),
),
),
),
);
},
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: EdgeInsets.only(bottom: Dimensions.height20),
child: DotsIndicator(
dotsCount: widget.product.productImages!.length,
position: _currentPageValue,
decorator: DotsDecorator(
activeColor: kMainColour,
size: const Size.square(9.0),
activeSize: const Size(18.0, 9.0),
activeShape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(Dimensions.radius5)),
),
),
),
),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: EdgeInsets.only(
bottom: Dimensions.height20,
right: Dimensions.height20),
child: GestureDetector(
onTap: () {
pModel.toggleSave(context, widget.product);
},
child: Container(
height: Dimensions.height32,
width: Dimensions.height32,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
border: Border.all(
color: const Color(0xffDADADA),
),
),
child: Icon(
widget.product.isSaved
? IconlyBold.heart
: IconlyBroken.heart,
color: kMainColour,
),
),
),
),
),
],
),
),
),
// todo: nav row
Positioned(
top: Dimensions.height45,
right: Dimensions.height20,
left: Dimensions.height20,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BackNavButton(
onTap: () {
Navigator.pop(context);
},
),
Text(
widget.product.productSeller!,
style: TextStyle(
fontFamily: kFontFamily,
fontWeight: FontWeight.w600,
fontSize: Dimensions.font14,
),
),
SvgPicture.asset("assets/icons/chat_icon.svg")
],
),
),
// todo: review and details
Positioned(
left: 0,
right: 0,
top: Dimensions.height395 - Dimensions.height10,
child: Container(
height: 900,
padding: EdgeInsets.only(left: Dimensions.height20, right: Dimensions.height20, top: Dimensions.height20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(Dimensions.radius20),
color: Colors.white,
),
child: Row(
children: [
Column(
children: [
Text(
widget.product.productName!,
style: TextStyle(
fontFamily: kFontFamily,
fontWeight: FontWeight.w500,
fontSize: Dimensions.font18,
),
),
Visibility(
visible: widget.product.discount != null,
child: Text(
"(${widget.product.discount}% Discount)",
style: TextStyle(
fontFamily: kFontFamily,
fontWeight: FontWeight.w500,
fontSize: Dimensions.font13,
color: const Color(0xff9098B1),
),
),
),
const FilterRating(
stars: 4,
),
],
),
Column(
children: [
],
),
],
),
),
),
],
),
),
),
);
}
I am having problems positioning the text inside my stack of widgets.
I just want to position two text widgets one below another. But one of them is initially at the bottom already , it moves up the whole widget.
You can see in this attachment the result I would like to achieve (red lines) and the current result:
Widget build(BuildContext context) {
return Stack(
children: [
Card(
elevation: 6,
margin: const EdgeInsets.all(8),
child: Padding(
padding: const EdgeInsets.only(
bottom: 60, right: 5, left: 5, top: 5), //bordes interiores
child: Stack(children: [
Container(
width: double.infinity,
height: 200,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(image as String), //varImg.url
fit: BoxFit.cover)),
//child:
),
]),
)),
Container(
//alignment: Alignment.centerLeft,
padding: const EdgeInsets.all(10),
child: Column(
children: [
Row(
children: [
IconTheme(
data: IconThemeData(
color: gender == 'Male' ? Colors.blue : Colors.pink),
child: Icon(gender == 'Male'
? Icons.male_rounded
: Icons.female_rounded)),
Positioned(
bottom: 10,
child: Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey),
),
),
],
),
],
),
),
],
);
}
}
You could use a Column with mainAxisAlignment set to MainAxisAlignment.end and set the two widgets as its children.
I have a container that represents a card with dynamical content. So it can be about 175 pixels height, can be 300. If this card is disabled I want to overlay this card by another container with, let say, gray color with opacity 0.5. I've realized that I can use Stack for this, but how can I define the correct size for overlay container?
Widget build(BuildContext context) {
return Center(
child: Stack(children: [
_buildCard(context),
Container(
margin: EdgeInsets.only(top: 8, left: 16, right: 16, bottom: 8),
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.75),
borderRadius: BorderRadius.circular(20.0)),
child: Center(
child: Text(
AppTranslations.of(context).text("pending"),
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold),
)))
]),
);
}
Widget _buildCard(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 8, left: 16, right: 16, bottom: 8),
child: Container(
decoration: new BoxDecoration(
borderRadius: new BorderRadius.all(new Radius.circular(20.0)),
boxShadow: [
BoxShadow(
color: Colors.grey,
blurRadius: 10.0,
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
child: Material(
clipBehavior: Clip.antiAlias,
color: Colors.white,
child: Column(
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_buildImageLogo(),
Expanded(
child: Container(
padding: EdgeInsets.only(top: 12, left: 6, right: 6),
// margin: EdgeInsetsDirectional.fromSTEB(0, 8, 0, 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
LimitedBox(
maxWidth: 135,
child: Text(
widget.serviceInfo.title == null
? ""
: widget.serviceInfo.title,
style: TextStyle(fontSize: 16),
textAlign: TextAlign.start,
),
),
LimitedBox(
maxWidth: 50,
child: Container(
margin: EdgeInsetsDirectional.fromSTEB(
0, 0, 8, 0),
child: Text(
widget.serviceInfo.price == null
? ""
: ("AED" +
widget.serviceInfo.price
.toString()),
style: Theme.of(context)
.textTheme
.title
.copyWith(
fontSize: 13,
color: Colors.black54),
textAlign: TextAlign.end,
),
),
),
],
),
SizedBox(
height: Size.fromHeight(15).height,
),
Text(
widget.serviceInfo.description == null
? ""
: widget.serviceInfo.description,
style: Theme.of(context)
.textTheme
.caption
.copyWith(fontSize: 13),
),
serviceOptions(),
],
),
)),
],
),
SizedBox(
height: Size.fromHeight(10).height,
),
Row(
children: <Widget>[
Expanded(
child: Padding(
padding: EdgeInsets.only(
top: 8, bottom: 8, left: 8, right: 8),
child: _btnDecline(),
),
),
Expanded(
child: Padding(
padding: EdgeInsets.only(
top: 8, bottom: 8, left: 8, right: 8),
child: _btnAccept(),
),
),
],
),
],
),
),
),
));
}
the full hierarchy looks like
Scaffold -> Consumer -> Center -> Column -> Expanded -> Consumer -> ListView -> ServiceCard
You GlobalKey to get the BuildContext and use it to find out your container's size using it's RenderBox. Then you can give this size to the overlaying container. However, this is overkill imo.
You can also define a boolean to see whether or not your container should be overlayed and adjust it's decoration conditionally.
I am using a white container with height
height: MediaQuery.of(context).size.height
And I am adding several red containers in it. When the number of these inside containers is less, the scrolling works perfectly like this
But as I increase the number of containers inside the big container, scrolling kind of overflows the container, like this
One solution I found out was that if I increase the height of white container, i.e:-
height: MediaQuery.of(context).size.height*7
But it makes the app look ugly and would eventually fail when the number of red containers is further increased. How can I fix this issue?
Code for the Program:-
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Test(),
));
}
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: Scaffold(
body: Container(
color: Colors.black,
child: ListView(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 15.0, left: 10.0),
),
SizedBox(
height: 25.0,
),
Padding(
padding: EdgeInsets.only(left: 20.0),
child: Row(
children: <Widget>[
Text(
'TEST',
style: TextStyle(
fontFamily: 'Montserrat',
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 25.0),
),
SizedBox(
width: 10.0,
),
],
),
),
SizedBox(height: 60.0,),
Container(
margin: EdgeInsets.only(top:180.0,),
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
color: Color(0xFFEEEEEE),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(75.0),
topRight: Radius.circular(75.0),
),
),
child: ListView(
primary: false,
padding: EdgeInsets.only(
left: 15.0,
right: 15.0,
top: 20.0,
),
children: <Widget>[
Padding(
padding: const EdgeInsets.only(
top: 30.0,
),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Center(
child: Text(
'TEST',
style: TextStyle(
color: Colors.black,
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
SizedBox(height: 20.0,),
Column(
children: <Widget>[
Container(
height: 150,
color: Colors.red,
),
SizedBox(height: 20,),
Container(
height: 150,
color: Colors.red,
),
SizedBox(height: 20,),
Container(
height: 150,
color: Colors.red,
),
SizedBox(height: 20,),
Container(
height: 150,
color: Colors.red,
),
SizedBox(height: 20,),
Container(
height: 150,
color: Colors.red,
),
SizedBox(height: 20,),
Container(
height: 150,
color: Colors.red,
),
SizedBox(height: 20,),
Container(
height: 150,
color: Colors.red,
),
SizedBox(height: 20,),
],
)
],
),
),
],
),
),
],
),
),
),
),
);
}
}
Okay so after some suggestions from comments I myself found the solution.
Instead of using listview inside the white container, I removed it and wrapped the white container with SingleChildScrollView and also wrapped it with Flexible
Now the container automatically adjusts according to the amount of containers in it.
Fixed code:-
import 'package:flutter/material.dart';
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: Scaffold(
body: Container(
color: Colors.black,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 15.0, left: 10.0),
),
SizedBox(
height: 25.0,
),
Padding(
padding: EdgeInsets.only(left: 20.0),
child: Row(
children: <Widget>[
Text(
'TEST',
style: TextStyle(
fontFamily: 'Montserrat',
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 25.0),
),
SizedBox(
width: 10.0,
),
],
),
),
SizedBox(
height: 60.0,
),
//User INFO
SingleChildScrollView(
child: Flexible(
child: Container(
margin: EdgeInsets.only(
top: 180.0,
),
decoration: BoxDecoration(
color: Color(0xFFEEEEEE),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(75.0),
topRight: Radius.circular(75.0),
),
),
child: Padding(
padding: EdgeInsets.only(
left: 15.0,
right: 15.0,
top: 20.0,
),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(
top: 30.0,
),
child: Column(
children: <Widget>[
//greeting text
Row(
children: <Widget>[
Expanded(
child: Center(
child: Text(
'TEST',
style: TextStyle(
color: Colors.black,
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
SizedBox(
height: 20.0,
),
//app work
Column(
children: <Widget>[
Container(
height: 150,
color: Colors.red,
),
SizedBox(
height: 20,
),
Container(
height: 150,
color: Colors.red,
),
SizedBox(
height: 20,
),
Container(
height: 150,
color: Colors.red,
),
SizedBox(
height: 20,
),
Container(
height: 150,
color: Colors.red,
),
SizedBox(
height: 20,
),
Container(
height: 150,
color: Colors.red,
),
SizedBox(
height: 20,
),
Container(
height: 150,
color: Colors.red,
),
SizedBox(
height: 20,
),
Container(
height: 150,
color: Colors.red,
),
SizedBox(
height: 20,
),
],
)
//add button
],
),
),
],
),
),
),
),
),
],
),
),
),
),
),
);
}
}
To achieve your desired layout try playing with padding value of this container
Container(padding: EdgeInsets.only(top: 35.0, ),
margin: EdgeInsets.only(top:180.0,),
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
color: Color(0xFFEEEEEE),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(75.0),
topRight: Radius.circular(75.0),
),
),
in my case i used padding: EdgeInsets.only(top: 35.0, ),
result
First, you need to get the correct screen height by reducing the extra top padding and appBar size (if that page contains appBar)
To do that simply add
appBar: PreferredSize(
preferredSize: Size.fromHeight(50),
child: AppBar(
automaticallyImplyLeading: false,
elevation: 0,
title: Text(
"Hello"
),
backgroundColor: const Color(0xFF005898),
)),
Then add this to height
double screenHeight = MediaQuery.of(context).size.height -
50 -
MediaQuery.of(context).padding.top; // Top Screen Height - appbarHeight - Top Padding(That top status bar on every phone)
Then Everything is as same but, Before SingleChildScrollView add a sizedBox then provide screenHeight and width as per requirement then in the child add scrollView and the rest of the code