When I launch a keyboard (on iOS and Android) I have an extra 50 px padding added above it which shows an empty space.
This isn't caused by other suggested problems where there are multiple Scaffold widgets. I know that this is caused by my layout and I'm not sure if it can be fixed at the moment.
I have a static navigation bar that appears across the app at the bottom. This is 50px high and sits below CupertinoApp. When the keyboard is launched, extra padding is added to fill this space it seems. This navigation bar is a Container widget with InkWell links to open views directly, sort of like CupertinoTabScaffold except I don't have tabs, it just pushes or replaces the current navigation item
Adding resizeToAvoidBottomInset: false, can't be used as this removes the scroll action or doesn't include the 50px so content is hidden.
This problem only occurs when the keyboard is active
The CupertinoApp builder
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => Future<bool>.value(true),
child: Column(
children: <Widget>[
Expanded(
child: DefaultTextStyle(
style: Fonts.defaultFontStyle(),
child: CupertinoApp(
navigatorKey: navigatorKey,
routes: routes.builder,
)),
),
BottomBar(),
],
),
);
}
Adding a minimal example to get across a better idea
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
final NAVIGATION_HEIGHT = 50.0;
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
static const home = "/";
static const about = "/about";
static const products = "/products";
Map<String, WidgetBuilder> builder = {
home: (BuildContext context) => MyPage(),
about: (BuildContext context) => MyPage(),
products: (BuildContext context) => MyPage(),
};
Widget _createButtonsBuilder(int index) {
String pageTitle = builder.keys.toList()[index].substring(1);
if (pageTitle.length == 0) {
pageTitle = "Home";
}
return Expanded(
child: Material(
color: Colors.black87,
child: InkWell(
onTap: () {
navigatorKey.currentState.push(CupertinoPageRoute<void>(
builder: (BuildContext context) => MyPage(
title: "$pageTitle",
navigationHeight: NAVIGATION_HEIGHT,
),
));
},
child: Container(
padding: EdgeInsets.all(10.0),
height: NAVIGATION_HEIGHT,
child: Text(
"$pageTitle",
style: TextStyle(
inherit: true, color: Colors.white, fontSize: 20.0),
textAlign: TextAlign.center,
),
)),
),
);
}
Widget _BottomBar() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: List.generate(builder.keys.toList().length,
(int i) => _createButtonsBuilder(i)),
);
}
#override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.ltr,
child: Container(
color: Colors.white,
child: Column(
children: <Widget>[
Expanded(
child: CupertinoApp(
title: 'Flutter Demo',
routes: builder,
navigatorKey: navigatorKey,
),
),
_BottomBar()
],
),
),
);
}
}
class MyPage extends StatefulWidget {
MyPage({Key key, this.title, this.navigationHeight}) : super(key: key);
final String title;
final double navigationHeight;
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Scrollbar(
child: SingleChildScrollView(
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'${widget.title}',
),
SizedBox(height: 40.0),
Container(
margin: EdgeInsets.all(30.0),
child: CupertinoTextField(
placeholder: "Tap in here",
),
),
Container(
margin: EdgeInsets.all(30.0),
child: Text(
"When tapping to add focus to the above CupertinoTextField Widget the keyboard will appear as normal but because of the _BottomBar() outside the CupertinoApp the height (${widget.navigationHeight}px) is included in the padding above the keyboard")),
Container(
margin: EdgeInsets.all(30.0),
child: Text(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi interdum blandit diam sed faucibus. Donec ut enim in ante luctus sagittis. Donec vestibulum aliquet nunc, in efficitur erat molestie quis. In dictum aliquet neque. Vivamus pharetra nibh dictum urna sodales malesuada. Nunc porta condimentum mi, sed laoreet erat maximus vitae. Nunc luctus nisi urna, a luctus nisi consectetur quis. Sed rhoncus euismod nisl, in laoreet leo molestie sed. Suspendisse aliquet commodo dui, sit amet rhoncus sapien venenatis in. Nulla tempus libero diam, non cursus odio euismod at. Fusce nec ipsum ipsum. Mauris congue blandit risus, vitae pretium leo euismod id. Vivamus venenatis finibus diam id auctor. Donec vel urna finibus erat viverra bibendum. Vivamus sagittis eros id bibendum tristique. Nullam eleifend elit dapibus elit porttitor, ac egestas libero mollis.\n\nMorbi eleifend ligula sed leo placerat tristique. Mauris consequat fringilla maximus. Fusce pharetra ultrices risus, quis fermentum urna ultrices non. Vivamus suscipit nunc non ipsum ultrices laoreet. Etiam sed vestibulum eros, nec tempus neque. Vestibulum efficitur mauris ac ipsum aliquet, et tincidunt massa suscipit. Vivamus enim justo, viverra tempor purus eu, elementum tempor tortor. Sed rhoncus gravida sem, vitae molestie augue iaculis et. Donec augue ligula, interdum id interdum sit amet, condimentum in dolor. Donec dignissim erat lorem, ut accumsan felis porta id. Nunc lorem enim, maximus ut odio at, ultrices sodales velit."))
],
),
),
),
) // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
I wrapped my CupertinoApp in Scaffold widget which exposed bottomNavigationBar.
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => Future<bool>.value(true),
child: Scaffold(
body: DefaultTextStyle(
style: Fonts.defaultFontStyle(),
child: CupertinoApp(
navigatorKey: navigatorKey,
routes: routes.builder,
)),
),
bottomNavigationBar: HomeBottomBar(),
));
}
And then added resizeToAvoidBottomPadding = true to any CupertinoPageScaffold's that have CupertinoTextfield children
Related
I'm trying to build a custom pop-up in Flutter/Dart using the following features:
Non-blocking. The popup should be triggered from a scrollable view and should not block the view from being scrolled after the popup is shown.
The popup should contain custom content that supports pushing and popping pages inside the popup, including scrollable views.
The popup should support an arrow pointing to a specific location on the screen where it was triggered from.
Please refer to the attached images for an example of this implementation in IOS. I desire to achieve the same thing using Flutter/Dart.
The popups included in Flutter framework are rudimentary. Any ideas on how this is done?
I'm not a flutter pro at all but just out of curiosity I tried to implement as much as I can of your targets in my free time.
You can show a popup without blocking the scroll. And you can push and pop the content of popup.
I'm not claiming that it's an efficient or a healthy way to do things. It's not complete but I think you can go on and build on it. If you decide to use it I think it would be best to;
Edit the PopupBase showPopup method to avoid misplacement of popup as it can overflow in certain situations.
The arrow which will point to the clicked location can also be added after the improvement of placement.
class MyScaffold extends StatefulWidget {
const MyScaffold({Key? key}) : super(key: key);
#override
State<MyScaffold> createState() => _MyScaffoldState();
}
class _MyScaffoldState extends State<MyScaffold> {
CustomPopupController customPopupController = CustomPopupController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("NonBlocking PopUp")),
body: Column(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: PopupBase(
child: RichTextContent(),
)),
),
),
],
),
);
}
}
class PopupBase extends StatefulWidget {
const PopupBase({Key? key, required this.child}) : super(key: key);
final Widget child;
#override
State<PopupBase> createState() => _PopupBaseState();
static _PopupBaseState of(BuildContext context) =>
context.findAncestorStateOfType<_PopupBaseState>()!;
}
class _PopupBaseState extends State<PopupBase> {
List<Widget> popupStack = List.empty(growable: true);
#override
void initState() {
super.initState();
popupStack.add(widget.child);
}
void showPopup(CustomPopup popup, Offset position) {
setState(() {
popupStack.add(Positioned(
child: popup,
left: position.dx,
top: position.dy,
));
});
}
void removePopup(CustomPopup popup) {
print("popupbase remove");
setState(() {
popupStack.removeLast();
});
print(popupStack.length);
}
#override
Widget build(BuildContext context) {
return Stack(
children: popupStack,
);
}
}
class CustomPopupController {
late bool Function(Widget) push;
late Widget Function()? pop;
}
class CustomPopup extends StatefulWidget {
CustomPopup({Key? key, required this.content, required this.controller})
: super(key: key);
Widget content;
final CustomPopupController controller;
#override
State<CustomPopup> createState() => _CustomPopupState();
static _CustomPopupState of(BuildContext context) =>
context.findAncestorStateOfType<_CustomPopupState>()!;
}
class _CustomPopupState extends State<CustomPopup> {
#override
void initState() {
super.initState();
widget.controller.push = push;
widget.controller.pop = pop;
}
final List<Widget> _contentStack = List.empty(growable: true);
bool push(Widget widgetToPush) {
_contentStack.add(widget.content);
setState(() {
widget.content = widgetToPush;
});
return false;
}
Widget pop() {
if (_contentStack.isNotEmpty) {
Widget temp = widget.content;
setState(() {
widget.content = _contentStack.last;
_contentStack.removeLast();
});
return temp;
} else {
PopupBase.of(context).removePopup(this.widget);
return widget.content;
}
}
#override
Widget build(BuildContext context) {
return Card(
child: SizedBox(
height: 300,
width: 200,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
color: Colors.red,
height: 25,
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
(_contentStack.isNotEmpty)
? Padding(
padding: EdgeInsets.only(left: 10),
child: InkWell(
onTap: () {
pop();
},
child: Icon(Icons.arrow_back_ios)),
)
: Container(),
Expanded(child: Container()),
Padding(
padding: EdgeInsets.only(left: 10),
child: InkWell(
onTap: () {
pop();
},
child: Icon(Icons.close)),
)
],
),
),
Expanded(
child: SingleChildScrollView(
child: widget.content,
))
],
)),
);
}
}
class RichTextContent extends StatelessWidget {
RichTextContent({Key? key}) : super(key: key);
TextStyle text = TextStyle(color: Colors.black);
TextStyle clickable = TextStyle(color: Colors.blue);
#override
Widget build(BuildContext context) {
CustomPopupController controller = CustomPopupController();
return RichText(
text: TextSpan(children: [
TextSpan(style: text, text: """Lorem ipsum dolor sit """),
TextSpan(
text: 'amet',
style: clickable,
recognizer: TapGestureRecognizer()
..onTapUp = (TapUpDetails details) {
PopupBase.of(context).showPopup(
CustomPopup(content: Text("Popup"), controller: controller),
details.localPosition);
}),
TextSpan(
style: text,
text:
""", consectetur adipiscing elit. Nulla bibendum at massa et euismod. Praesent ex ipsum, ultrices ut rhoncus et, efficitur vehicula mi. Duis sollicitudin dolor sed tristique molestie. Ut eu elit velit. Cras et lorem quis risus mattis porta vitae ac velit. Nunc laoreet malesuada lectus at laoreet. Etiam ut tristique nulla. Pellentesque eros est, pretium sit amet convallis ut, convallis eu justo. Nulla suscipit blandit massa. Vestibulum vitae magna eu urna faucibus hendrerit. Etiam eros nibh, venenatis ac ullamcorper vitae, venenatis ac neque. Aenean vitae erat massa. Aliquam vulputate facilisis volutpat. Sed tincidunt dolor a enim dictum, eget tristique nisi laoreet. Quisque suscipit, odio et mattis mattis, lectus justo lacinia enim, nec finibus mi ante gravida sem.
Nunc ac nunc nec sapien porttitor volutpat. In et accumsan est. Duis non dui porta, pharetra dolor consequat, bibendum lorem. Quisque suscipit sit amet mi ac placerat. Integer cursus, est nec aliquet consequat, felis erat sodales sem, eget maximus ante libero in nibh. Cras et orci magna. Suspendisse viverra nibh eget nulla mattis laoreet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi tortor diam, pulvinar quis erat a, posuere vehicula ante. Sed aliquet et magna nec finibus. Morbi in dapibus risus. Nullam ac imperdiet enim.
Nam enim mauris, volutpat et metus ut, euismod porttitor odio. Duis at orci hendrerit, posuere tortor sed, convallis nibh. Nullam lobortis est eget magna finibus porttitor. Nulla facilisi. Donec bibendum ac lorem eget consectetur. Phasellus at lacinia augue. Integer a mi quam. Morbi malesuada maximus diam. In orci nisi, mollis sed urna eget, fermentum efficitur libero. Cras auctor est sit amet ex aliquet, eu rutrum turpis gravida. Donec mattis, erat eget ultricies accumsan, odio urna egestas lectus, at mattis lorem sem scelerisque dolor. Curabitur aliquet venenatis bibendum.
"""),
]));
}
}
You can find a live example of this. Using selectable plugin.
It is not an easy task but you can Modify this code to do what you want.
EDIT:
ENTIRE DART FILE -- Here is the entire dart file. Im not seeing how to call the method in the ARR declaration and return the correct mapping. Just need to generate the array from the http call and we are good. This is our first run at returning multiple records from JSON and iterating thru it.
import 'package:flutter/material.dart';
import 'package:lightbridge_mobile/screens/forum/assets/colors.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:lightbridge_mobile/screens/forum/assets/app_bar_forum.dart';
import 'package:lightbridge_mobile/models/forum_answers.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
class ForumDetailPage extends StatefulWidget {
#override
_ForumDetailPageState createState() => new _ForumDetailPageState();
}
class _ForumDetailPageState extends State<ForumDetailPage> {
#override
Widget build(BuildContext context) {
var questionSection = new Padding(
padding: const EdgeInsets.all(8.0),
child: new Column(
children: <Widget>[
new Text(
// Post Title
"How do I become a expert in programming as well as design ??",
textScaleFactor: 1.5,
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: 15.0 ),
),
new Padding(
padding: const EdgeInsets.all(10.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
new IconWithText(Icons.laptop_windows, "Technology", iconColor: Colors.grey),
new IconWithText(
Icons.question_answer,
"Answered",
iconColor: Colors.grey,
),
new IconWithText(Icons.remove_red_eye, "54", iconColor: Colors.grey)
],
),
),
new Divider( height: 1.0)
],
),
);
var responses = new Container(
padding: const EdgeInsets.all(8.0),
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) => new ForumPost(ForumPostArr[index]),
itemCount: ForumPostArr.length,
)
);
return new Scaffold(
appBar : LBForumAppBar().getAppBar(),
body: new Column(
children: <Widget>[
questionSection,
new Expanded(
child: new Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: responses,
))
],
),
);
}
}
var ForumPostArr = [
new ForumPostEntry("User1", "2 Days ago", 0 , 0 , "Hello,\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
new ForumPostEntry("User2", "23 Hours ago", 1 , 0 , "Pellentesque justo metus, finibus porttitor consequat vitae, tincidunt vitae quam. Vestibulum molestie sem diam. Nullam pretium semper tempus. Maecenas lobortis lacus nunc, id lacinia nunc imperdiet tempor. Mauris mi ipsum, finibus consectetur eleifend a, maximus eget lorem. Praesent a magna nibh. In congue sapien sed velit mattis sodales. Nam tempus pulvinar metus, in gravida elit tincidunt in. Curabitur sed sapien commodo, fringilla tortor eu, accumsan est. Proin tincidunt convallis dolor, a faucibus sapien auctor sodales. Duis vitae dapibus metus. Nulla sit amet porta ipsum, posuere tempor tortor.\n\nCurabitur mauris dolor, cursus et mi id, mattis sagittis velit. Duis eleifend mi et ante aliquam elementum. Ut feugiat diam enim, at placerat elit semper vitae. Phasellus vulputate quis ex eu dictum. Cras sapien magna, faucibus at lacus vel, faucibus viverra lorem. Phasellus quis dui tristique, ultricies velit non, cursus lectus. Suspendisse neque nisl, vestibulum non dui in, vulputate placerat elit. Sed at convallis mauris, eu blandit dolor. Vivamus suscipit iaculis erat eu condimentum. Aliquam erat volutpat. Curabitur posuere commodo arcu vel consectetur."),
new ForumPostEntry("User3", "2 Days ago", 5 , 0 , "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
new ForumPostEntry("User4", "2 Days ago", 0 , 0 , "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
];
Future<List<ForumAnswers>> getForumAnswers(String postID) async {
final response =
await http.post('http://api/ForumAnswers',
headers: {"Content-Type": "application/json",
'Accept': 'application/json',},
body: json.encode({'PostID' : postID }));
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON
List l = json.decode(response.body);
List<ForumAnswers> posts = l.map((m) => ForumAnswers.fromJson(m)).toList();
return posts;
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load user');
}
}
class ForumPostEntry{
final String username;
final String hours;
final int likes;
final int dislikes;
final String text;
ForumPostEntry(this.username, this.hours, this.likes, this.dislikes, this.text);
}
class ForumPost extends StatelessWidget {
final ForumPostEntry entry;
ForumPost(this.entry);
#override
Widget build(BuildContext context) {
return new Container(
margin: const EdgeInsets.only(bottom: 10.0),
decoration: new BoxDecoration(
color: Colors.grey,
borderRadius: const BorderRadius.all(const Radius.circular(20.0)),
),
child: new Column(
children: <Widget>[
new Container(
decoration: new BoxDecoration(
color: Colors.grey[600],
borderRadius: const BorderRadius.only(
topLeft: const Radius.circular(20.0),
topRight: const Radius.circular(20.0)),
),
child: new Row(
children: <Widget>[
new Icon(
Icons.person,
size: 50.0,
color: Colors.white
),
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
entry.username
),
new Text(
entry.hours
),
],
),
),
new Row(
children: <Widget>[
new Padding(
padding: const EdgeInsets.all(2.0),
child: new Icon(Icons.thumb_up, color: Colors.white),
),
new Padding(
padding: const EdgeInsets.all(2.0),
child: new Text(entry.likes.toString()),
),
new Padding(
padding: const EdgeInsets.all(2.0),
child: new Icon(Icons.thumb_down, color: Colors.white),
),
new Padding(
padding: const EdgeInsets.only(right: 8.0, left: 2.0),
child: new Text(entry.dislikes.toString()),
),
],
)
],
),
),
new Container(
margin: const EdgeInsets.only(left: 2.0,right: 2.0,bottom: 2.0),
padding: const EdgeInsets.all(8.0),
decoration: new BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(bottomLeft :const Radius.circular(20.0),bottomRight :const Radius.circular(20.0))
),
child: new Text(entry.text),
),
],
),
);
}
}
class IconWithText extends StatelessWidget {
final IconData iconData;
final String text;
final Color iconColor;
IconWithText(this.iconData, this.text, {this.iconColor});
#override
Widget build(BuildContext context) {
return new Container(
child: new Row(
children: <Widget>[
new Icon(
this.iconData,
color: this.iconColor,
),
new Padding(
padding: const EdgeInsets.only(left: 8.0),
child: new Text(this.text),
),
],
),
);
}
}
Presumably, your json consists of an array at the outer most level; it's enclosed in [..].
l, the result of json.decode will be a List<dynamic>, though the list will happen to contain Maps.
You should find that this works better:
List l = json.decode(response.body);
List<ForumAnswers> posts = l.map((m) => ForumAnswers.fromJson(m)).toList();
Note that if you want to return posts you should change the signature to returning Future<List<ForumAnswers>>.
use these
factory ForumAnswers.fromJson(Map parsedjson) {
return ForumAnswers(
content: parsedjson['content'],
username: parsedjson['username'],
createDate : parsedjson['createDate'],
upvote : parsedjson['upvote'],
);
if not follow these link https://medium.com/flutter-community/parsing-complex-json-in-flutter-747c46655f51
How to achive drop cap letter of a text paragraph in Flutter.
As below:
I managed to hack this together, it looks like it should work but doesn't break at the right spot for some reason. getPositionForOffset seems to give an index that is too large. Might be better than nothing though, and it demonstrates how we can use TextPainter to get the size of a text.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(MaterialApp(title: 'Demo', home: MyApp()));
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) => MaterialApp(
home: Scaffold(
body: Container(
width: 200.0,
decoration: BoxDecoration(border: Border.all()),
child: DropCapText(
dropCap: "L",
dropCapStyle: Theme.of(context).textTheme.display3,
text: "orem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
textStyle: Theme.of(context).textTheme.body1,
),
),
),
);
}
class DropCapText extends StatelessWidget {
final String dropCap;
final TextStyle dropCapStyle;
final String text;
final TextStyle textStyle;
final EdgeInsets dropCapPadding;
DropCapText({this.dropCap, this.dropCapStyle, this.text, this.textStyle, this.dropCapPadding = EdgeInsets.zero});
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
//get the drop cap size
final dropCapSpan = TextSpan(text: dropCap, style: dropCapStyle);
final dropCapPainter = TextPainter(text: dropCapSpan, textDirection: Directionality.of(context));
dropCapPainter.layout(maxWidth: constraints.maxWidth);
final dropCapSize = dropCapPainter.size;
//get the position of the last bit of text next to the dropcap
final textSpan = TextSpan(text: text, style: textStyle);
final textPainter = TextPainter(text: textSpan, textDirection: Directionality.of(context));
textPainter.layout(maxWidth: max(constraints.minWidth, constraints.maxWidth - dropCapPadding.horizontal - dropCapSize.width));
final lastPosition = textPainter.getPositionForOffset(Offset(textPainter.width, dropCapSize.height + dropCapPadding.vertical));
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: dropCapPadding,
child: Text(dropCap, style: dropCapStyle),
),
Expanded(
child: Text(text.substring(0, lastPosition.offset), style: textStyle, softWrap: true,),
),
],
),
Text(text.substring(lastPosition.offset), style: textStyle),
],
);
}
);
}
}
Consider the following code:
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new FlatButton(
child: new Container(
child: new Center(child: new Text("ABOVE")),
height: 300.0,
color: const Color.fromARGB(255, 255, 0, 0),
),
),
new Expanded(
child: new Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",)
),
new FlatButton(
child: new Container(
child: new Center(child: new Text("BELOW")),
height: 300.0,
color: const Color.fromARGB(255, 255, 0, 0),
),
),
],
);
}
}
On my Pixel, this produces:
My expectation was that the text would continue up until it reaches the bottom button, then clip. If I set overflow: TextOverflow.ellipsis then it truncates after the first line:
If I set maxLines: 3 then it continues until the fourth line:
However, I can't find a way to just continue until it reaches the bottom button.
Can anyone enlighten me?
There is no direct styling option that does this, but however you can do it by first calculating maxLines that can occupy the available view in runtime and then just specify overflow and maxLines properties.
To get available height, use a LayoutBuilder the provides the constrains
lineHeight = fontSize * textScaleFactor * lineHeightScaleFactor
maxlines = (available height/line height)
Example:
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Material(
child: new Column(
children: <Widget>[
new FlatButton(
child: new Container(
child: new Center(child: new Text("ABOVE")),
height: 300.0,
color: const Color.fromARGB(255, 255, 0, 0),
),
),
new Expanded(child: new LayoutBuilder(builder: (context, constrains) {
double lineScaleFactor = 1.1; // this is multiplied with fontsize to get lineHeight
TextStyle style = new TextStyle(fontSize: 16.0,height: lineScaleFactor);
double scale = 1.0;
double lineHeight = style.fontSize*scale*lineScaleFactor;
return new Container(
child: new Text(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
overflow: TextOverflow.ellipsis,
style: style,
textScaleFactor: scale,
maxLines: (constrains.maxHeight ~/ lineHeight),
),
);
})),
new FlatButton(
child: new Container(
child: new Center(child: new Text("BELOW")),
height: 300.0,
color: const Color.fromARGB(255, 255, 0, 0),
),
),
],
),
);
}
}
Hope that helped!
I'm trying to style two columns in bootstrap so they have the same height, there is a small gap between them and they have a background color too.
Boxes have the same row height and desired gap but the background doesn't stretch to the whole height
I managed to apply the css for same height, but struggle with applying the color to the background.
Any ideas? Thanks in advance!
.row-eq-height {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
flex-wrap: wrap;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
}
.row-eq-height > [class*='col-'] {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
}
.white-box {
padding: 25px;
-webkit-box-shadow: 0px 10px 23px -10px rgba(0, 0, 0, 0.43);
-moz-box-shadow: 0px 10px 23px -10px rgba(0, 0, 0, 0.43);
-ms-box-shadow: 0px 10px 23px -10px rgba(0, 0, 0, 0.43);
-o-box-shadow: 0px 10px 23px -10px rgba(0, 0, 0, 0.43);
box-shadow: 0px 10px 23px -10px rgba(0, 0, 0, 0.43);
background-color: red;
margin-bottom: 25px;
}
<div class="container">
<div class="row row-eq-height">
<div class="col-md-6 text-justify">
<div class="white-box">
<h5>SOME TITLE</h5>
<p>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt.
</p>
</div>
</div>
<div class="col-md-6 text-justify">
<div class="white-box">
<h5>SOME TITLE</h5>
<p>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt.
</p>
<p>Continued >></p>
</div>
</div>
</div>
</div>