I have a Card widget with TextFormField as a child. I want to increase the height of the card each time a user gets to a new line. The card is wrapped in a Container and I have a height variable to determine the height.
How can I detect new line in TextFormField? Also whenever the text wraps to the next line.
I think you need to create a function to check the TextFormField specific line.
I think this an be useful for you "https://stackoverflow.com/questions/45900387/multi-line-textfield-in-flutter"
To update height without pressing enter:
onChanged: (String e) {
int sizeIncreaseConstant = 30; //the fontSize 20 + buffer
int widthOfCharacter = 17; // 85% of fontsize
int newNumLines = ((e.length * widthOfCharacter)/widthOfContainer).truncate();
if( newNumLines != numLines) {
setState(() {
if(newNumLines > numLines)
heightOfContainer = heightOfContainer + sizeIncreaseConstant;
else
heightOfContainer = heightOfContainer - sizeIncreaseConstant;
numLines = newNumLines;
});
}
},
initial values:
int numLines = 0;
double widthOfContainer = 120;
double heightOfContainer = 50;
//fontSize = 20;
For this, you will have to use a font that has equal width for all characters like Monospace. And you will also need to determine the width of the character based on fontSize. It is supposed to be 50-60% of the font size but 85% worked for me.
Related
I want to set the minimum fixed width for an EditText so that it can contain its hint but also the typed, length-limited content like a number of 2 digits.
Some details:
I want to be able to do this dynamically since I have numerous
fields for different purposes with different hints (in different languages) and input length (some 2 digits, others 4).
Hints are not necessarily longer than the input itself. A
hint could be "dd" or "Day" and the input could be a to digit
number.
I do not need room for hint and content at the same time;
hints disappear when the user starts typing.
I'm using custom fonts in an extended EditText class, but that should be handled as I'm copying the EditText's Paint.
I have a utility method for doing so, but it returns a width that is too narrow so the hint is clipped. What am I doing wrong?
The EditText is specified in XML like this:
<EditText
android:id="#+id/birthday_month"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="number"
android:hint="#string/birthday_month_hint"
android:lines="1"
android:maxLength="2">
In my Activity I first find the EditText and then prepare it using Texts.setNiceAndTightWidth(monthEditText, 2) defined below (including helper methods):
public class Texts
{
public static void setNiceAndTightWidth ( EditText editText, int maxInputLength )
{
// String of chars to test for widest char. Include all possible input chars and chars of hint, as we need to make room for hint as well.
String testChars = String.format("1234568790%s", editText.getHint().toString());
char widestChar = getWidestChar(editText, testChars);
String widestString = repeat(widestChar, maxInputLength);
float widestStringWidth = getTextWidth(editText, widestString);
int width = (int)(widestStringWidth + 0.5f);
editText.setWidth(width);
// This was an experiment but it doesn't help.
ViewGroup.LayoutParams lp = editText.getLayoutParams();
lp.width = width;
editText.setLayoutParams(lp);
}
public static char getWidestChar ( TextView textView, String testChars )
{
float width, widest = 0;
char widestChar = '\0';
// Using Paint properties of TextView, including Fontface, text size, etc.
Paint paint = new Paint( textView.getPaint() );
for ( int i = 0 ; i < testChars.length() ; i++ ) {
width = paint.measureText(testChars, i, i+1);
if ( width > widest ) {
widest = width;
widestChar = testChars.charAt(i);
}
}
return widestChar;
}
public static String repeat ( char ch, int length )
{
char[] chars = new char[length];
Arrays.fill(chars, ch);
String string = String.valueOf(chars);
return string;
}
public static float getTextWidth ( TextView textView, CharSequence text )
{
Paint paint = new Paint( textView.getPaint() );
float width = paint.measureText(text, 0, text.length());
return width;
}
}
I have a TextView with an OnTouchListener. What I want is the character index the user is pointing to when I get the MotionEvent. Is there any way to get to the underlying font metrics of the TextView?
Have you tried something like this:
Layout layout = this.getLayout();
if (layout != null)
{
int line = layout.getLineForVertical(y);
int offset = layout.getOffsetForHorizontal(line, x);
// At this point, "offset" should be what you want - the character index
}
Hope this helps...
I am not aware of a simple direct way to do this but you should be able to put something together using the Paint object of the TextView via a call to TextView.getPaint()
Once you have the paint object you will have access to the underlying FontMetrices via a call to Paint.getFontMetrics() and have access to other functions like Paint.measureText() Paint.getTextBounds(), and Paint.getTextWidths() for accessing the actual size of the displayed text.
While it generally works I had a few problems with the answer from Tony Blues.
Firstly getOffsetForHorizontal returns an offset even if the x coordinate is way beyond the last character of the line.
Secondly the returned character offset sometimes belongs to the next character, not the character directly underneath the pointer. Apparently the method returns the offset of the nearest cursor position. This may be to the left or to the right of the character depending on what's closer by.
My solution uses getPrimaryHorizontal instead to determine the cursor position of a certain offset and uses binary search to find the offset underneath the pointer's x coordinate.
public static int getCharacterOffset(TextView textView, int x, int y) {
x += textView.getScrollX() - textView.getTotalPaddingLeft();
y += textView.getScrollY() - textView.getTotalPaddingTop();
final Layout layout = textView.getLayout();
final int lineCount = layout.getLineCount();
if (lineCount == 0 || y < layout.getLineTop(0) || y >= layout.getLineBottom(lineCount - 1))
return -1;
final int line = layout.getLineForVertical(y);
if (x < layout.getLineLeft(line) || x >= layout.getLineRight(line))
return -1;
int start = layout.getLineStart(line);
int end = layout.getLineEnd(line);
while (end > start + 1) {
int middle = start + (end - start) / 2;
if (x >= layout.getPrimaryHorizontal(middle)) {
start = middle;
}
else {
end = middle;
}
}
return start;
}
Edit: This updated version works better with unnatural line breaks, when a long word does not fit in a line and gets split somewhere in the middle.
Caveats: In hyphenated texts, clicking on the hyphen at the end of a line return the index of the character next to it. Also this method does not work well with RTL texts.
I have a multiline TextView which can display some optional URL. Now I have a problem: some of my long URLs displayed wrapped in the position of ://
sometext sometext http:// <-- AUTO LINE WRAP
google.com/
How can I disable wrapping for the whole URL or at least for http(s):// prefix? I still need text wrapping to be enabled however.
My text should wrap like that
sometext sometext <-- AUTO LINE WRAP
http://google.com/
This is just proof of concept to implement custom wrap for textview.
You may need to add/edit conditions according to your requirement.
If your requirement is that our textview class must show multiline in such a way that it should not end with certain text ever (here http:// and http:),
I have modified the code of very popular textview class over SO to meet this requirement:
Source :
Auto Scale TextView Text to Fit within Bounds
Changes:
private boolean mCustomLineWrap = true;
/**
* Resize the text size with specified width and height
* #param width
* #param height
*/
public void resizeText(int width, int height) {
CharSequence text = getText();
// Do not resize if the view does not have dimensions or there is no text
if(text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
return;
}
// Get the text view's paint object
TextPaint textPaint = getPaint();
// Store the current text size
float oldTextSize = textPaint.getTextSize();
// If there is a max text size set, use the lesser of that and the default text size
float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;
// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while(textHeight > height && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
}
if(mCustomLineWrap ) {
// Draw using a static layout
StaticLayout layout = new StaticLayout(text, textPaint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
// Check that we have a least one line of rendered text
if(layout.getLineCount() > 0) {
String lineText[] = new String[layout.getLineCount()];
// Since the line at the specific vertical position would be cut off,
// we must trim up to the previous line
String wrapStr = "http:", wrapStrWithSlash = "http://";
boolean preAppendWrapStr = false, preAppendWrapStrWithSlash = false ;
for(int lastLine = 0; lastLine < layout.getLineCount(); lastLine++)
{
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
lineText[lastLine] = ((String) getText()).substring(start,end);
if(preAppendWrapStr)
{
lineText[lastLine] = "\n" + wrapStr + lineText[lastLine];
preAppendWrapStr = false;
}
else if(preAppendWrapStrWithSlash)
{
lineText[lastLine] = "\n" + wrapStrWithSlash + lineText[lastLine];
preAppendWrapStrWithSlash = false;
}
if(lineText[lastLine].endsWith(wrapStr))
{
preAppendWrapStr = true;
lineText[lastLine] = lineText[lastLine].substring(0,lineText[lastLine].lastIndexOf(wrapStr));
}
if( lineText[lastLine].endsWith(wrapStrWithSlash))
{
preAppendWrapStrWithSlash = true;
lineText[lastLine] = lineText[lastLine].substring(0,lineText[lastLine].lastIndexOf(wrapStrWithSlash));
}
}
String compString = "";
for(String lineStr : lineText)
{
compString += lineStr;
}
setText(compString);
}
}
// Some devices try to auto adjust line spacing, so force default line spacing
// and invalidate the layout as a side effect
textPaint.setTextSize(targetTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registered
if(mTextResizeListener != null) {
mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
}
// Reset force resize flag
mNeedsResize = false;
}
I have an EditText in Android. It has long content and it scrolls into pages
How can I find the real height of content, not just the visible area?
I tried this, but it just gives height of visible area:
EditText editor=(EditText)findViewById(R.id.ed1);
.
.
.
double H=editor.getHeight();
not H is 1150, not 10000 or something like that which is real height of content.
This is what I do:
EditText tv = ...;
if (Build.VERSION.SDK_INT >= 16) {
totalHeight = Math.round((tv.getLineCount() * (tv.getLineHeight() + tv.getLineSpacingExtra()) *
tv.getLineSpacingMultiplier())) + tv.getCompoundPaddingTop() + tv.getCompoundPaddingBottom();
} else {
totalHeight = tv.getLineCount() * tv.getLineHeight() + tv.getCompoundPaddingTop() + tv.getCompoundPaddingBottom();
}
You can do this in following steps
count the lines in the edit text get help from here
after that calculate the height(includeing top padding) of single line then simply multiply that number to get the real height of the text inside the editText
It was enough for me to call EditText.getLayout().getHeight();
I've just debugged an app where I need this functionality too:
int contentHeight = view.getTextArea().getLayout().getHeight();
int visibleAreaHeight = view.getTextArea().getHeight();
The values are:
contentHeight = 7531
visibleAreaHeight = 1408
Just trying to get clarification on this issue. Is the scaling of fonts working for android using this attribute? Its working for iOS, I know, but it doesn't seem to want to play nice for android.
I'm trying to make a label that has a string of varying size in it. Here is a sample of the code that I'm using:
var name = Titanium.UI.createLabel({ text:response.name, color:'#000', minimumFontSize:10, font:{fontSize:24,fontFamily:'Helvetica Neue'}, width:120, height:45, top:0, left:0, right:2, wordWrap:false, textAlign:'left' });
Use 'sp' as measurement unit for font and 'dp' for sizes (example: fontSize: '24sp'). You can read more about density-specific resources on Android here.
If you are asking about whether 'minimumFontSize' on a label works on Android to automatically scale the size of the text to fit the label on a single line, according to the latest documentation for Titanium 3.X here:
http://docs.appcelerator.com/titanium/latest/#!/api/Titanium.UI.Label-property-minimumFontSize
no, it only works on iPhone and iPad
This might be a bit off topic, but this is the code we use to make sure some text fits a label. You call the function in a postLayout event handler or at some other moment when the dimensions of the label and the screen are known.
function fitTextInLabel(label,options)
{
/*
* Make the given text fit in the label by, well, just trying. Run this when the layout is complete
* IE in the onPostlayout of the view or the label. When using the cache-feature don't forget to
* check for orientation in creating the cache key: otherwise the font size will not be recalculated for the other orientation
* This is an alloy function: it requires underscore.js But rewriting it for plain titanium is not a big deal.
* Spin in het Web - www.spininhetweb.nl - Contact us for questions. Yes we build apps.
*
* Label: the Ti.UI.Label to fit the text in
* Options: an options object:
* text: the text to fit. When not given we will use the current text of the label. Use a lorum ipsum that's big enough.
* fitWidth: which width to fit the text in. Either the fixed width of the label ("current") or that of the parent ("parent"). When
* width is Ti.UI.SIZE use "parent". Default: current.
* fitHeight: which height to fit the text in. "current" or "parent". Default: current
* marginVertical: space to keep vertically. Will use marginVertical / 2 for top and bottom. Default: 0
* marginHorizontal: space to keep horizontally. Will use marginHorizontal / 2 for left and right. Default: 0
* cacheKey: string. When given, use caching. We will save the found fontsize as a persistant property. When called again with the same key
* we will not calculute, but just set the fontsize. The cache is only cleared when the user removes the app or its data
* We add the device orientation to the cacheKey, so we automatically differentiate between setting for portrait and landscape
* applyTo: array of labels. When given, we will set the same fontsize on the given labels.
* callback: function. When given, we will call this after setting the fontsize on the label. The prototype for the callback function is:
* fn(Ti.UI.Label lbl, int newFontSize)
*
* RETURNS boolean. False on some error, true when everything started out okay.
*
* This function runs on the event engine so it is basically async. After calling it, the font will not be changed until the callback runs
*/
//defaults
var o =
{
text: false,
fitWidth: "current",
fitHeight: "current",
marginVertical: 0,
marginHorizontal: 0,
cacheKey: false,
deleteCache: false, //special for development: set to true to recache without using the old value
callback: false,
applyTo: []
};
if (typeof(options) == "object")
{
_.each(options, function(v,k)
{
o[k] = v;
});
}
//o now contains all the chosen options plus defaults for the rest
//add orientation to the cachekey
if (o.cacheKey)
{
o.cacheKey = o.cacheKey + "_" + Ti.Gesture.orientation; //int
}
//log("*** fitTextInLabel label " + label.id + " tekst " + (o.text ? o.text : "(origineel)"),o);
var font = _.clone(label.font);
//cache?
if (o.cacheKey && (! o.deleteCache))
{
var cached = Ti.App.Properties.getInt(o.cacheKey,0);
if (cached)
{
font.fontSize = cached;
label.setFont(font);
//log("*** Cached op key " + o.cacheKey + " fontSize: " + cached);
_.each(o.applyTo,function(otherlabel)
{
//just set the font
var f = otherlabel.font;
f.fontSize = cached;
otherlabel.setFont(f);
});
//callback
if (o.callback)
{
o.callback(label,cached);
}
return; //done
}
}
//find the fontsize that fits in the label
//we use a different label outside of the view, to check it
var labelsize = label.getSize();
var parentsize = label.parent.getSize();
//which width and height to use?
var maxw = (o.fitWidth == "parent" ? parentsize : labelsize).width - (o.marginHorizontal / 2);
var maxh = (o.fitHeight == "parent" ? parentsize : labelsize).height - (o.marginVertical / 2);
//log("*** Moet passen in " + maxw + " X " + maxh);
font.fontSize = 40; //beginnen we mee, kan hoger en lager
var starting = true; //voor als we omhoog moeten
//create the test label in the parent container, using a postLayout callback for checking the fit
var testl = Ti.UI.createLabel({
text: (o.text ? o.text : label.getText()),
width: label.wordWrap ? maxw : Ti.UI.SIZE, //when wrapping, use a fixed with, otherwise just see how big it becomes
height: Ti.UI.SIZE, //we want to measure the height after setting the font size
wordWrap: label.wordWrap, //copy the wordWrap from the real label
font: font,
top: -5000 //somewhere out of view please (does this create scrollbars?)
});
var done = false;
var onPostLayout =
function()
{
//called when the test label is relayout because of the font change, so let's see how it all fits now
if (done)
{
return;
}
var lsize = testl.getSize();
//log("*** Proberen " + font.fontSize,lsize);
//We declare it a fit when the font becomes to small, fits inside the height of a wrapping label
//or fits inside the height AND width of a nonwrapping label
if (font.fontSize == 5 || (lsize.height <= maxh && (label.wordWrap || lsize.width < maxw)))
{
//it fits!
//did we startup with a too small font?
if (starting)
{
//the fontsize we started with fits. So let's try something bigger
font.fontSize += 10;
testl.setFont(font);
}
else
{
//we found it: it fits the space or is so small we stop trying
//log("*** Past!");
done = true; //stop the postLayout eventloop
label.setFont(font); //set the font
testl.parent.remove(testl);
testl = null; //garbace collect
if (o.cacheKey)
{
//let's cache this value
//log("*** Cachen naar " + o.cacheKey + ": " + font.fontSize);
Ti.App.Properties.setInt(o.cacheKey,font.fontSize);
}
//set the font for the applyTo array
_.each(o.applyTo,function(otherlabel)
{
//just set the font
var f = otherlabel.font;
f.fontSize = font.fontSize;
otherlabel.setFont(f);
});
//and callback
if (o.callback)
{
o.callback(label,font.fontSize);
}
}
}
else
{
//no fit yet. Let's try a pixel smaller
font.fontSize--;
testl.setFont(font); //this will fire a new postLayout event, running this function again
starting = false; //we are no longer starting up. When we find a fit, it's what we'll use
}
};
//let's go
testl.addEventListener("postlayout",onPostLayout);
label.parent.add(testl);
return true;
}
Hope this helps someone.
This parity-feature (TIMOB-1618) is currently in review and will be available in Titanium SDK 6.1.0 and later. The parity-review took some time, but you can already grab the changes from this Pull Request and apply them to your SDK today.