I have a long passage in a TextView which is wrapped around by ScrollView. Is there any way to find the current visible text?
I can find the number of lines, line height in textview and also scrollx and scrolly from scrollview, but find the linkage to the current displayed text. Please help! Thanks.
It is simple to do this:
int start = textView.getLayout().getLineStart(0);
int end = textView.getLayout().getLineEnd(textView.getLineCount() - 1);
String displayed = textView.getText().toString().substring(start, end);
Here. Get the line number of the first displayed line. Then get the line number of the second displayed line. Then get the text and count the number of words.
private int getNumberOfWordsDisplayed() {
int start = textView.getLayout().getLineStart(getFirstLineIndex());
int end = textView.getLayout().getLineEnd(getLastLineIndex());
return textView.getText().toString().substring(start, end).split(" ").length;
}
/**
* Gets the first line that is visible on the screen.
*
* #return
*/
public int getFirstLineIndex() {
int scrollY = scrollView.getScrollY();
Layout layout = textView.getLayout();
if (layout != null) {
return layout.getLineForVertical(scrollY);
}
Log.d(TAG, "Layout is null: ");
return -1;
}
/**
* Gets the last visible line number on the screen.
* #return last line that is visible on the screen.
*/
public int getLastLineIndex() {
int height = scrollView.getHeight();
int scrollY = scrollView.getScrollY();
Layout layout = textView.getLayout();
if (layout != null) {
return layout.getLineForVertical(scrollY + height);
}
return -1;
}
Using textView.getLayout().getEllipsisStart(0) only works if android:singleLine="true"
Here is a solution that will work if android:maxLines is set:
public static String getVisibleText(TextView textView) {
// test that we have a textview and it has text
if (textView==null || TextUtils.isEmpty(textView.getText())) return null;
Layout l = textView.getLayout();
if (l!=null) {
// find the last visible position
int end = l.getLineEnd(textView.getMaxLines()-1);
// get only the text after that position
return textView.getText().toString().substring(0,end);
}
return null;
}
Remember: this works after the view is already loaded.
Usage:
textView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
textView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Log.i("test" ,"VisibleText="+getVisibleText(textView));
}
});
You claim that you know scrollY, the current number of pixels scrolled. You also know the height of the window you're considering in pixels, so call that scrollViewHeight. Then
int scrollY; // This is your current scroll position in pixels.
int scrollViewHeight; // This is the height of your scrolling window.
TextView textView; // This is the TextView we're considering.
String text = (String) textView.getText();
int charsPerLine = text.length() / textView.getLineCount();
int lineHeight = textView.getLineHeight();
int startLine = scrollY / lineHeight;
int endLine = startLine + scrollViewHeight/lineHeight + 1;
int startChar = charsPerLine * startLine;
int endChar = charsPerLine * (endLine+1) + 1;
String approxVisibleString = text.substring(startChar, endChar);
It's an approximation, so use it as a last resort.
I also having about the same problem myself. I needed first visible line from textview currently visible in recyclerview. If you are trying to get currently displayed first line of textview in recyclerview you may use the following code:
TextView tv = (TextView) recyclerView.getChildAt(0); //gets current visible child view
// this is for top visible
//view or the textview directly
Rect r1 = new Rect();
tv.getHitRect(r1);//gets visible rect of textview
Layout l = tv.getLayout();
int line = l.getLineForVertical(-1 * r1.top);//first visible line
int start = l.getLineStart(line);//visible line start
int end = l.getLineEnd(line);//visible line end
String displayed = tv.getText().toString().substring(start, end);
try use getEllipsisStart()
int end = textView.getLayout().getEllipsisStart(0);
This depends on the use of Ellipsize in the TextView. Try this:
public String getVisibleText(TextView tv) {
int lastLine = tv.getMaxLines() < 1 || tv.getMaxLines() > tv.getLineCount() ? tv.getLineCount() : tv.getMaxLines();
if (tv.getEllipsize() != null && tv.getEllipsize().equals(TextUtils.TruncateAt.END)) {
int ellCount = tv.getLayout().getEllipsisCount(lastLine - 1);
if (ellCount > 0 && tv.length() > ellCount)
return tv.getText().toString().substring(0, tv_title.getText().length() - ellCount);
return tv.getText().toString();
} else {
int end = tv.getLayout().getLineEnd(lastLine - 1);
return tv.getText().toString().substring(0, end);
}
}
...
textView.post(new Runnable() {
#Override
public void run() {
Log.d(TAG, getVisibleText(textView));
}
});
Assuming you have the scrolled line number, you can use the following to get displayed text:
int start = tv.getLayout().getLineStart(scrolllinenumber);
int end=scrolllinenumber+tv.getLayout().getHeight();
String displayedtext = tv.getText().toString().substring(start, end);
Related
I have a fragment which has RelativeLayout and a ImageView.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/pagelayout"
android:layout_below="#layout/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#color/aqua_blue"
tools:context="MainActivityFragment">
<ImageView
android:id="#+id/pdfImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"
android:adjustViewBounds="true"
android:scaleType="fitStart"/>
I render a pdfpage as an image and display in the image view in onViewCreated as follows.
pageLayout = (RelativeLayout) rootView.findViewById(R.id.pagelayout);
//Retain view references.
mImageView = (ImageView) rootView.findViewById(R.id.pdfImage);
// Show the first page by default.
mCurrentPageNum = pdfRenderer.getmCurrentPageNum();
if (mCurrentPageNum == 0) {
mCurrentPageNum = 1;
pdfRenderer.setmCurrentPageNum(mCurrentPageNum);
}
showPage(mCurrentPageNum, true, this.activity);
//get screen size
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
scrwidth = metrics.widthPixels;
scrheight = pdfRenderer.getPDFImage().getHeight();
pageControlsList = pdfRenderer.GetPageControls();
if (pageControlsList != null) {
if (pageControlsList.size() > 0) {
generateControls();
showPage(mCurrentPageNum, true, this.activity);
}
}
When the user swipe on the page, I navigate to next page or previous page. For that I am calling the following function written in fragment from Mainactivity.
public void Readpage(int index, boolean PdfReload, Activity activity)
{
//context=activity;
//pageLayout = (RelativeLayout) rootView.findViewById(R.id.pagelayout);
//mImageView = (ImageView) rootView.findViewById(R.id.pdfImage);
showPage(index, true, activity);
pageControlsList = pdfRenderer.GetPageControls();
if (pageControlsList != null) {
generateControls();
}
}
Generatecontrols method is called if there are any controls in the page and generated dynamically. The code is as follows (given only one control for sample).
public void generateControls() {
//iterate loop to create each control
for (Map.Entry<Integer, PageElement> ctrl : pageControlsList.entrySet()) {
Integer ctrlKey = ctrl.getKey();
PageElement pageField = ctrl.getValue();
String name = pageField.getName();
int type = pageField.getType();
int pdfw = (int) pdfRenderer.getPage_width();
int pdfh = (int) pdfRenderer.getPage_height();
//get dimensions of the control
int left = (int) pageField.getLeft();
int top = (int) pageField.getTop();
int right = (int) pageField.getRight();
int bottom = (int) pageField.getBottom();
int width = (int) pageField.getWidth();
int height = (int) pageField.getHeight();
//calculate dimensions w.r.t. screen size
int ctrlW = scrwidth * width / pdfw;
int ctrlH = scrheight * height / pdfh;
int ctrlLeft = (int) (scrwidth * left) / pdfw;
int ctrlTop = (int) (((scrheight * (pdfh - top)) / pdfh));
int ctrlRight = (int) (scrwidth * right) / pdfw;
int ctrlBottom = (int) (scrheight * bottom / pdfh);
//set dimensions of the control with layout parameters
RelativeLayout.LayoutParams ctrllp = new RelativeLayout.LayoutParams(ctrlW, ctrlH);
ctrllp.setMargins(ctrlLeft, ctrlTop, 0, 0);
//generate controls based on the type of control
if (type == 2) { //checkbox
CheckBox myChkBox = new CheckBox(context);
myChkBox.setId(ctrlKey);
myChkBox.setFocusable(false);
myChkBox.setEnabled(false);
if (pageField.ExportValue().contains("Off"))
myChkBox.setChecked(false);
else
myChkBox.setChecked(true);
pageLayout.setLayoutParams(ctrllp);
pageLayout.addView(myChkBox, ctrllp);
}
It works fine even if the first page contains any controls or not. Once I swipe if any other page contains controls then i can see only a blank fragment. If no controls then I can see everything working perfectly. I tried many ways but none worked for me. Any help please.
After some R& D I found the problem in my code. Simply write the code in the main activity instead of Fragment. Render the controls in the activity but not in fragment.
//to display page controls during appload
pageControlsList = pdfRenderer.GetPageControls();
if (pageControlsList != null) {
if (pageControlsList.size() > 0) {
generateControls();
//fillDefaultDocControls();
mfrg.showPage(pdfRenderer.getmCurrentPageNum(), true, this);
}
}
simply generate controls and call the fragment to show the page.
I'm doing almost the same as here (https://github.com/kmshack/Android-ParallaxHeaderViewPager), but with RecyclerView in tabs.
RecyclerView in one tab has items with big height and the first one is not always fully visible under header.
At some point I call RecyclerView's computeVerticalScrollOffset() (that calls StaggeredGridLayoutManager's method) and it returns completely wrong values. If I change header height to make first item fully visible I'm getting right values.
Is there any known solution/fix for this?
P.S. I also use LinearLayoutManager and always get right values even if first item is not fully visible under header
I believe the error is with the findFirstVisiblePosition() and findLastVisiblePosition() used in the computeScrollOffset() function arguments. Even if I use almost equal row height computeVerticalScrollOffset() returns really weird numbers.
If we replace them with the recyclerView.getChildAt(0) and recyclerView.getChildAt(recyclerView.getChildCount() - 1) that actually works.
So we can write our own function:
View firstItemView = recyclerView.getChildAt(0);
View lastItemView = recyclerView.getChildAt(recyclerView.getChildCount() - 1);
int firstItem = recyclerView.getChildLayoutPosition(firstItemView);
int lastItem = recyclerView.getChildLayoutPosition(lastItemView);
int itemsBefore = firstItem;
int laidOutArea = recyclerView.getLayoutManager().getDecoratedBottom(lastItemView) - recyclerView.getLayoutManager().getDecoratedTop(firstItemView);
int itemRange = lastItem - firstItem + 1;
float avgSizePerRow = (float) laidOutArea / itemRange;
int offset = (int) (itemsBefore * avgSizePerRow + recyclerView.getLayoutManager().getPaddingTop() - recyclerView.getLayoutManager().getDecoratedTop(firstItemView));
Let's follow the source code of RecyclerView:
#Override
public int computeVerticalScrollOffset() {
return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0;
}
mLayout is an instance of LayoutManager. In our case, it should belong to StaggeredGridLayoutManager. Let's check there:
#Override
public int computeVerticalScrollOffset(RecyclerView.State state) {
return computeScrollOffset(state);
}
then here:
private int computeScrollOffset(RecyclerView.State state) {
if (getChildCount() == 0) {
return 0;
}
ensureOrientationHelper();
return ScrollbarHelper.computeScrollOffset(state, mPrimaryOrientation,
findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
, findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
this, mSmoothScrollbarEnabled, mShouldReverseLayout);
}
And finally we go to ScrollbarHelper.computeScrollOffset:
/**
* #param startChild View closest to start of the list. (top or left)
* #param endChild View closest to end of the list (bottom or right)
*/
static int computeScrollOffset(RecyclerView.State state, OrientationHelper orientation,
View startChild, View endChild, RecyclerView.LayoutManager lm,
boolean smoothScrollbarEnabled, boolean reverseLayout) {
if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null ||
endChild == null) {
return 0;
}
final int minPosition = Math.min(lm.getPosition(startChild),
lm.getPosition(endChild));
final int maxPosition = Math.max(lm.getPosition(startChild),
lm.getPosition(endChild));
final int itemsBefore = reverseLayout
? Math.max(0, state.getItemCount() - maxPosition - 1)
: Math.max(0, minPosition);
if (!smoothScrollbarEnabled) {
return itemsBefore;
}
final int laidOutArea = Math.abs(orientation.getDecoratedEnd(endChild) -
orientation.getDecoratedStart(startChild));
final int itemRange = Math.abs(lm.getPosition(startChild) -
lm.getPosition(endChild)) + 1;
final float avgSizePerRow = (float) laidOutArea / itemRange;
return Math.round(itemsBefore * avgSizePerRow + (orientation.getStartAfterPadding()
- orientation.getDecoratedStart(startChild)));
}
And we should focus on last statement. This method returns the offset calculated by items * avgHeight, which isn't accurate when our items have variety of height.
As a result, when we use a LinearLayoutManager of GridLayoutManager, since height of each row is confirmed, computeVerticalScrollOffset() will return correct distance. However, unfortunately, when we use StaggeredGridLayoutManager, we cannot get accurate scroll offset by it.
P.S I know the reason why it isn't correct, but I don't know how to get correct scroll distance. If anyone knows, please visit: How to get correct scroll distance of RecyclerView when using a StaggeredGridLayoutManager? and post your answer.
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 a TextView with many ClickableSpan.
On click on a ClickableSpan, I have to get the coordinate on screen of it (to show a custom View at his position).
The problem is that I have no idea of how I can do this. The onClick() method of the ClickableSpan gives me in parameter a View, the TextView which contains the ClickableSpan.
I have used the following to get characters position in the TextView, but I don't know how I can convert it to get x/y position on screen of the text.
#Override
public void onClick(View v){
SpannableString completeText = (SpannableString)((TextView) v).getText();
Log.v("characters position", completeText.getSpanStart(this) + " / " + completeText.getSpanEnd(this));
}
Thanks in advance for your help!
EDIT : I want to get the entire coordinate of the ClickableSpan and its size, the aim is to show my custom view at the bottom center of the text. The onTouch method will give me the finger position, not the entire text coordinates. So, with this method, I will not have the middle of the text.
I have found a solution :-)
Here it is, may be this will help others with the same problem like me !
// Initialize global value
this.parentTextViewRect = new Rect();
// Initialize values for the computing of clickedText position
SpannableString completeText = (SpannableString)(parentTextView).getText();
Layout textViewLayout = parentTextView.getLayout();
double startOffsetOfClickedText = completeText.getSpanStart(clickedText);
double endOffsetOfClickedText = completeText.getSpanEnd(clickedText);
double startXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal((int)startOffsetOfClickedText);
double endXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal((int)endOffsetOfClickedText);
// Get the rectangle of the clicked text
int currentLineStartOffset = textViewLayout.getLineForOffset((int)startOffsetOfClickedText);
int currentLineEndOffset = textViewLayout.getLineForOffset((int)endOffsetOfClickedText);
boolean keywordIsInMultiLine = currentLineStartOffset != currentLineEndOffset;
textViewLayout.getLineBounds(currentLineStartOffset, this.parentTextViewRect);
// Update the rectangle position to his real position on screen
int[] parentTextViewLocation = {0,0};
parentTextView.getLocationOnScreen(parentTextViewLocation);
double parentTextViewTopAndBottomOffset = (
parentTextViewLocation[1] -
parentTextView.getScrollY() +
this.parentTextView.getCompoundPaddingTop()
);
this.parentTextViewRect.top += parentTextViewTopAndBottomOffset;
this.parentTextViewRect.bottom += parentTextViewTopAndBottomOffset;
// In the case of multi line text, we have to choose what rectangle take
if (keywordIsInMultiLine){
int screenHeight = this.mWindowManager.getDefaultDisplay().getHeight();
int dyTop = this.parentTextViewRect.top;
int dyBottom = screenHeight - this.parentTextViewRect.bottom;
boolean onTop = dyTop > dyBottom;
if (onTop){
endXCoordinatesOfClickedText = textViewLayout.getLineRight(currentLineStartOffset);
}
else{
this.parentTextViewRect = new Rect();
textViewLayout.getLineBounds(currentLineEndOffset, this.parentTextViewRect);
this.parentTextViewRect.top += parentTextViewTopAndBottomOffset;
this.parentTextViewRect.bottom += parentTextViewTopAndBottomOffset;
startXCoordinatesOfClickedText = textViewLayout.getLineLeft(currentLineEndOffset);
}
}
this.parentTextViewRect.left += (
parentTextViewLocation[0] +
startXCoordinatesOfClickedText +
this.parentTextView.getCompoundPaddingLeft() -
parentTextView.getScrollX()
);
this.parentTextViewRect.right = (int) (
this.parentTextViewRect.left +
endXCoordinatesOfClickedText -
startXCoordinatesOfClickedText
);
try this:
inside of CustomSpannableString class onClick function
#Override
public void onClick(View v){
val textView = v as TextView
val s = textView.text as Spanned
val startCoordinates = s.getSpanStart(this)
val endCoordinates = s.getSpanEnd(this)
return arrayOf(startCoordinates, endCoordinates)
}
not in spannableString class
val textView = findView... // txtView which text is set to SpannableString
val s = textView.text as Spanned
// CustomSpannableStringObj of type CustomSpannableString
val startCoordinates = s.getSpanStart(CustomSpannableStringObj)
val endCoordinates = s.getSpanEnd(CustomSpannableStringObj)
So I have a TextView in android that has the width of the whole length of the screen and a padding of dip 5. How can I calculate the number of characters that will fit a single line on the screen? I guess in other words, I'm trying to get the number of columns of a textview?
I considered manual calculation depending on textsize and width, but 1) don't know the correlation and 2) due to the padding in the units of dip, different screens will use different number of actual pixels to pad.
Overall Question: I am trying to use this to solve: if given a string how can I manually edit to string such that when the textview prints the string character by character, I will know when to start a word that won't fit on one line on the next. Note: I know that textview automatically puts words that won't fit onto the next line, however, since I'm printing character by character, like typing animation, textview doesn't know the word won't fit until it prints out the overflowing characters of that word.
Been searching everywhere for this...
Thanks!
Added solutions:
one possible solution:
public String measure2 (TextView t, String s) {
String u = "";
int start = 0;
int end = 1;
int space = 0;
boolean ellipsized = false;
float fwidth = t.getMeasuredWidth();
for(;;) {
//t.setText(s.substring(start, end));
float twidth = t.getPaint().measureText(s.substring(start, end));
if (twidth < fwidth){
if (end < s.length())
end++;
else {
if (!ellipsized)
return s;
return u + s.subSequence(start, end);
}
}
else {
ellipsized = true;
space = (u + s.substring(start, end)).lastIndexOf(" ");
if (space == -1)
space = end - 1;
u += s.subSequence(start, space) + "\n";
start = space + 1;
end = start + 1;
}
}
}
solution 2, but still uses solution1 sometimes:
public String measure3 (TextView t, String s) {
List<String> wlist = Arrays.asList(s.split(" "));
if (wlist.size() == 1)
return measure2(t, s);
String u = "";
int end = 1;
float fwidth = t.getMeasuredWidth();
for(;;) {
//t.setText(s.substring(start, end));
if (wlist.isEmpty())
return u;
String temp = listStr(wlist, end);
float twidth = t.getPaint().measureText(temp);
if (twidth < fwidth){
if (end < wlist.size())
end++;
else {
return u + temp;
}
}
else {
temp = listStr(wlist, end-1);
if (end == 1)
temp = measure2(t, temp);
if (wlist.isEmpty())
return u + temp;
else
u = u + temp + "\n";
wlist = wlist.subList(end - 1, wlist.size());
end = 1;
}
}
}
public String listStr (List<String> arr, int end) {
String s = "";
for (String e : arr.subList(0, end) ){
s = s + e + " ";
}
return s.trim();
}
I used the above code to generate off a original string s, a string u that would be printed. However, I think this approach is very inefficient. Is there another approach or a better algorithm? Note: there are some errors in measure3 that I fixed, but was too lazy to edit
Try this:
private boolean isTooLarge (TextView text, String newText) {
float textWidth = text.getPaint().measureText(newText);
return (textWidth >= text.getMeasuredWidth ());
}
Detecting how many characters fit will be impossible due to the variable width of the characters. The above function will test if a particular string will fit or not in the TextView. The content of newText should be all the characters in a particular line. If true, then start a new line (and using a new string to pass as parameter).
Answer to the comment:
because the app can be run in many systems is exactly why you need to measure it.
This is a way to solve your "overall question". What is the difference between using str.size()>numCol vs is too large? You will need to implement your animation (hint #1: insert a newline character)
as I said before when you start a new line, you start a new string (hint #2: if you extend TextView, you can implement all this in overriding setText). (hint #3: Keep track of the lines created with a static int lines; and use newString.split("\\r?\\n")[lines-1] to check for length).
You can get total line of Textview and get string for each characters by below code.Then you can set style to each line whichever you want.
I set first line bold.
private void setLayoutListner( final TextView textView ) {
textView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
textView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
final Layout layout = textView.getLayout();
// Loop over all the lines and do whatever you need with
// the width of the line
for (int i = 0; i < layout.getLineCount(); i++) {
int end = layout.getLineEnd(0);
SpannableString content = new SpannableString( textView.getText().toString() );
content.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, end, 0);
content.setSpan(new StyleSpan(android.graphics.Typeface.NORMAL), end, content.length(), 0);
textView.setText( content );
}
}
});
}
Try this way.You can apply multiple style this way.
I had the same issue and I calculated the number characters per line by 2 steps:
Step 1: Calculate the number of lines
val widthOfTvComment = widthOfScreen - marginLeft - marginRight
var bounds = Rect()
var paint = Paint()
paint.textSize = textSize
paint.getTextBounds(comment,0,comment.length,bounds)
val lines = ( bounds.width()/widthOfTvComment)
Step 2: Calculated the number characters per line
val charactersPerLine = comment.length / lines