Flutter: Alternative text when truncated - flutter

In some Text Widgets we display text which is getting truncated on some devices and we want to replace the text in this cases instead of truncating it.
I.E. we display a name like "Kamala Devi Harris"
When the name is too long for displaying we want to call a method and transform it to "Kamala D. Harris" and if that is also to long then "K. D. Harris" ... "K. Harris" or even "K. H."
How can we recognize that the current text is getting truncated?

I don't know if it's the most suitable way to achieve what you're trying to do but here's how we can do it :
Make a custom Widget which will take a list of String from greater value to lower so that it can check which value fit's in the available space.
class OverflowAwareText extends StatefulWidget {
final List<String> alternativeTexts;
OverflowAwareText({this.alternativeTexts});
#override
_OverflowAwareText createState() => _OverflowAwareText();
}
class _OverflowAwareText extends State<OverflowAwareText> {
List<String> texts;
int activeTextIndex = 0;
#override
void initState() {
super.initState();
texts = widget.alternativeTexts;
}
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, size) {
var textSpan = TextSpan(
text: texts[activeTextIndex],
style: TextStyle(fontSize: 42),
);
// Use a textpainter to determine if it will exceed max lines
var textPainter = TextPainter(
maxLines: 1,
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,
text: textSpan,
)..layout(maxWidth: size.maxWidth);
if (textPainter.didExceedMaxLines) {
if (activeTextIndex != texts.length - 1) {
WidgetsBinding.instance.addPostFrameCallback((t) {
setState(() {
activeTextIndex += 1;
});
});
}
}
return Text.rich(
textSpan,
overflow: TextOverflow.ellipsis,
maxLines: 1,
);
});
}
}
Our OverflowAwareText takes a parameter alternativeTexts which is List<String>. You have to provide a list of Strings with larger to smaller values.
We can now use the OverflowAwareText :
#override
Widget build(BuildContext context) {
print('Printing from build method');
return Scaffold(
body: Center(
child: Container(
alignment: Alignment.center,
height: 50,
color: Colors.amber,
child: OverflowAwareText(
alternativeTexts: [
'Kamala Devi Harris',
'K. D. Harris',
'K. Harris',
'K. H'
],
),
),
),
);
}
We've wrapped the OverflowAwareText in a Container, so that we can check if the text is being changed or not when there is less space.
RESULT :
With no width in Container
With width = 330 in Container
With width = 180 in Container
With width = 100 in Container

Related

Show counter to number of elements hidden when overflow occurs in flutter row widget

Can anyone please help to implement this feature of Gmail that shows the counter to number of emails hidden when the email list becomes large ? I want to implement this in row widget where instead of being scrollable extra elements count is shown when overflow occurs.Gmail shows +15 counter for hidden emails
I was Curious to give a try to achieve the same effect, as asked.
Just in case, If anyone want a start for writing a custom one, then below code may help.
Here is my Code, Feel free to give any suggestions,
(For Now delete button in chips is not working bcoz of some logic problem, I will make it work another day)
import 'package:flutter/material.dart';
class Demo3 extends StatefulWidget {
#override
_Demo3State createState() => _Demo3State();
}
class _Demo3State extends State<Demo3> {
String temp = "";
bool showChips = false;
List<Widget> chipsList = new List();
TextEditingController textEditingController = new TextEditingController();
final _focusNode = FocusNode();
int countChipsToDeleteLater = 0;
#override
void initState() {
super.initState();
_focusNode.addListener(() {
print("Has focus: ${_focusNode.hasFocus}");
if (!_focusNode.hasFocus) {
showChips = false;
setState(() {});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
},
child: new Container(
height: 500,
child: new Center(
child: Container(
width: 300,
child: !showChips
? Row(
children: [
buildTextField(),
showNumberWidgetIfAny(),
],
)
: Center(
child: Wrap(
children: [
Wrap(
children: buildChips(),
),
buildTextField(),
],
),
),
),
),
),
),
);
}
buildChips() {
return chipsList;
}
buildTextField() {
return Container(
width: 200,
child: new TextField(
showCursor: true,
focusNode: _focusNode,
autofocus: true,
cursorColor: Colors.black,
style: new TextStyle(fontSize: 22.0, color: Colors.black),
controller: textEditingController,
// decoration: InputDecoration.collapsed(
// hintText: "",
// ),
onChanged: (value) {
if (value.contains(" ")) {
checkWhatToStoreInChips(value, countChipsToDeleteLater);
textEditingController.clear();
setState(() {
showChips = true;
});
countChipsToDeleteLater++;
}
},
),
);
}
checkWhatToStoreInChips(String val, int chipsIndex) {
temp = "";
for (int i = 0; i < val.length; i++) {
if (val[i] == " ") {
break;
}
temp = temp + val[i];
}
addToChips(temp, chipsIndex);
}
addToChips(String tmp, int chipsIndex) {
chipsList.add(Chip(
// onDeleted: () {
// if (chipsList.length == 0) {
// countChipsToDeleteLater = 0;
// }
// chipsList.removeAt(chipsIndex);
// print(chipsList.length);
// print(chipsIndex);
// setState(() {});
// },
avatar: CircleAvatar(
backgroundColor: Colors.grey.shade800,
child: Text(tmp[0]),
),
label: Text(temp),
));
}
showNumberWidgetIfAny() {
int len = chipsList.length;
if (len >= 1) {
return GestureDetector(
onTap: () {
showChips = true;
setState(() {});
},
child: new Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.blue,
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: new Text(
"${chipsList.length.toString()} ",
style: new TextStyle(color: Colors.white, fontSize: 22),
),
),
),
);
}
return Container();
}
}
How it works:
Write something in text field, then press space, showChips boolean will become true
onChanged will detect the space and will send the string to a function.
That function will extract the string before space and then will add the string to a chip,
Finally the chip will be added to a chipslist.
We will have a boolean variable to check if the textfield is in focus and when to show the textfield and numberwidget (a widget which will keep count of the total chips, same like you asked in your question) or when to show the chipslist and textfield wraped in a wrap widget.
You can play around by changing the decoration of textfield to collapsed, to it look like the same as gmail.
Check this package, if you want to use custom package for ease.
I was facing a similar issue. I found a way to implement the Overflow count text.
Sample image
You basically have to paint the overflow text, and get its width like below
final TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: style),
textDirection: TextDirection.ltr,
textScaleFactor: WidgetsBinding.instance.window.textScaleFactor,
)..layout();
var textSize = textPainter.size;
textSize.width;
Then subtract that from the width available. Lets call it x.
Then create a sum of width for each row item(using TextPainter.layout() method mentioned above), till its value is less than x.
This way you'll know how many items can be shown in the row.
I have created a Flutter library to help with this.

How can I know the string length of each line in Text?

I want to create a text widget which has a 'see more' function. I want to get the string length in third line so I can do the substring.
The following code is what I have so far:
class ExpandedTextState extends State<ExpandedTextWidget> {
String firstHalf;
String secondHalf;
bool flag = true;
#override
void initState() {
super.initState();
TextPainter textPainter = new TextPainter();
textPainter.maxLines = 3;
textPainter.text = TextSpan(text: widget.text);
textPainter.textDirection = TextDirection.ltr;
textPainter.layout(maxWidth: double.infinity , minWidth: 0.0);
if(textPainter.didExceedMaxLines){
firstHalf = widget.text.substring(0, 50); //substring here
secondHalf = widget.text.substring(50, widget.text.length);
}else{
firstHalf = widget.text;
secondHalf = "";
}
}
#override
Widget build(BuildContext context) {
return new Container(
padding: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
child: secondHalf.isEmpty
? new Text(firstHalf)
: Container(
child: new RichText(
text: TextSpan(children: [
TextSpan(
text: flag ? (firstHalf + "...") : (firstHalf + secondHalf),
style: TextStyle(fontSize: 13, color: Colors.amber),),
TextSpan(
text: flag ? "see more" : "",
style: TextStyle(fontSize: 13, color: Colors.black),
recognizer: new TapGestureRecognizer()
..onTap = () {
setState(() {
flag = !flag;
});
}),
]),
),
)
);
}
}
How can I know the string length in each line, or how can I know the last offset of the third line?
If you want a component that expands the text and starts wih only three lines i would suggest to use a simple approach , if you want to know the offset you would need to know the font dimensions, the screen dimensions, the font weight, the device orientation.
Flutter itself can't give you that, it would be kinda hard to implement..
But, if you want a text of 3 lines that expands when you click "see more" and if it's expanded "see more" dissapears, you can use this approach
class ExpandableText extends StatefulWidget {
#override
_ExpandableTextState createState() => _ExpandableTextState();
}
class _ExpandableTextState extends State<ExpandableText> {
final String text =
'ppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp';
bool isExpanded = false;
#override
Widget build(BuildContext context) {
return isExpanded
? Text(text + text + text,
style: TextStyle(fontSize: 13, color: Colors.amber))
: Column(children: <Widget>[
Text(text + text + text,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 13, color: Colors.amber)),
GestureDetector(
onTap: () => setState(() => isExpanded = true),
child: Text('See More...',
style: TextStyle(fontSize: 13, color: Colors.black)))
]);
}
}
in that component we use a maxlines property to tell if the text is expanded or not, if it is , it's null , if it's not, it's 3

unable to style text size in flutter widget

I am trying to style the field where i type in the address, I want to make my font size smaller. I have tried changing the fontsize through the various "textstyle" properties in "Inputdecoration" but had no luck. How do i achieve it?
There are also these yellow lines underscoring my text. Is it an error?
Please help me out :/ would really appreciate it~
return MaterialApp(
home: Scaffold(
body: Stack(
children: <Widget>[
GoogleMap(
onMapCreated: (GoogleMapController controller) {
mapController = controller;
},
initialCameraPosition: currentPosition,
markers: marks,
),
SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: PlacesAutocompleteField(
apiKey: 'AIzaSyDVxxxxxxxxxxxxxx',
hint: 'Search Places',
style: TextStyle(fontSize: 50.0),
inputDecoration: InputDecoration(
icon: Icon(Icons.search),
hintStyle: TextStyle(
fontSize: 50.0,
fontWeight: FontWeight.bold,
),
),
onChanged: (value) async {
placeName = value;
print(placeName);
List<Placemark> placemark =
await Geolocator().placemarkFromAddress(placeName);
print(placemark[0].position);
Set<Marker> markers =
await getMarkers(placemark[0].position);
updateUI(placemark[0].position, markers);
mapController.animateCamera(
CameraUpdate.newCameraPosition(currentPosition));
},
),
),
),
],
),
),
);
code for the PlacesAutocompleteField:
class PlacesAutocompleteField extends StatefulWidget {
/// Creates a text field like widget.
///
/// To remove the decoration entirely (including the extra padding introduced
/// by the decoration to save space for the labels), set the [decoration] to
/// null.
const PlacesAutocompleteField({
Key key,
#required this.apiKey,
this.style,
this.controller,
this.leading,
this.hint = "Search",
this.trailing,
this.trailingOnTap,
this.mode = Mode.fullscreen,
this.offset,
this.location,
this.radius,
this.language,
this.sessionToken,
this.types,
this.components,
this.strictbounds,
this.onChanged,
this.onError,
this.inputDecoration = const InputDecoration(),
}) : super(key: key);
/// Controls the text being edited.
///
/// If null, this widget will create its own [TextEditingController].
final TextEditingController controller;
/// Icon shown inside the field left to the text.
final Icon leading;
/// Icon shown inside the field right to the text.
final Icon trailing;
/// Callback when [trailing] is tapped on.
final VoidCallback trailingOnTap;
/// Text that is shown, when no input was done, yet.
final String hint;
final TextStyle style;
/// Your Google Maps Places API Key.
///
/// For this key the Places Web API needs to be activated. For further
/// information on how to do this, see their official documentation below.
///
/// See also:
///
/// * <https://developers.google.com/places/web-service/autocomplete>
final String apiKey;
/// The decoration to show around the text field.
///
/// By default, draws a horizontal line under the autocomplete field but can be
/// configured to show an icon, label, hint text, and error text.
///
/// Specify null to remove the decoration entirely (including the
/// extra padding introduced by the decoration to save space for the labels).
final InputDecoration inputDecoration;
/// The position, in the input term, of the last character that the service
/// uses to match predictions.
///
/// For example, if the input is 'Google' and the
/// offset is 3, the service will match on 'Goo'. The string determined by the
/// offset is matched against the first word in the input term only. For
/// example, if the input term is 'Google abc' and the offset is 3, the service
/// will attempt to match against 'Goo abc'. If no offset is supplied, the
/// service will use the whole term. The offset should generally be set to the
/// position of the text caret.
///
/// Source: https://developers.google.com/places/web-service/autocomplete
final num offset;
final Mode mode;
final String language;
final String sessionToken;
final List<String> types;
final List<Component> components;
final Location location;
final num radius;
final bool strictbounds;
/// Called when the text being edited changes.
final ValueChanged<String> onChanged;
/// Callback when autocomplete has error.
final ValueChanged<PlacesAutocompleteResponse> onError;
#override
_LocationAutocompleteFieldState createState() =>
_LocationAutocompleteFieldState();
}
class _LocationAutocompleteFieldState extends State<PlacesAutocompleteField> {
TextEditingController _controller;
TextEditingController get _effectiveController =>
widget.controller ?? _controller;
#override
void initState() {
super.initState();
if (widget.controller == null) _controller = TextEditingController();
}
#override
void didUpdateWidget(PlacesAutocompleteField oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.controller == null && oldWidget.controller != null)
_controller = TextEditingController.fromValue(oldWidget.controller.value);
else if (widget.controller != null && oldWidget.controller == null)
_controller = null;
}
Future<Prediction> _showAutocomplete() async => PlacesAutocomplete.show(
context: context,
apiKey: widget.apiKey,
offset: widget.offset,
onError: widget.onError,
mode: widget.mode,
hint: widget.hint,
language: widget.language,
sessionToken: widget.sessionToken,
components: widget.components,
location: widget.location,
radius: widget.radius,
types: widget.types,
strictbounds: widget.strictbounds,
);
void _handleTap() async {
Prediction p = await _showAutocomplete();
if (p == null) return;
setState(() {
_effectiveController.text = p.description;
if (widget.onChanged != null) {
widget.onChanged(p.description);
}
});
}
#override
Widget build(BuildContext context) {
final TextEditingController controller = _effectiveController;
var text = controller.text.isNotEmpty
? Text(
controller.text,
softWrap: true,
)
: Text(
widget.hint ?? '',
style: TextStyle(color: Colors.black38),
);
Widget child = Row(
children: <Widget>[
widget.leading ?? SizedBox(),
SizedBox(
width: 16.0,
),
Expanded(
child: text,
),
widget.trailing != null
? GestureDetector(
onTap: widget.trailingOnTap,
child: widget.trailingOnTap != null
? widget.trailing
: Icon(
widget.trailing.icon,
color: Colors.grey,
),
)
: SizedBox()
],
);
if (widget.inputDecoration != null) {
child = InputDecorator(
decoration: widget.inputDecoration,
child: child,
);
}
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: _handleTap,
child: child,
);
}
}
The reason being you are not providing any widget to your Text which has got material design, there are many ways to fix it, like you can provide Material or a basic Scaffold. The recommended way would be to use Scaffold as it can provide many basic functionalities too.
So you need to wrap your Stack in Scaffold
Scaffold(
body: Stack(...)
)
Update (for text size) :
In the build() method of PlacesAutocompleteField class, replace yours with following
var text = controller.text.isNotEmpty
? Text(
controller.text,
softWrap: true,
style: TextStyle(fontSize: 50),
)
: Text(
widget.hint ?? '',
style: TextStyle(color: Colors.black38, fontSize: 50),
);

Flutter TextField - how to shrink the font if the text entered overflows

I have a TextField (not a Text) widget that must remain on one line. I want to reduce it's font size if the text entered is too large for the TextField box, ie shrink it if it overflows. How can I do this?
I have written some code like this in a stateful component
if (textLength < 32) {
newAutoTextVM.fontSize = 35.0;
} else if (textLength < 42) {
newAutoTextVM.fontSize = 25.0;
In the view
fontSize: 25.0,
but it isn't very intelligent, it doesn't cope with resizing, also, because the font size isn't monospaced (courier etc), different characters take up different amounts of space.
Use a TextPainter to calculate the width of your text. Use a GlobalKey to get the size of your widget (A LayoutBuilder might be better to handle screen rotation).
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: Home()));
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
const textFieldPadding = EdgeInsets.all(8.0);
const textFieldTextStyle = TextStyle(fontSize: 30.0);
class _HomeState extends State<Home> {
final TextEditingController _controller = TextEditingController();
final GlobalKey _textFieldKey = GlobalKey();
double _textWidth = 0.0;
double _fontSize = textFieldTextStyle.fontSize;
#override
void initState() {
super.initState();
_controller.addListener(_onTextChanged);
}
void _onTextChanged() {
// substract text field padding to get available space
final inputWidth = _textFieldKey.currentContext.size.width - textFieldPadding.horizontal;
// calculate width of text using text painter
final textPainter = TextPainter(
textDirection: TextDirection.ltr,
text: TextSpan(
text: _controller.text,
style: textFieldTextStyle,
),
);
textPainter.layout();
var textWidth = textPainter.width;
var fontSize = textFieldTextStyle.fontSize;
// not really efficient and doesn't find the perfect size, but you got all you need!
while (textWidth > inputWidth && fontSize > 1.0) {
fontSize -= 0.5;
textPainter.text = TextSpan(
text: _controller.text,
style: textFieldTextStyle.copyWith(fontSize: fontSize),
);
textPainter.layout();
textWidth = textPainter.width;
}
setState(() {
_textWidth = textPainter.width;
_fontSize = fontSize;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Autosize TextField'),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextField(
key: _textFieldKey,
controller: _controller,
decoration: InputDecoration(
border: InputBorder.none,
fillColor: Colors.orange,
filled: true,
contentPadding: textFieldPadding,
),
style: textFieldTextStyle.copyWith(fontSize: _fontSize),
),
Text('Text width:'),
Container(
padding: textFieldPadding,
color: Colors.orange,
child: Row(
children: <Widget>[
Container(width: _textWidth, height: 20.0, color: Colors.blue),
],
),
)
],
),
),
);
}
}
I have searched through the docs and found a couple of solutions that could come at your help:
L̶o̶o̶k̶ ̶a̶t̶ ̶t̶h̶e̶ ̶o̶f̶f̶i̶c̶i̶a̶l̶ ̶d̶o̶c̶s̶[̶1̶]̶,̶ ̶i̶n̶ ̶p̶a̶r̶t̶i̶c̶u̶l̶a̶r̶e̶ ̶a̶t̶ ̶t̶h̶e̶s̶e̶ ̶p̶r̶o̶p̶e̶r̶t̶i̶e̶s̶:̶ ̶ ̶m̶a̶x̶L̶i̶n̶e̶s̶,̶ ̶o̶v̶e̶r̶f̶l̶o̶w̶ ̶a̶n̶d̶ ̶s̶o̶f̶t̶W̶r̶a̶p̶ (These are TextBox properties, not TextFields)
Have a look at this thread where they suggest to wrap the TextBox/TextFeld with a Flexible Widget
Depending on the rest of your code one of these solutions could be better, try tweaking around.
Hope it helps.

Widgets sliding from outside the screen in Flutter ? Similar to Android 8 app drawer

I am writing a flashcard app (an extension to the open source AnkiDroid app) in Flutter. The basic workflow is: the app shows me a question and I can reveal the answer. The gesture I want in order to reveal the answer is similar to the Android 8 swipe up from the bottom icon row to reveal the app drawer. A fast swipe (or fling in the android terminology?) can reveal the app list, but a drawn out, slow swipe can control the motion of the apps drawer.
My questions are the following:
What is the proper way to have widgets slide in from outside the screen ? Flutter complains that I'm trying to display widgets outside the screen, suggests I use ClipRect, but I haven't found a way for ClipRect to only display something the size of the screen (it seems to adjust itself to the size of the child)
What is the recommended layout for what I want to do ? Currently I have the question and answer in a Column, and in order to center the question initially and hide the question, I modify the padding. It feels like a bit of a hack.
Is there a helper library that can help me achieve the exact swipe/fling motion that I'm after? It needs to take into account momentum and position in order for the motion to feel just as natural as the android 8 app drawer.
Thank you for any suggestions you may have.
Here are the screens I have so far:
Question screen
Answer screen (after swiping up)
And here's the code:
import 'package:flutter/material.dart';
import 'dart:math';
// Uncomment lines 7 and 10 to view the visual layout at runtime.
//import 'package:flutter/rendering.dart' show debugPaintSizeEnabled;
void main() {
//debugPaintSizeEnabled = true;
runApp(MyApp());
}
/*
* travel around the world
* 環遊世界
* wàan jàu sâi gâai
*/
class Card extends StatefulWidget {
#override
createState() => CardState();
}
class CardState extends State<Card> with SingleTickerProviderStateMixin {
var _dragStartOffset;
Animation<double> questionAnimation;
Animation<double> answerAnimation;
Animation<double> opacityAnimation;
AnimationController controller;
initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 250), vsync: this);
questionAnimation = Tween(begin: 250.0, end: 150.0).animate(controller)
..addListener(() {
setState(() {
// the state that has changed here is the animation object’s value
});
});
answerAnimation = Tween(begin: 200.0, end: 32.0).animate(controller)
..addListener(() {
setState(() {
// the state that has changed here is the animation object’s value
});
});
opacityAnimation = Tween(begin: 0.0, end: 1.0).animate(controller)
..addListener(() {
setState(() {
// the state that has changed here is the animation object’s value
});
});
}
#override
Widget build(BuildContext context) {
Widget question = Container(
padding: EdgeInsets.only(top: questionAnimation.value),
child: Center (
child: Text(
"travel around the world",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 48.0,
),
textAlign: TextAlign.center,
)
),
);
Widget answer = Container(
padding: EdgeInsets.only(top: answerAnimation.value),
child: Opacity(
opacity: opacityAnimation.value,
child: Text(
"wàan jàu sâi gâai 環遊世界",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 48.0,
),
textAlign: TextAlign.center,
)
)
);
var children = [question, answer];
var child = GestureDetector(
onTap: () {
controller.reset();
},
onVerticalDragUpdate: (data) {
// print(data);
var currentOffset = data.globalPosition;
var travel = _dragStartOffset - currentOffset;
// print(travel);
if(travel.dy <0 )
{
return;
}
// cannot be lower than zero
var travelY = max<double>(0.0, travel.dy);
// cannot be higher than 100
travelY = min<double>(200.0, travelY);
var animationPosition = travelY / 200.0;
controller.value = animationPosition;
},
onVerticalDragEnd: (data) {
if(controller.value > 0.50) {
// make the animation continue on its own
controller.forward();
} else {
// go back the other way
controller.reverse();
}
},
onVerticalDragStart: (data) {
//print(data);
_dragStartOffset = data.globalPosition;
},
child: Scaffold(
appBar: AppBar(
title: Text('AnkiReview'),
),
body: Container(
child:Column(
children: children,
)
),
)
);
return child;
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Card(),
);
}
}
I figured out one solution. It involves a Column, the top is just a Container with the question, but the bottom is a PageView which has a blank first page. The user can slide up to reveal the answer.
It solves the clipping issue, and also the physics issue, because PageView has built-in physics and snapping, which would otherwise not be trivial to build (I would probably have to use a CustomScrollView).
code:
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'dart:math';
// Uncomment lines 7 and 10 to view the visual layout at runtime.
//import 'package:flutter/rendering.dart' show debugPaintSizeEnabled;
void main() {
//debugPaintSizeEnabled = true;
runApp(MyApp());
}
/*
* travel around the world
* 環遊世界
* wàan jàu sâi gâai
*/
class Card extends StatefulWidget {
#override
createState() => CardState();
}
class CardState extends State<Card> with SingleTickerProviderStateMixin {
var _dragStartOffset;
var _fontSize = 48.0;
static const _padding = 28.0;
initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
var questionText = Text(
"travel around the world",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: _fontSize,
),
textAlign: TextAlign.center,
);
var answerText = Text(
"wàan jàu sâi gâai 環遊世界",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: _fontSize,
),
textAlign: TextAlign.center
);
Widget question = Container(
padding: EdgeInsets.only(bottom: _padding),
alignment: Alignment.bottomCenter,
child: questionText
);
Widget answer = Container(
padding: EdgeInsets.only(top: _padding),
alignment: Alignment.topCenter,
child: answerText
);
var card = Column(
children: [
Expanded(
child: question,
),
Expanded(
child: PageView(
scrollDirection: Axis.vertical,
children: [
Container(),
answer
]
)
)
]
);
return card;
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('AnkiReview'),
),
body: Container(
child:Card()
),
),
);
}
}