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
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 span like this:
private SpannableStringBuilder spandex(List<String> ret, Boolean startDark) {
SpannableStringBuilder spanBuilder = new SpannableStringBuilder();
Spannable span = null;
int color = 0;
int startDarkMod = 0;
if(startDark)
startDarkMod = 1;
for (String x : ret) {
if ((ret.indexOf(x) + startDarkMod) % 2 > 0)
color = WzTheme.NOT_HIGHLIGHTED_COLOR;
else
color = WzTheme.ALT_NOT_HIGHLIGHTED_COLOR;
span = new SpannableString(x.toUpperCase());
span.setSpan(new ForegroundColorSpan(color),
0,
x.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanBuilder.append(span);
}
return spanBuilder;
}
public static int NOT_HIGHLIGHTED_COLOR = Color.rgb(68, 68, 68);
public static int ALT_NOT_HIGHLIGHTED_COLOR = Color.rgb(110, 110, 110);
It produces light gray and dark grey code like this:
Then there is that bright white "T". It happens after every ß character, which, as the other thread mentioned, gets converted to SS when capitalized. I would prefer no capatalization of that char, but I can live with the conversion to SS. What I need to stop from happening is the big white "T". Any ideas?
ugh, this fixes it:
private String myUpperCase(String word) {
word = word.replaceAll("ß", "XXX");
return word.toUpperCase().replaceAll("XXX", "ß");
}
so as you probably already know, the issue is that ß gets expanded to SS in java utf-8 toUpperCase() and that increases the length of the string in the span by one, but "x" (in my code above) is one char short (length) so i get the default white text.
i am happy with the fix.
I use PDFJet-Open-Source library to construct a pdf. So, I have couple of questions:
1) How can I place the multiline text inside Cell?
Problem description: Currently I faced with problem that I can't place the multiline text inside Cell object. I tried set text like "text1 \n text2..." but it does not have any effect. Unfortunatelly, open source version does not have TextColumn and Paragraph classes.
2) What is the CompositeTextLine and how to use it?
Problem description: Perhaps I have wrong imagination, but I tried to do the following:
...
CompositeTextLine ctl = new CompositTextLine(0,0);
ctl.addComponent(new TextLine(f1,"MyText1"));
ctl.addComponent(new TextLine(f1,"MyText2"));
ctl.addComponent(new TextLine(f1,"MyText3"));
Cell cell = new Cell(f1);
cell.setCompositeTextLine(ctl);
...
I expected to see several multiple lines in the Cell but I observed nothing. Moreover, if I add the line table.wrapAroundCellText(), I've got NullPointerException. If I call ctl.drawOn(page), I just observe: "MyText1 MyText2 MyText3" without line breaking.
UPDATE: I discovered the TextBox class, so that, if I write:
TextBox textbox = new TextBox(f1);
textbox.setText("First Line \n Second Line");
textbox.drawOn(page);
it will construct what I want:
First Line
Second Line
But still I am interested with the possibility of (1) and description of (2) and some of them variations, like to "How to set TextBox (or image etc.) inside Cell, not only single line?"
And last one, could anyone, please, refer me to the realization of "text justification" algorithm in Java or C++.
I can across the same problem, and I ended up extending Cell, and using WordUtils from apache commons lang3:
public class MultilineCell extends Cell {
private final int characterCount;
public MultilineCell(Font font, String content, int characterCount) {
super(font, content);
this.characterCount = characterCount;
}
#Override
public String getText() {
return WrapUtil.wrap(super.getText(), this.characterCount);
}
#Override
public float getHeight() {
float height = this.font.getBodyHeight();
String text = getText();
if (text != null) {
String[] wrappedTexts = text.split(System.getProperty("line.separator"));
if (wrappedTexts.length > 1) {
return (height * wrappedTexts.length) + this.top_padding + this.bottom_padding;
}
}
return height + this.top_padding + this.bottom_padding;
}
#Override
protected void paint(Page page, float x, float y, float w, float h) throws Exception {
page.setPenColor(this.getPenColor());
page.setPenWidth(this.lineWidth);
drawBorders(page, x, y, w, h);
drawText(page, x, y, w);
}
private void drawBorders(
Page page,
float x,
float y,
float cell_w,
float cell_h) throws Exception {
if (getBorder(Border.TOP) &&
getBorder(Border.BOTTOM) &&
getBorder(Border.LEFT) &&
getBorder(Border.RIGHT)) {
page.drawRect(x, y, cell_w, cell_h);
}
else {
if (getBorder(Border.TOP)) {
page.moveTo(x, y);
page.lineTo(x + cell_w, y);
page.strokePath();
}
if (getBorder(Border.BOTTOM)) {
page.moveTo(x, y + cell_h);
page.lineTo(x + cell_w, y + cell_h);
page.strokePath();
}
if (getBorder(Border.LEFT)) {
page.moveTo(x, y);
page.lineTo(x, y + cell_h);
page.strokePath();
}
if (getBorder(Border.RIGHT)) {
page.moveTo(x + cell_w, y);
page.lineTo(x + cell_w, y + cell_h);
page.strokePath();
}
}
}
private void drawText(
Page page,
float x,
float y,
float cell_w) throws IOException {
String wrappedText = WrapUtil.wrap(super.getText(), this.characterCount);
String[] lines = wrappedText.split(System.getProperty("line.separator"));
float x_text = x + this.left_padding;
float y_text = y + this.font.getAscent() + this.top_padding;
for (String line : lines) {
page.drawString(this.font, line, x_text, y_text);
y_text += this.font.getBodyHeight();
}
}
}
You can instantiate and add the MultilineCell as you would with a Cell:
List<Cell> rowCells = new ArrayList<Cell>();
rowCells.add(new MultilineCell(font, c.getString(reasonIdx), 42));
I know that extension is not a nice solution, and copying drawBorders() is even worse, but in this case, it is the only solution if you don't want to fork PDFJet.
This however breaks autoAdjustColumnWidths: the width is calculated on the whole text, instead of the longest line. So if you intend to use this method, either subclass Table, fork PDFJet, or extract just this method (the only downside of the latter is that I couldn't work around the cell padding though):
/**
* Auto adjusts the widths of all columns so that they are just wide enough to hold the text without truncation.
*/
private static void autoAdjustColumnWidths(List<List<Cell>> tableData) {
// Find the maximum text width for each column
float[] max_col_widths = new float[tableData.get(0).size()];
for (int i = 0; i < tableData.size(); i++) {
List<Cell> row = tableData.get(i);
for (int j = 0; j < row.size(); j++) {
Cell cell = row.get(j);
if (cell.getColSpan() == 1) {
float cellWidth = 0f;
if (cell.getImage() != null) {
cellWidth = cell.getImage().getWidth();
}
if (cell.getText() != null) {
// Is this a multiline cell? If so, measure the widest line
if (cell.getText().contains(MultilineCell.NEW_LINE)) {
String[] lines = cell.getText().split(MultilineCell.NEW_LINE);
for (String line : lines) {
if (cell.getFont().stringWidth(cell.getFallbackFont(), line) > cellWidth) {
cellWidth = cell.getFont().stringWidth(cell.getFallbackFont(), line);
}
}
}
// Standard (single-line) cell, measure whole text
else {
if (cell.getFont().stringWidth(cell.getFallbackFont(), cell.getText()) > cellWidth) {
cellWidth = cell.getFont().stringWidth(cell.getFallbackFont(), cell.getText());
}
}
}
cell.setWidth(cellWidth + 2f /*cell.left_padding*/ + 2f/*cell.right_padding*/);
if (max_col_widths[j] == 0f ||
cell.getWidth() > max_col_widths[j]) {
max_col_widths[j] = cell.getWidth();
}
}
}
}
for (int i = 0; i < tableData.size(); i++) {
List<Cell> row = tableData.get(i);
for (int j = 0; j < row.size(); j++) {
Cell cell = row.get(j);
cell.setWidth(max_col_widths[j]);
}
}
}
PDFJet is a funny library, btw: protected fields, every other method throws Exception, and the classes are definitely not designed for extension, even if there're not final.
There is a tricky solution for that:
As suggested before split your input into several lines (using for example using WordUtils from apache commons lang3)
String wrappedText = WrapUtil.wrap(super.getText(), this.characterCount);
String[] lines = wrappedText.split(System.getProperty("line.separator"));
After you have your lines, add as many rows as lines into your table with those separate lines.
To simulate "multiline cell" call function removeLineBetweenRows providing index of those added rows. It will look like a one big cell.
I have a TextView in which I want to place a solid color block over given words of the TextView, for example:
"This is a text string, I want to put a rectangle over this WORD" - so, "WORD" would have a rectangle with a solid color over it.
To do this, I am thinking about overriding the onDraw(Canvas canvas) method, in order to draw a block over the text. My only problem is to find an efficient way to get the absolute position of a given word or character.
Basically, I am looking for something that does the exact opposite of the getOffsetForPosition(float x, float y) method
Based on this post: How get coordinate of a ClickableSpan inside a TextView?, I managed to use this code in order to put a rectangle on top of the text:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
// Initialize global value
TextView parentTextView = this;
Rect parentTextViewRect = new Rect();
// Find where the WORD is
String targetWord = "WORD";
int startOffsetOfClickedText = this.getText().toString().indexOf(targetWord);
int endOffsetOfClickedText = startOffsetOfClickedText + targetWord.length();
// Initialize values for the computing of clickedText position
Layout textViewLayout = parentTextView.getLayout();
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, parentTextViewRect);
// Update the rectangle position to his real position on screen
int[] parentTextViewLocation = {0,0};
parentTextView.getLocationOnScreen(parentTextViewLocation);
double parentTextViewTopAndBottomOffset = (
//parentTextViewLocation[1] -
parentTextView.getScrollY() +
parentTextView.getCompoundPaddingTop()
);
parentTextViewRect.top += parentTextViewTopAndBottomOffset;
parentTextViewRect.bottom += parentTextViewTopAndBottomOffset;
// In the case of multi line text, we have to choose what rectangle take
if (keywordIsInMultiLine){
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int screenHeight = display.getHeight();
int dyTop = parentTextViewRect.top;
int dyBottom = screenHeight - parentTextViewRect.bottom;
boolean onTop = dyTop > dyBottom;
if (onTop){
endXCoordinatesOfClickedText = textViewLayout.getLineRight(currentLineStartOffset);
}
else{
parentTextViewRect = new Rect();
textViewLayout.getLineBounds(currentLineEndOffset, parentTextViewRect);
parentTextViewRect.top += parentTextViewTopAndBottomOffset;
parentTextViewRect.bottom += parentTextViewTopAndBottomOffset;
startXCoordinatesOfClickedText = textViewLayout.getLineLeft(currentLineEndOffset);
}
}
parentTextViewRect.left += (
parentTextViewLocation[0] +
startXCoordinatesOfClickedText +
parentTextView.getCompoundPaddingLeft() -
parentTextView.getScrollX()
);
parentTextViewRect.right = (int) (
parentTextViewRect.left +
endXCoordinatesOfClickedText -
startXCoordinatesOfClickedText
);
canvas.drawRect(parentTextViewRect, paint);
}
You can use spans for that.
First you create a spannable for your text, like this:
Spannable span = new SpannableString(text);
Then you put a span around the word that you want to highlight, somewhat like this:
span.setSpan(new UnderlineSpan(), start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Unfortunately I don't know of an existing span that puts a border around a word. I found UnderlineSpan, and also BackgroundColorSpan, perhaps these are also useful for you, or you can have a look at the code and see if you can create a BorderSpan based on one of those.
Instead of drawing a rectangle over the WORD, you could simply replace its characters with an appropriate unicode symbol like U+25AE (▮ Black vertical rectangle).
So you'd get
"This is a text string, I want to put a rectangle over this ▮▮▮▮"
If that is sufficient. See for example Wikipedia for a wast list of unicode symbols.
If you actually need to paint that black box you can do the following as long as your text is in a single line:
Calculate the width of the text part before 'WORD' as explained here to find the left edge of the box and calcuate the width of 'WORD' using the same method to find the width of the box.
For a multiline text the explained method might also work but I think you'll have to do quite a lot of work here.
use getLayout().getLineBottom and textpaint.measureText to manually do the reverse calculation of getOffsetForPosition.
below is an example of using the calculated x,y for some textOffset to position the handle drawable when the textview gets clicked.
class TextViewCustom extends TextView{
float lastX,lastY;
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean ret = super.onTouchEvent(event);
lastX=event.getX();
lastY=event.getY();
return ret;
}
BreakIterator boundary;
Drawable handleLeft;
private void init() {// call it in constructors
boundary = BreakIterator.getWordInstance();
handleLeft=getResources().getDrawable(R.drawable.abc_text_select_handle_left_mtrl_dark);
setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
int line = getLayout().getLineForVertical((int) lastY);
int offset = getLayout().getOffsetForHorizontal(line, lastX);
int wordEnd = boundary.following(offset);
int wordStart = boundary.previous();
CMN.Log(getText().subSequence(wordStart, wordEnd));
int y = getLayout().getLineBottom(line);
int trimA = getLayout().getLineStart(line);
float x = getPaddingLeft()+getPaint().measureText(getText(), trimA, wordStart);
x-=handleLeft.getIntrinsicWidth()*1.f*9/12;
handleLeft.setBounds((int)x,y,(int)(x+handleLeft.getIntrinsicWidth()),y+handleLeft.getIntrinsicHeight());
invalidate();
}
});
}
#Override
public void setText(CharSequence text, BufferType type) {
super.setText(text, type);
if(boundary!=null)
boundary.setText(text.toString());
}
}
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);