How to change the state of the checkbox in the drop-down list? - expandablelistview

I study flutter and make a simple application with a drop-down list and checkboxes, as shown in the picture. But it is not possible to achieve a change in the status of checkboxes.
Many different options reviewed, I did following the example. Most likely the problem is in the EntryItem class constructor, because when creating an entry instance, its children child element is called as null, writes the error "The getter 'children' was called on null."
Here is the code I'm currently working with:
import 'package:flutter/material.dart';
// run app
void main() => runApp(new ExpansionTileSample());
class ExpansionTileSample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('ExpansionTile'),
),
body: new ListView.builder(
itemBuilder: (BuildContext context, int index) => new EntryItem(data[index]),
itemCount: data.length,
),
),
);
}
}
class EntryItem extends StatefulWidget {
// Entry entry;
EntryItem(Entry entry);
#override
_EntryItemState createState() {
return new _EntryItemState();
}
}
class _EntryItemState extends State<EntryItem> {
Entry entry;
Widget _buildTiles(Entry root) {
if (root.children.isEmpty)
return new Column(
children: <Widget>[
new CheckboxListTile(
title: new Text(
root.title,
style: new TextStyle(fontSize: 14.0),
),
value: entry.isCheck,
onChanged: (bool value) {
setState(() {
entry.isCheck = value;
});
},
),
new Divider(height: 16.0, indent: 0.0),
],
);
// return new Divider();
return new ExpansionTile(
key: new PageStorageKey<Entry>(root),
title: new Text(root.title),
children: root.children.map(_buildTiles).toList(),
);
}
#override
Widget build(BuildContext context) {
return _buildTiles(entry);
}
}
// One entry in the multilevel list displayed by this app.
class Entry {
Entry(this.isCheck, this.title, [this.children = const <Entry>[]]);
final String title;
List<Entry> children;
bool isCheck;
}
// The entire multilevel list displayed by this app.
List<Entry> data = <Entry>[
new Entry(false,
' Компетентность',
<Entry>[
new Entry(false,
'Опыт работы и практические знания',
<Entry>[
new Entry(false,"Обладает исключительно большим опытом работы, большими практическими знаниями, такой опыт и такова практика имеются далеко не у каждого."),
new Entry(false,"Обладает большим опытом работы и большими практическими знаниями."),
new Entry(false,"Обладает достаточным опытом работы и практическими знаниями, чтобы успешно справляться с порученным делом."),
new Entry(false,"Опыт работы и практические знания несколько маловаты."),
new Entry(false,"Опыт работы и практические знания недостаточны для того, чтобы успешно справляться со своей работой."),
]),
new Entry(false,'Знания по специальности', <Entry>[
new Entry(false,"Имеет обширные и глубокие знания по своей специальности, широкую общую эрудицию в служебных вопросах. Умело использует свои знания в повседневной работе, может давать ценные консультации."),
new Entry(false,"Имеет обширные и глубокие знания по своей специальности, может дать ценную консультацию. Однако, вопросах деятельности других служб ориентируется недостаточно."),
new Entry(false,"Имеет хорошие знания по своей специальности, достаточную эрудицию в других служебных вопросах."),
new Entry(false,"Имеет достаточные знания по своей специальности, однако в других служебных вопросах разбирается меньше."),
new Entry(false,"Имеет достаточные знания по своей специальности, но совершенно отсутствует осведомленность по другим служебным вопросам."),
new Entry(false,"Явно не достает знаний по своей специальности и совершенно отсутствует осведомленность в других вопросах."),
]),
new Entry(false,'Самостоятельность', <Entry>[
new Entry(false,"Может решать все вопросы, касавшиеся своей работы, совершенно самостоятельно, не ожидая чьей-либо подсказки или указания."),
new Entry(false,"В основном может решать большинство вопросов, касающихся своей работы, самостоятельно, не ожидая подсказки или указания."),
new Entry(false,"Может решать многие вопросы, касающиеся своей работы, более или менее самостоятельно."),
new Entry(false,"Многие вопросы, связанные со своей работой, не может решать самостоятельно, нуждается в известной помощи, подсказках и указаниях."),
new Entry(false,"Вопросы, связанные со своей работой, не может решать самостоятельно, нуждается в помощи, подсказках и указаниях."),
new Entry(false,"Не может решать самостоятельно даже самые простые вопросы, связанные со своей собственной работой, постоянно нуждается в помощи, подсказках и указаниях."),
]),
new Entry(false,'Самообразование', <Entry>[
new Entry(false,"Постоянно, много и упорно занимается самообразованием по своей специальности и это очень положительно сказывается на работе."),
new Entry(false,"Много и упорно занимается самообразованием по своей специальности и это заметно сказывается на улучшении работы."),
new Entry(false,"Успешно сочетает работу с самообразованием по своей специальности."),
new Entry(false,"Понимает пользу самообразования и по мере возможности стремится пополнить свои знания по избранной специальности."),
new Entry(false,"Признает на словах необходимость самообразования, однако, никаких успехов в этом не заметно."),
new Entry(false,"Отрицает необходимость самообразования, что не приносит пользы, делу."),
]),
],
),
new Entry(false,
'Работоспособность',
<Entry>[
new Entry(false,'Результативность', <Entry>[
new Entry(false,"В своей работе постоянно добивается высоких результатов, своим примером воодушевляет коллег."),
new Entry(false,"В своей работе постоянно добивается хороших результатов, вносит важный вклад в работу коллектива."),
new Entry(false,"Работает ровно, без срывов, трудовая отдача соответствует предъявленным требованиям."),
new Entry(false,"Работает неровно, наряду с успехами в служебной деятельности бывают и отдельные срывы."),
new Entry(false,"Работает недостаточно интенсивно, не всегда добивается требуемых результатов, иногда допускает серьезные срывы."),
new Entry(false,"Работает плохо. Результаты работы хронически не отвечают предъявленным требованиям."),
]),
new Entry(false,'Отношение к работе', <Entry>[
new Entry(false,"Очень любит свою работу, практически уделяет ей все свое свободное время и энергию."),
new Entry(false,"Любит свою работу."),
new Entry(false,"К своей работе относится добросовестно."),
new Entry(false,"К своей работе относится равнодушно."),
new Entry(false,"Не любит свою работу, но выполняет ее добросовестно."),
new Entry(false,"Крайне не любит свою работу и повсюду говорит об этом."),
]),
new Entry(false,'Интенсивность работы', <Entry>[
new Entry(false,"В работе показывает очень высокую интенсивность, способность работать за пятерых."),
new Entry(false,"В работе показывает высокую интенсивность."),
new Entry(false,"В работе показывает достаточную интенсивность."),
new Entry(false,"В работе показывает недостаточную интенсивность."),
new Entry(false,"В работе показывает низкую интенсивность."),
new Entry(false,"В работе показывает крайне низкую интенсивность, нуждается в постоянном понукании."),
]),
],
),
new Entry(false,
'Деловитость',
<Entry>[
new Entry(false,'Соблюдение сроков', <Entry>[
new Entry(false,'Всегда все делает вовремя, всегда укладывается в срок, в связи с этим можно совершенно не беспокоиться.'),
new Entry(false,'Обычно все делает вовремя и укладывается в срок - можно положиться.'),
new Entry(false,'В основном порученные задания выполняет в срок и других товарищей не подводит.'),
new Entry(false,'Не всегда выполняет порученную работу вовремя, иногда не укладывается в срок, но в особо ответственных случаях старается не подводить других товарищей.'),
new Entry(false,'Часто при выполнении порученных заданий не укладывается в срок и подводит этим других товарищей.'),
new Entry(false,'Постоянно не укладывается в срок и подводит этим других товарищей, на такого человека совершенно невозможно положиться.'),
]),
new Entry(false,'Качество работы', <Entry>[
new Entry(false,'Работает практически всегда без ошибок.'),
new Entry(false,'Работает, в основном, без ошибок, если и допускает ошибки в работе, то они не отражаются на конечных результатах.'),
new Entry(false,'Редко допускает в работе ошибки, как правило, лишь незначительные.'),
new Entry(false,'Допускает в работе ошибки, и иногда это сказывается на конечных результатах.'),
new Entry(false,'Часто допускает в работе ошибки, в тон числе и довольно грубые.'),
new Entry(false,'Постоянно допускает в работе грубейшие ошибки.'),
]),
new Entry(false,'Отношение к нововведениям',
<Entry>[
new Entry(false,'Любит различные нововведения и реорганизации. Но не любит работать в нормальном спокойном режиме.'),
new Entry(false,'Порой излишне увлекается различными новшествами и реорганизациями в ущерб текущей работе.'),
new Entry(false,'Стремится вовремя поддержать любое начинание.'),
new Entry(false,'Может поддержать полезное назначение, хотя не особенно любит различные нововведения и реорганизации.'),
new Entry(false,'Порой проявляет изливший консерватизм, не любит различных нововведений и реорганизаций.'),
new Entry(false,'Проявляет крайний консерватизм, выступает против всякого нововведения.'),
],
),
],
),
];

The main problem in you code is, You are passing an object to EntryItem(data[index]) but there is no constructor.
Replace your below code of "EntryItem" Class
class EntryItem extends StatefulWidget {
// Entry entry;
EntryItem(Entry entry);
#override
_EntryItemState createState() {
return new _EntryItemState();
}
}
With this:
class EntryItem extends StatefulWidget {
Entry entry;
EntryItem(Entry entry){
this.entry=entry;
}
#override
_EntryItemState createState() {
return new _EntryItemState(entry);
}
}
And add below constructor to _EntryItemState class:
...
_EntryItemState(Entry entry){
this.entry = entry;
}
...
And your error will be gone.

Related

The following _TypeError was thrown building RecommendedFoodDetail (dirty): type 'Null' is not a subtype of type 'int'

I have this error code:
======== Exception caught by widgets library =======================================================
The following _TypeError was thrown building RecommendedFoodDetail(dirty):
type 'Null' is not a subtype of type 'int'
The relevant error-causing widget was:
RecommendedFoodDetail RecommendedFoodDetail:file:///C:/Users/IMAN/StudioProjects/food_app/lib/routes/route_helper.dart:33:14
When the exception was thrown, this was the stack:
#0 PopularProductController.initProduct (package:food_app/controllers/popular_product_controller.dart:80:7)
#1 RecommendedFoodDetail.build (package:food_app/pages/food/recommended_food_detail.dart:24:42)
#2 StatelessElement.build (package:flutter/src/widgets/framework.dart:4949:49)
#3 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4878:15)
#4 Element.rebuild (package:flutter/src/widgets/framework.dart:4604:5)
#5 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4859:5)
#6 ComponentElement.mount (package:flutter/src/widgets/framework.dart:4853:5)
... Normal element mounting (154 frames)
I'm typing the codes the same as my instructure but I don't know why I got that error.
Thanks in advance
cart_page.dart
GestureDetector(
onTap: (){
var popularIndex=Get.find<PopularProductController>()
.popularProductList
.indexOf(_cartList[index].product);
if( popularIndex>=0){
Get.toNamed(RouteHelper.getPopularFood(popularIndex,"cartpage"));
}else{
var recommendedIndex=Get.find<RecommendedProductController>()
.recommendedProductList
.indexOf(_cartList[index].product);
Get.toNamed(RouteHelper.getRecommendedFood(recommendedIndex,"cartpage"));
}
},
child: Container(
width: Dimensions.height20*5,
height: Dimensions.height20*5,
margin: EdgeInsets.only(bottom: Dimensions.height10),
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(
AppConnstants.BASE_URL+AppConnstants.UPLOAD_URL+cartController.getItems[index].img!
)
),
borderRadius: BorderRadius.circular(Dimensions.radius20),
),
),
),
cart_controller.dart
bool existInCart(ProductModel product) {
if (_items.containsKey(product.id)) {
return true;
}
return false;
}
getQuantity(ProductModel product) {
var quantity = 0;
if (_items.containsKey(product.id)) {
_items.forEach((key, value) {
if (key == product.id) {
quantity = value.quantity!;
}
});
}
}
int get totalItems {
var totalQuantity = 0;
_items.forEach((key, value) {
totalQuantity += value.quantity!;
});
return totalQuantity;
}
List<CartModel> get getItems {
return _items.entries.map((e){
return e.value;
}).toList();
}
}
route_helper.dart
class RouteHelper{
static const String initial="/";
static const String popularFood="/popular-food";
static const String recommendedFood="/recommended-food";
static const String cartPage="/cart-page";
static String getInitial()=>'$initial';
static String getPopularFood(int pageId, String page)=>'$popularFood?pageId=$pageId&page=$page';
static String getRecommendedFood(int pageId, String page )=>'$recommendedFood?pageId=$pageId&page=$page';
static String getCartPage()=> '$cartPage';
static List<GetPage> routes=[
GetPage(name: initial, page: ()=>MainFoodPage()),
GetPage(name: popularFood, page: (){
var pageId= Get.parameters['pageId'];
var page=Get.parameters["page"];
return Container(
child: PopularFoodDetail(pageId:int.parse(pageId!),page:page!));
},
transition: Transition.fadeIn
),
GetPage(name: recommendedFood, page: (){
var pageId= Get.parameters['pageId'];
var page=Get.parameters["page"];
return RecommendedFoodDetail(pageId:int.parse(pageId!),page: page!);
},
transition: Transition.fadeIn
),
GetPage(name: cartPage, page: (){
return CartPage();
},
transition: Transition.fadeIn
)
];
}
recommended_food_detail.dart
GetBuilder<PopularProductController>(
builder: (controller){
return GestureDetector(
onTap: (){
if( controller.totalItems>=1)
Get.toNamed(RouteHelper.getCartPage());
},
child: Stack(
children: [
AppIcon(icon: Icons.shopping_cart),
Get.find<PopularProductController>().totalItems>=1?
Positioned(
right:0, top:0,
child: AppIcon(icon: Icons.circle,
size: 20,
iconColor: Colors.transparent,
backgroundColor:AppColors.mainColor,),
):
Container(),
Get.find<PopularProductController>().totalItems>=1?
Positioned(
right:3, top:3,
child:BigText(text:Get.find<PopularProductController>().totalItems.toString(),
size: 12, color: Colors.white,
)
):
Container(),
],
),
);
}),
popular_foor_details.dart
GestureDetector(
onTap:(){
if(page=="cartpage"){
Get.toNamed(RouteHelper.getCartPage());
}else{
Get.toNamed(RouteHelper.getInitial());
}
},
child: AppIcon(icon: Icons.arrow_back_ios,)),
.....................

change single char style inside string when it will be clicked flutter

for example i have word welcome i want to change color of c when ever user clicks the letter c i get the position but i can not chnage this position to an index of the string.
i used RichText for using different style in one string and this is the code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:positioned_tap_detector/positioned_tap_detector.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,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
RichText rich;
String word;
String letter;
String pressedLetter;
Color color = Colors.black;
TapPosition _position = TapPosition(Offset.zero, Offset.zero);
int x=0;
int y=0;
#override
Widget build(BuildContext context) {
word = 'ویژدان';
letter = 'د';
var blackRt = new GestureDetector(
child: Text(
word,
style: TextStyle(fontSize: 25),
),
onTap: () {
setState(() {
color = Colors.red;
});
});
var redRt = useDifferentDesignInAString(word, Colors.red);
Widget currentRT = new Container();
if (color == Colors.black)
currentRT = blackRt;
else if (color == Colors.red) currentRT = redRt;
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: PositionedTapDetector(
onTap: _onTap,
child: currentRT,
),
),
);
}
void _onTap(TapPosition position) {
setState((){
_position = position;
x=position.global.dx.toInt();
y=position.global.dy.toInt();
String pressed=word[offset];
});
}
RichText useDifferentDesignInAString(String word, Color color) {
String firstPart = "";
String remain = "";
RichText richText;
if (word.startsWith(letter)) {
richText = RichText(
text: TextSpan(
text: letter,
style: TextStyle(color: color, fontSize: 25),
children: <TextSpan>[
TextSpan(
text: word.substring(1, word.length),
style: TextStyle(color: Colors.black, fontSize: 25)),
],
),
);
} else if (word.endsWith(letter)) {
richText = RichText(
text: TextSpan(
text: word.substring(0, word.length - 2),
style: TextStyle(color: Colors.black, fontSize: 25),
children: <TextSpan>[
TextSpan(text: letter, style: TextStyle(color: color, fontSize: 25))
],
),
);
} else {
for (int i = 0; i < word.length; i++) {
if (word[i] != letter && i < word.indexOf(letter)) {
firstPart = firstPart + word[i];
} else if (word[i] != letter && i > word.indexOf(letter)) {
remain = remain + word[i];
}
}
richText = RichText(
text: TextSpan(
text: firstPart,
style: TextStyle(color: Colors.black, fontSize: 25),
children: <TextSpan>[
TextSpan(
text: letter,
style: TextStyle(color: color, fontSize: 25),
),
TextSpan(
text: remain,
style: TextStyle(color: Colors.black, fontSize: 25)),
],
),
);
}
return richText;
}
}
the problem is inside the ontap method that when i found the position i can not convert it to index to get the real char that clicked if it is c chnage the color other wise show toast.

How to achieve coplanar card layout in flutter

I'm trying to achieve a coplanar/disallined card collection layout in flutter. This is on Card layout on material Design documentation https://imgur.com/miHhpFs
I''ve tried with a GridView.count layout, but can't figure out how to disalline items. I also found out that there are user created libraries like this https://pub.dev/packages/flutter_staggered_grid_view that can help with what I want to do, but I'd prefer an official solution since this layout is on the material design documentation.
I don't know what the "official solution" for creating this is, but I believe it would be something along the lines of "Create three list views with three scroll controllers, one of which is offset from the other two, and then sync up their scroll controllers, accounting for the offset."
I didn't know if this would work or not, so I created this dartpad in order to test it:
https://dartpad.dev/f9c8f00b78899d3c8c4a426d3466a8a3
Just in case the dartpad doesn't work, here is the code that I used:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ScrollSync(),
);
}
}
class ScrollSync extends StatefulWidget {
#override
_ScrollSyncState createState() => _ScrollSyncState();
}
class _ScrollSyncState extends State<ScrollSync> {
CustomScrollController _controller1 =
CustomScrollController(keepScrollOffset: true);
CustomScrollController _controller2 = CustomScrollController(
initialScrollOffset: 150.0, keepScrollOffset: true);
CustomScrollController _controller3 =
CustomScrollController(keepScrollOffset: true);
#override
void initState() {
_controller1.addListener(() =>
_controller2.jumpToWithoutGoingIdleAndKeepingBallistic(
_controller1.offset, "1 listen 2"));
_controller1.addListener(() =>
_controller3.jumpToWithoutGoingIdleAndKeepingBallistic(
_controller1.offset, "1 listen 3"));
_controller2.addListener(() =>
_controller1.jumpToWithoutGoingIdleAndKeepingBallistic(
_controller2.offset, "2 listen 1"));
_controller2.addListener(() =>
_controller3.jumpToWithoutGoingIdleAndKeepingBallistic(
_controller2.offset, "2 listen 3"));
_controller3.addListener(() =>
_controller1.jumpToWithoutGoingIdleAndKeepingBallistic(
_controller3.offset, "3 listen 1"));
_controller3.addListener(() =>
_controller2.jumpToWithoutGoingIdleAndKeepingBallistic(
_controller3.offset, "3 listen 2"));
super.initState();
}
#override
void dispose() {
_controller1.dispose();
_controller2.dispose();
_controller3.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Scroll Sync"),
),
body: Row(
children: [
Flexible(
flex: 1,
child: ListView.builder(
controller: _controller1,
scrollDirection: Axis.vertical,
itemBuilder: (_, index) => Container(
color: Colors.blueGrey,
width: 150,
height: 300,
margin: EdgeInsets.all(10.0),
child: Center(
child: Text(
"$index",
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.display2,
),
),
),
),
),
Flexible(
flex: 1,
child: ListView.builder(
controller: _controller2,
itemBuilder: (_, index) => Container(
height: 300,
color: Colors.blueGrey,
margin: EdgeInsets.all(10.0),
child: Center(
child: Text(
"$index",
style: Theme.of(context)
.textTheme
.display2
.copyWith(color: Colors.white),
),
),
),
),
),
Flexible(
flex: 1,
child: ListView.builder(
controller: _controller3,
itemBuilder: (_, index) => Container(
height: 300,
color: Colors.blueGrey,
margin: EdgeInsets.all(10.0),
child: Center(
child: Text(
"$index",
style: Theme.of(context)
.textTheme
.display2
.copyWith(color: Colors.black),
),
),
),
),
),
],
),
);
}
}
class CustomScrollController extends ScrollController {
CustomScrollController(
{double initialScrollOffset = 0.0,
keepScrollOffset = true,
debugLabel,
String controller})
: super(
initialScrollOffset: initialScrollOffset,
keepScrollOffset: keepScrollOffset,
debugLabel: debugLabel,
);
#override
_SilentScrollPosition createScrollPosition(
ScrollPhysics physics,
ScrollContext context,
ScrollPosition oldPosition,
) {
return _SilentScrollPosition(
physics: physics,
context: context,
oldPosition: oldPosition,
initialPixels: initialScrollOffset,
);
}
void jumpToWithoutGoingIdleAndKeepingBallistic(
double value, String controller) {
assert(positions.isNotEmpty, 'ScrollController not attached.');
for (_SilentScrollPosition position
in new List<ScrollPosition>.from(positions))
position.jumpToWithoutGoingIdleAndKeepingBallistic(value, controller);
}
}
class _SilentScrollPosition extends ScrollPositionWithSingleContext {
_SilentScrollPosition({
ScrollPhysics physics,
ScrollContext context,
ScrollPosition oldPosition,
double initialPixels,
}) : super(
physics: physics,
context: context,
oldPosition: oldPosition,
initialPixels: initialPixels,
);
void jumpToWithoutGoingIdleAndKeepingBallistic(
double value, String controller) {
print(controller);
print(value);
print(pixels);
if (controller[0] == "2") {
if (pixels + 150.0 != value) {
forcePixels(value - 150.0);
}
} else if (controller[9] == "2") {
if (pixels - 150.0 != value) {
forcePixels(value + 150.0);
}
} else if (pixels != value) {
forcePixels(value);
}
}
}

adding dropdown menu in alert dialog box in flutter

I'm having UI rendering issues when I attempt to add a drop down menu with a date and time picker in an alert dialog box in my flutter application. I've been trying to troubleshoot this for quite some time now but to no avail. This is the output that I am getting:
image output
My Code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:test_prep/utils/Reminder.dart';
import 'package:intl/intl.dart';
class RemindersPage extends StatefulWidget {
#override
_RemindersPageState createState() => _RemindersPageState();
}
class _RemindersPageState extends State<RemindersPage> {
final TextEditingController _titleController = new
TextEditingController();
List<DropdownMenuItem<Future>> dateDrop = [];
List<DropdownMenuItem<Future>> timeDrop = [];
int selected = null;
void loadDateData() {
dateDrop = [];
dateDrop.add(new DropdownMenuItem(
child: new Text('Pick Date'),
value: _selectedDate(context),
));
}
void loadTimeData() {
timeDrop = [];
timeDrop.add(new DropdownMenuItem(
child: new Text('Pick a Time'),
value: _selectedTime(context),
));
}
#override
Widget build(BuildContext context) {
// loadDateData();
// loadTimeData();
return Scaffold(
backgroundColor: Colors.black87,
body: Column(children: <Widget>[]),
// Floating Action button
floatingActionButton: new FloatingActionButton(
tooltip: "Add Item",
backgroundColor: Colors.greenAccent,
child: new ListTile(
title: Icon(
Icons.add,
),
),
onPressed: _showFormDialog),
bottomNavigationBar: new Theme(
data: Theme.of(context)
.copyWith(canvasColor: Colors.grey, primaryColor:
Colors.white),
child: new BottomNavigationBar(
items: [
new BottomNavigationBarItem(
icon: new Icon(Icons.filter_none),
title: new Text("Reminders")),
new BottomNavigationBarItem(
icon: new Icon(Icons.all_out), title: new Text("Quizes"))
],
onTap: (int i) => debugPrint("You tapped $i"),
),
),
);
}
// Alert Dialog
void _showFormDialog() {
var alert = new AlertDialog(
title: Text("Set Reminder"),
content: Column(children: <Widget>[
Expanded(
child: TextField(
controller: _titleController,
autofocus: true,
decoration: InputDecoration(
labelText: 'Name of Reminder',
hintText: "eg. Test on Thursday!",
icon: Icon(Icons.title),
),
),
),
// Date
_dropDownDate(),
// Time
_dropDownTime()
]),
actions: <Widget>[
new FlatButton(
onPressed: () => debugPrint("Save button"), child:
Text('Save')),
new FlatButton(
onPressed: () => Navigator.pop(context), child:
Text('Cancel'))
],
);
showDialog(
context: context,
builder: (_) {
return alert;
});
}
// Date and time picker
DateTime _date = new DateTime.now();
TimeOfDay _time = new TimeOfDay.now();
Future<Null> _selectedDate(BuildContext context) async {
final DateTime picked = await showDatePicker(
context: context,
initialDate: _date,
firstDate: new DateTime(2018),
lastDate: new DateTime(2019));
if (picked != null) {
debugPrint('Date selected: ${_date.toString()}');
setState(() {
_date = picked;
});
}
}
Future<Null> _selectedTime(BuildContext context) async {
final TimeOfDay picked =
await showTimePicker(context: context, initialTime: _time);
if (picked != null && picked != _time) {
debugPrint('Time selected: ${_time.toString()}');
setState(() {
_time = picked;
});
}
}
_dropDownDate() {
var drop_date = Container(
child: Row(mainAxisAlignment: MainAxisAlignment.start, children:
[
DropdownButton(
value: selected,
items: dateDrop,
hint: Text('Pick a date'),
onChanged: (value) {
selected = value;
setState(() {});
}),
]));
return drop_date;
}
_dropDownTime() {
var drop_time = Container(
child: Row(mainAxisAlignment: MainAxisAlignment.start, children:
[
DropdownButton(
value: selected,
items: timeDrop,
hint: Text('Pick a time'),
onChanged: (value) {
selected = value;
setState(() {});
}),
]));
return drop_time;
}
}
Flutter runtime message:
Syncing files to device iPhone X...
flutter: ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
flutter: The following ArgumentError was thrown during paint():
flutter: Invalid argument(s): 0.0
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #0 double.clamp (dart:core/runtime/libdouble.dart:144:7)
flutter: #1 _DropdownMenuPainter.paint (package:flutter/src/material/dropdown.dart:57:33)
flutter: #2 RenderCustomPaint._paintWithPainter (package:flutter/src/rendering/custom_paint.dart:520:13)
flutter: #3 RenderCustomPaint.paint (package:flutter/src/rendering/custom_paint.dart:558:7)
flutter: #4 RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2085:7)
flutter: #5 PaintingContext.paintChild (package:flutter/src/rendering/object.dart:171:13)
flutter: #6 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:126:15)
flutter: #7 PaintingContext.pushLayer (package:flutter/src/rendering/object.dart:367:12)
flutter: #8 PaintingContext.pushOpacity (package:flutter/src/rendering/object.dart:491:5)
flutter: #9 RenderAnimatedOpacity.paint (package:flutter/src/rendering/proxy_box.dart:904:15)
flutter: #10 RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2085:7)
flutter: #11 PaintingContext.paintChild (package:flutter/src/rendering/object.dart:171:13)
flutter: #12 RenderShiftedBox.paint (package:flutter/src/rendering/shifted_box.dart:70:15)
flutter: #13 RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2085:7)
flutter: #14 PaintingContext.paintChild (package:flutter/src/rendering/object.dart:171:13)
flutter: #15 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:126:15)
flutter: #16 RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2085:7)
flutter: #17 PaintingContext._repaintCompositedChild (package:flutter/src/rendering/object.dart:128:11)
flutter: #18 PaintingContext.repaintCompositedChild (package:flutter/src/rendering/object.dart:96:5)
flutter: #19 PipelineOwner.flushPaint (package:flutter/src/rendering/object.dart:852:29)
flutter: #20 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:272:19)
flutter: #21 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:654:13)
flutter: #22 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:208:5)
flutter: #23 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:990:15)
flutter: #24 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:930:9)
flutter: #25 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:842:5)
flutter: #26 _invoke (dart:ui/hooks.dart:128:13)
flutter: #27 _drawFrame (dart:ui/hooks.dart:117:3)
flutter:
flutter: The following RenderObject was being processed when the exception was fired:
flutter: RenderCustomPaint#556fb relayoutBoundary=up2
flutter: creator: CustomPaint ← FadeTransition ← _DropdownMenu<Object> ← CustomSingleChildLayout ← Builder
flutter: ← MediaQuery ← Builder ← RepaintBoundary-[GlobalKey#4c3ae] ← IgnorePointer ← AnimatedBuilder ←
flutter: RepaintBoundary ← _FocusScopeMarker ← ⋯
flutter: parentData: <none> (can use size)
flutter: constraints: BoxConstraints(w=148.0, 0.0<=h<=716.0)
flutter: size: Size(148.0, 16.0)
flutter: This RenderObject had the following descendants (showing up to depth 5):
flutter: RenderSemanticsAnnotations#151e5 relayoutBoundary=up3 NEEDS-PAINT
flutter: RenderCustomPaint#b9211 relayoutBoundary=up4 NEEDS-PAINT
flutter: _RenderInkFeatures#13585 relayoutBoundary=up5 NEEDS-PAINT
flutter: RenderRepaintBoundary#ed5d6 relayoutBoundary=up6 NEEDS-PAINT
flutter: RenderCustomPaint#f5290 relayoutBoundary=up7 NEEDS-PAINT
flutter: ════════════════════════════════════════════════════════════════════════════════════════════════════
flutter: Another exception was thrown: Invalid argument(s): 0.0
flutter: Another exception was thrown: Invalid argument(s): 0.0
flutter: Another exception was thrown: Invalid argument(s): 0.0
flutter: Another exception was thrown: Invalid argument(s): 0.0
flutter: Another exception was thrown: Invalid argument(s): 0.0
flutter: Another exception was thrown: Invalid argument(s): 0.0
[C2.1 748F7D32-4C53-4798-B15B-8E3BDB7D9006 2601:196:4801:b518:c076:dbf1:83d3:ada3.50444<->2607:f8b0:4002:c08::8b.443]
Connected Path: satisfied (Path is satisfied), interface: en0
Duration: 121.165s, DNS #0.004s took 0.008s, TCP #0.018s took 0.055s, TLS took 0.137s
bytes in/out: 3879/764, packets in/out: 10/8, rtt: 0.056s, retransmitted packets: 0, out-of-order packets: 0
[C3.1 67E05789-ED99-4A78-A900-D136E04B908C 2601:196:4801:b518:c076:dbf1:83d3:ada3.50445<->2607:f8b0:4002:c08::8b.443]
Connected Path: satisfied (Path is satisfied), interface: en0
Duration: 120.368s, DNS #0.002s took 0.004s, TCP #0.008s took 0.056s, TLS took 0.137s
bytes in/out: 3591/1188, packets in/out: 9/9, rtt: 0.058s, retransmitted packets: 0, out-of-order packets: 0
[C1.1 89EEE9E8-79C7-4861-9FD9-148DA484E6BE 2601:196:4801:b518:c076:dbf1:83d3:ada3.50428<->2607:f8b0:4002:813::200a.443]
Connected Path: satisfied (Path is satisfied), interface: en0
Duration: 240.659s, DNS #0.002s took 0.031s, TCP #0.036s took 0.058s, TLS took 0.563s
bytes in/out: 3394/1027, packets in/out: 10/9, rtt: 0.057s, retransmitted packets: 0, out-of-order packets: 0
You need to create a new StatefulWidget class that should return your AlertDialog
Future _showDialog(context) async {
return await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
//your code dropdown button here
]),
);
},
),
);
},
);
}
You can see flutter's documentation here :
Flutter AlertDialog Stateful Widget
Well, your code has several gotchas.
The first one is that you are parameterizing the DropdownMenuItem with a Future. I'm afraid that is not possible. The documentation says that they must be consistent types. I think the error you receive is because the framework can not paint the values associated to those types.
Also you have forgotten to initialize the dropdown lists.
I give you a solution that works. I hope this is something you are after. It uses a custom dropdown button that displays the Timepicker widget instead of showing the list menu.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new RemindersPage(),
);
}
}
class RemindersPage extends StatefulWidget {
#override
_RemindersPageState createState() => _RemindersPageState();
}
class _RemindersPageState extends State<RemindersPage> {
final TextEditingController _titleController = new TextEditingController();
DateTime _date = new DateTime.now();
onDateChanged(DateTime date) {
_date = date;
//setState(() {}); //optional
}
// Alert Dialog
void _showFormDialog() {
var alert = new AlertDialog(
title: Text("Set Reminder"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
controller: _titleController,
autofocus: true,
decoration: InputDecoration(
labelText: 'Name of Reminder',
hintText: "eg. Test on Thursday!",
icon: Icon(Icons.title),
),
),
// Date
SelectDateButton(
date: _date,
dateCallback: onDateChanged,
),
],
),
actions: <Widget>[
new FlatButton(
onPressed: () => debugPrint("Save button"), child: Text('Save')),
new FlatButton(
onPressed: () => Navigator.pop(context), child: Text('Cancel'))
],
);
showDialog(
context: context,
builder: (_) {
return alert;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black87,
body: Column(children: <Widget>[]),
// Floating Action button
floatingActionButton: new FloatingActionButton(
tooltip: "Add Item",
backgroundColor: Colors.greenAccent,
child: new ListTile(title: Icon(Icons.add)),
onPressed: _showFormDialog,
),
);
}
}
class SelectDateButton extends StatefulWidget {
final DateTime date;
final ValueChanged<DateTime> dateCallback;
SelectDateButton({Key key, this.date, this.dateCallback}) : super(key: key);
#override
SelectDateButtonState createState() {
return new SelectDateButtonState();
}
}
class SelectDateButtonState extends State<SelectDateButton> {
DateTime _date;
#override
void initState() {
super.initState();
_date = widget.date;
}
void selectDate(BuildContext context) async {
final DateTime picked = await showDatePicker(
context: context,
initialDate: _date,
firstDate: new DateTime(2018),
lastDate: new DateTime(2019),
);
if (picked != null) {
widget.dateCallback(picked);
_date = picked;
setState(() {});
}
}
#override
Widget build(BuildContext context) {
return FlatButton(
onPressed: () => selectDate(context),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("${_date.toString().substring(0, 10)}"),
Icon(Icons.arrow_drop_down),
],
),
);
}
}

Replace text if it will overflow

I'm wondering if there's a way in Flutter to show alternate text if the original text will overflow.
Example:
I'm by default showing a full date: January 1, 2019.
However, if I'm on a small screen and it would overflow (January 1...), I'd like to instead display a different string (1/1/2019).
The current Text implementation doesn't allow for this kind of logic. You will need to override their implementation with custom overflow logic.
The modification is trivial, but bear in mind that in case of an overflow you're actually computing the text twice.
The modification needs to be done inside RenderParagraph's performLayout.
In short, something like that :
performLayout()
layout();
if (overflow) {
layoutWithText(text);
}
}
Which then requires a custom RichText to use your new RenderParagraph. And then a new Text class, to use your new RichText.
Quite a lot of copy paste. But fortunately I'll do it for you :D
Here's an example rendering the same Super long text twice. Once without enough size, the other with no limitation.
Achieved using the following code :
new Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new SizedBox(
width: 70.0,
child: new Card(
child: new MyText(
"Super long text",
maxLines: 1,
overflowBuilder: (size) {
return new TextSpan(
text: "Hello", style: new TextStyle(color: Colors.red));
},
),
),
),
new Card(
child: new MyText(
"Super long text",
maxLines: 1,
overflowBuilder: (size) {
return new TextSpan(
text: "Hello", style: new TextStyle(color: Colors.red));
},
),
),
],
);
And here's the fully working example (with RenderParagraph changes and stuff)
import 'dart:async';
import 'package:flutter/rendering.dart';
import 'dart:ui' as ui show Gradient, Shader, TextBox;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final scrollController = new ScrollController();
final videoRef = Firestore.instance.collection('videos');
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new SizedBox(
width: 70.0,
child: new Card(
child: new MyText(
"Super long text",
maxLines: 1,
overflowBuilder: (size) {
return new TextSpan(
text: "Hello", style: new TextStyle(color: Colors.red));
},
),
),
),
new Card(
child: new MyText(
"Super long text",
maxLines: 1,
overflowBuilder: (size) {
return new TextSpan(
text: "Hello", style: new TextStyle(color: Colors.red));
},
),
),
],
),
),
);
}
}
class OverflowText extends LeafRenderObjectWidget {
final TextSpan textSpan;
final TextAlign textAlign;
final TextDirection textDirection;
final bool softWrap;
final TextOverflow overflow;
final double textScaleFactor;
final int maxLines;
final TextOverflowBuilder overflowBuilder;
OverflowText(
{this.textSpan,
this.textAlign: TextAlign.start,
this.textDirection,
this.softWrap: true,
this.overflow: TextOverflow.clip,
this.maxLines,
this.overflowBuilder,
this.textScaleFactor: 1.0});
#override
RenderObject createRenderObject(BuildContext context) {
return new OverflowTextRenderObject(this.textSpan,
textAlign: textAlign,
textDirection: textDirection ?? Directionality.of(context),
softWrap: softWrap,
overflow: overflow,
textScaleFactor: textScaleFactor,
maxLines: maxLines,
overflowBuilder: overflowBuilder);
}
#override
void updateRenderObject(
BuildContext context, OverflowTextRenderObject renderObject) {
renderObject
..text = textSpan
..textAlign = textAlign
..textDirection = textDirection ?? Directionality.of(context)
..softWrap = softWrap
..overflow = overflow
..textScaleFactor = textScaleFactor
..overflowBuilder = overflowBuilder
..maxLines = maxLines;
}
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(new StringProperty('textSpan', textSpan.toPlainText()));
}
}
typedef TextSpan TextOverflowBuilder(Size size);
const String _kEllipsis = '\u2026';
/// A render object that displays a paragraph of text
class OverflowTextRenderObject extends RenderBox {
/// Creates a paragraph render object.
///
/// The [text], [textAlign], [textDirection], [overflow], [softWrap], and
/// [textScaleFactor] arguments must not be null.
///
/// The [maxLines] property may be null (and indeed defaults to null), but if
/// it is not null, it must be greater than zero.
OverflowTextRenderObject(
TextSpan text, {
TextAlign textAlign: TextAlign.start,
#required TextDirection textDirection,
bool softWrap: true,
TextOverflow overflow: TextOverflow.clip,
double textScaleFactor: 1.0,
int maxLines,
this.overflowBuilder,
}) : assert(text != null),
assert(text.debugAssertIsValid()),
assert(textAlign != null),
assert(textDirection != null),
assert(softWrap != null),
assert(overflow != null),
assert(textScaleFactor != null),
assert(maxLines == null || maxLines > 0),
_softWrap = softWrap,
_overflow = overflow,
_textPainter = new TextPainter(
text: text,
textAlign: textAlign,
textDirection: textDirection,
textScaleFactor: textScaleFactor,
maxLines: maxLines,
ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null,
);
TextOverflowBuilder overflowBuilder;
final TextPainter _textPainter;
/// The text to display
TextSpan get text => _textPainter.text;
set text(TextSpan value) {
assert(value != null);
switch (_textPainter.text.compareTo(value)) {
case RenderComparison.identical:
case RenderComparison.metadata:
return;
case RenderComparison.paint:
_textPainter.text = value;
markNeedsPaint();
break;
case RenderComparison.layout:
_textPainter.text = value;
_overflowShader = null;
markNeedsLayout();
break;
}
}
/// How the text should be aligned horizontally.
TextAlign get textAlign => _textPainter.textAlign;
set textAlign(TextAlign value) {
assert(value != null);
if (_textPainter.textAlign == value) return;
_textPainter.textAlign = value;
markNeedsPaint();
}
/// The directionality of the text.
///
/// This decides how the [TextAlign.start], [TextAlign.end], and
/// [TextAlign.justify] values of [textAlign] are interpreted.
///
/// This is also used to disambiguate how to render bidirectional text. For
/// example, if the [text] is an English phrase followed by a Hebrew phrase,
/// in a [TextDirection.ltr] context the English phrase will be on the left
/// and the Hebrew phrase to its right, while in a [TextDirection.rtl]
/// context, the English phrase will be on the right and the Hebrew phrase on
/// its left.
///
/// This must not be null.
TextDirection get textDirection => _textPainter.textDirection;
set textDirection(TextDirection value) {
assert(value != null);
if (_textPainter.textDirection == value) return;
_textPainter.textDirection = value;
markNeedsLayout();
}
/// Whether the text should break at soft line breaks.
///
/// If false, the glyphs in the text will be positioned as if there was
/// unlimited horizontal space.
///
/// If [softWrap] is false, [overflow] and [textAlign] may have unexpected
/// effects.
bool get softWrap => _softWrap;
bool _softWrap;
set softWrap(bool value) {
assert(value != null);
if (_softWrap == value) return;
_softWrap = value;
markNeedsLayout();
}
/// How visual overflow should be handled.
TextOverflow get overflow => _overflow;
TextOverflow _overflow;
set overflow(TextOverflow value) {
assert(value != null);
if (_overflow == value) return;
_overflow = value;
_textPainter.ellipsis = value == TextOverflow.ellipsis ? _kEllipsis : null;
markNeedsLayout();
}
/// The number of font pixels for each logical pixel.
///
/// For example, if the text scale factor is 1.5, text will be 50% larger than
/// the specified font size.
double get textScaleFactor => _textPainter.textScaleFactor;
set textScaleFactor(double value) {
assert(value != null);
if (_textPainter.textScaleFactor == value) return;
_textPainter.textScaleFactor = value;
_overflowShader = null;
markNeedsLayout();
}
/// An optional maximum number of lines for the text to span, wrapping if necessary.
/// If the text exceeds the given number of lines, it will be truncated according
/// to [overflow] and [softWrap].
int get maxLines => _textPainter.maxLines;
/// The value may be null. If it is not null, then it must be greater than zero.
set maxLines(int value) {
assert(value == null || value > 0);
if (_textPainter.maxLines == value) return;
_textPainter.maxLines = value;
_overflowShader = null;
markNeedsLayout();
}
void _layoutText({double minWidth: 0.0, double maxWidth: double.infinity}) {
final bool widthMatters = softWrap || overflow == TextOverflow.ellipsis;
_textPainter.layout(
minWidth: minWidth,
maxWidth: widthMatters ? maxWidth : double.infinity);
}
void _layoutTextWithConstraints(BoxConstraints constraints) {
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
}
#override
double computeMinIntrinsicWidth(double height) {
_layoutText();
return _textPainter.minIntrinsicWidth;
}
#override
double computeMaxIntrinsicWidth(double height) {
_layoutText();
return _textPainter.maxIntrinsicWidth;
}
double _computeIntrinsicHeight(double width) {
_layoutText(minWidth: width, maxWidth: width);
return _textPainter.height;
}
#override
double computeMinIntrinsicHeight(double width) {
return _computeIntrinsicHeight(width);
}
#override
double computeMaxIntrinsicHeight(double width) {
return _computeIntrinsicHeight(width);
}
#override
double computeDistanceToActualBaseline(TextBaseline baseline) {
assert(!debugNeedsLayout);
assert(constraints != null);
assert(constraints.debugAssertIsValid());
_layoutTextWithConstraints(constraints);
return _textPainter.computeDistanceToActualBaseline(baseline);
}
#override
bool hitTestSelf(Offset position) => true;
#override
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
assert(debugHandleEvent(event, entry));
if (event is! PointerDownEvent) return;
_layoutTextWithConstraints(constraints);
final Offset offset = entry.localPosition;
final TextPosition position = _textPainter.getPositionForOffset(offset);
final TextSpan span = _textPainter.text.getSpanForPosition(position);
span?.recognizer?.addPointer(event);
}
bool _hasVisualOverflow = false;
ui.Shader _overflowShader;
#visibleForTesting
bool get debugHasOverflowShader => _overflowShader != null;
void _performLayout() {
_layoutTextWithConstraints(constraints);
final Size textSize = _textPainter.size;
final bool didOverflowHeight = _textPainter.didExceedMaxLines;
size = constraints.constrain(textSize);
final bool didOverflowWidth = size.width < textSize.width;
_hasVisualOverflow = didOverflowWidth || didOverflowHeight;
if (_hasVisualOverflow) {
switch (_overflow) {
case TextOverflow.clip:
case TextOverflow.ellipsis:
_overflowShader = null;
break;
case TextOverflow.fade:
assert(textDirection != null);
final TextPainter fadeSizePainter = new TextPainter(
text: new TextSpan(style: _textPainter.text.style, text: '\u2026'),
textDirection: textDirection,
textScaleFactor: textScaleFactor,
)..layout();
if (didOverflowWidth) {
double fadeEnd, fadeStart;
switch (textDirection) {
case TextDirection.rtl:
fadeEnd = 0.0;
fadeStart = fadeSizePainter.width;
break;
case TextDirection.ltr:
fadeEnd = size.width;
fadeStart = fadeEnd - fadeSizePainter.width;
break;
}
_overflowShader = new ui.Gradient.linear(
new Offset(fadeStart, 0.0),
new Offset(fadeEnd, 0.0),
<Color>[const Color(0xFFFFFFFF), const Color(0x00FFFFFF)],
);
} else {
final double fadeEnd = size.height;
final double fadeStart = fadeEnd - fadeSizePainter.height / 2.0;
_overflowShader = new ui.Gradient.linear(
new Offset(0.0, fadeStart),
new Offset(0.0, fadeEnd),
<Color>[const Color(0xFFFFFFFF), const Color(0x00FFFFFF)],
);
}
break;
}
} else {
_overflowShader = null;
}
}
#override
performLayout() {
_performLayout();
if (this._hasVisualOverflow && overflowBuilder != null) {
final replacement = overflowBuilder(size);
_textPainter.text = replacement;
_performLayout();
}
}
#override
void paint(PaintingContext context, Offset offset) {
_layoutTextWithConstraints(constraints);
final Canvas canvas = context.canvas;
assert(() {
if (debugRepaintTextRainbowEnabled) {
final Paint paint = new Paint()
..color = debugCurrentRepaintColor.toColor();
canvas.drawRect(offset & size, paint);
}
return true;
}());
if (_hasVisualOverflow) {
final Rect bounds = offset & size;
if (_overflowShader != null) {
canvas.saveLayer(bounds, new Paint());
} else {
canvas.save();
}
canvas.clipRect(bounds);
}
_textPainter.paint(canvas, offset);
if (_hasVisualOverflow) {
if (_overflowShader != null) {
canvas.translate(offset.dx, offset.dy);
final Paint paint = new Paint()
..blendMode = BlendMode.modulate
..shader = _overflowShader;
canvas.drawRect(Offset.zero & size, paint);
}
canvas.restore();
}
}
Offset getOffsetForCaret(TextPosition position, Rect caretPrototype) {
assert(!debugNeedsLayout);
_layoutTextWithConstraints(constraints);
return _textPainter.getOffsetForCaret(position, caretPrototype);
}
List<ui.TextBox> getBoxesForSelection(TextSelection selection) {
assert(!debugNeedsLayout);
_layoutTextWithConstraints(constraints);
return _textPainter.getBoxesForSelection(selection);
}
TextPosition getPositionForOffset(Offset offset) {
assert(!debugNeedsLayout);
_layoutTextWithConstraints(constraints);
return _textPainter.getPositionForOffset(offset);
}
TextRange getWordBoundary(TextPosition position) {
assert(!debugNeedsLayout);
_layoutTextWithConstraints(constraints);
return _textPainter.getWordBoundary(position);
}
Size get textSize {
assert(!debugNeedsLayout);
return _textPainter.size;
}
#override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config
..label = text.toPlainText()
..textDirection = textDirection;
}
#override
List<DiagnosticsNode> debugDescribeChildren() {
return <DiagnosticsNode>[
text.toDiagnosticsNode(
name: 'text', style: DiagnosticsTreeStyle.transition)
];
}
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(new EnumProperty<TextAlign>('textAlign', textAlign));
properties
.add(new EnumProperty<TextDirection>('textDirection', textDirection));
properties.add(new FlagProperty('softWrap',
value: softWrap,
ifTrue: 'wrapping at box width',
ifFalse: 'no wrapping except at line break characters',
showName: true));
properties.add(new EnumProperty<TextOverflow>('overflow', overflow));
properties.add(new DoubleProperty('textScaleFactor', textScaleFactor,
defaultValue: 1.0));
properties.add(new IntProperty('maxLines', maxLines, ifNull: 'unlimited'));
}
}
class MyText extends StatelessWidget {
const MyText(this.data,
{Key key,
this.style,
this.textAlign,
this.textDirection,
this.softWrap,
this.overflow,
this.textScaleFactor,
this.maxLines,
this.overflowBuilder})
: assert(data != null),
textSpan = null,
super(key: key);
const MyText.rich(this.textSpan,
{Key key,
this.style,
this.textAlign,
this.textDirection,
this.softWrap,
this.overflow,
this.textScaleFactor,
this.maxLines,
this.overflowBuilder})
: assert(textSpan != null),
data = null,
super(key: key);
final String data;
final TextSpan textSpan;
final TextStyle style;
final TextAlign textAlign;
final TextDirection textDirection;
final bool softWrap;
final TextOverflow overflow;
final double textScaleFactor;
final TextOverflowBuilder overflowBuilder;
final int maxLines;
#override
Widget build(BuildContext context) {
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
TextStyle effectiveTextStyle = style;
if (style == null || style.inherit)
effectiveTextStyle = defaultTextStyle.style.merge(style);
return new OverflowText(
textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
textDirection:
textDirection, // RichText uses Directionality.of to obtain a default if this is null.
softWrap: softWrap ?? defaultTextStyle.softWrap,
overflow: overflow ?? defaultTextStyle.overflow,
overflowBuilder: overflowBuilder,
textScaleFactor: textScaleFactor ??
MediaQuery.of(context, nullOk: true)?.textScaleFactor ??
1.0,
maxLines: maxLines ?? defaultTextStyle.maxLines,
textSpan: new TextSpan(
style: effectiveTextStyle,
text: data,
children: textSpan != null ? <TextSpan>[textSpan] : null,
),
);
}
}
I ended up going with a solution inspired by #Mantoska's answer.
import 'package:flutter/widgets.dart';
class OverflowProofText extends StatelessWidget {
const OverflowProofText({#required this.text, #required this.fallback});
final Text text;
final Text fallback;
#override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
child: LayoutBuilder(builder: (BuildContext context, BoxConstraints size) {
final TextPainter painter = TextPainter(
maxLines: 1,
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,
text: TextSpan(
style: text.style ?? DefaultTextStyle.of(context).style,
text: text.data
),
);
painter.layout(maxWidth: size.maxWidth);
return painter.didExceedMaxLines ? fallback : text;
})
);
}
}
Usage:
OverflowProofText(
text: Text('January 1, 2019'),
fallback: Text('1/1/2019', overflow: TextOverflow.fade),
),
Here is a solution that look simpler (or at least shorter) than Remi's.
The idea is that you use LayoutBuilder to wrap your widget, thus getting the BoxConstraints and using that you can use TextPainter to determine if the text would fit in the given BoxConstraints.
Here is a working example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Text Overflow Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(title: Text("DEMO")),
body: TextOverflowDemo(),
),
);
}
}
class TextOverflowDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
int maxLines = 1;
return Container(
color: Colors.white,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 60.0),// set maxWidth to a low value to see the result
child: LayoutBuilder(builder: (context, size) {
String text = 'January 1, 2019';
var exceeded = doesTextFit(text, maxLines, size);
return Column(children: <Widget>[
Text(
exceeded ? '1/1/2019' : text,
overflow: TextOverflow.ellipsis,
maxLines: maxLines,
),
]);
}),
),
);
}
bool doesTextFit(String text, int maxLines, BoxConstraints size,
{TextStyle textStyle}) {
TextSpan span;
if (textStyle == null) {
span = TextSpan(
text: text,
);
} else {
span = TextSpan(text: text, style: textStyle);
}
TextPainter tp = TextPainter(
maxLines: maxLines,
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,
text: span,
);
tp.layout(maxWidth: size.maxWidth);
return tp.didExceedMaxLines;
}
}
I found a bit easier solution for the people out there still looking.
I built my solution on the package: auto_size_text 2.1.0
import 'package:auto_size_text/auto_size_text.dart';
...
AutoSizeText(
"Your text that might be too long. I'm super duper long",
maxLines: 1,
overflowReplacement: Text("I'm the new (small) replacement!"),
// minFontSize: 20
),
Note this package will make the text smaller as well before triggering overflowReplacement. You can set the minimum allowed size in the package by specifying minFontSize.

Resources