How to delete the old lines of a TextView - android

I'm developping an app which constantly needs to show the results to the user in a TextView like some sort of log.
The app works nicely and it shows the results in the TextView but as long as it keeps running and adding lines the app gets slower and crashes because of the character length of the TextView.
I would like to know if the android API provides any way to force a TexView to automatically delete the oldest lines that were introduced in order to make room for the new ones.

I had the same problem. I just resolved it.
The trick is to use the getEditableText() method of TextView. It has a replace() method, even a delete() one. As you append lines in it, the TextView is already marked as "editable", which is needed to use getEditableText(). I have something like that:
private final static int MAX_LINE = 50;
private TextView _debugTextView; // Of course, must be filled with your TextView
public void writeTerminal(String data) {
_debugTextView.append(data);
// Erase excessive lines
int excessLineNumber = _debugTextView.getLineCount() - MAX_LINE;
if (excessLineNumber > 0) {
int eolIndex = -1;
CharSequence charSequence = _debugTextView.getText();
for(int i=0; i<excessLineNumber; i++) {
do {
eolIndex++;
} while(eolIndex < charSequence.length() && charSequence.charAt(eolIndex) != '\n');
}
if (eolIndex < charSequence.length()) {
_debugTextView.getEditableText().delete(0, eolIndex+1);
}
else {
_debugTextView.setText("");
}
}
}
The thing is, TextView.getLineCount() returns the number of wrapped lines, and not the number of "\n" in the text... It is why I clear the whole text if I reach the end of the text while seeking the lines to delete.
You can do that differently by erasing a number of characters instead of erasing a number of lines.

This solution keeps track of the log lines in a list and overwrites the textview with the contents of the list on each change.
private List<String> errorLog = new ArrayList<String>();
private static final int MAX_ERROR_LINES = 70;
private TextView logTextView;
public void addToLog(String str) {
if (str.length() > 0) {
errorLog.add( str) ;
}
// remove the first line if log is too large
if (errorLog.size() >= MAX_ERROR_LINES) {
errorLog.remove(0);
}
updateLog();
}
private void updateLog() {
String log = "";
for (String str : errorLog) {
log += str + "\n";
}
logTextView.setText(log);
}

Here is an example that adds lines to an output log limited by the set max lines. The scrollview will auto scroll to the bottom after every line is added. This example work purely with the contents of the TextView so it doesn't have the need for a separate data collection.
Add the following to your activity xml:
<ScrollView
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical" >
<TextView
android:id="#+id/textViewOutput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1000" />
</ScrollView>
In your activity add the following code:
private static final int MAX_OUTPUT_LINES = 50;
private static final boolean AUTO_SCROLL_BOTTOM = true;
private TextView _textViewOutput;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
_textViewOutput = (TextView) findViewById(R.id.textViewOutput);
}
//call to add line(s) to TextView
//This should work if either lineText contains multiple
//linefeeds or none at all
private void addLinesToTextView(String lineText) {
_textViewOutput.append(lineText);
removeLinesFromTextView();
if(AUTO_SCROLL_BOTTOM)
_scrollView.post(new Runnable() {
#Override
public void run() {
_scrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
});
}
// remove leading lines from beginning of the output view
private void removeLinesFromTextView() {
int linesToRemove = _textViewOutput.getLineCount() - MAX_OUTPUT_LINES;
if (linesToRemove > 0) {
for (int i = 0; i < linesToRemove; i++) {
Editable text = _textViewOutput.getEditableText();
int lineStart = _textViewOutput.getLayout().getLineStart(0);
int lineEnd = _textViewOutput.getLayout().getLineEnd(0);
text.delete(lineStart, lineEnd);
}
}
}

The TextView shows what you set via setText() method. So this sounds to me like you should cut down the input you provide.
To empty the TextView, you can do setText("");

Kotlin answer of Vincent Hiribarren
fun write_terminal_with_limit(data: String?, limit:Int)
{
log_textView.append(data)
val nb_line_to_del: Int = log_textView.lineCount - limit
// Erase excessive lines
if (nb_line_to_del > 0)
{
var end_of_line_idx = -1
val char_seq: CharSequence = log_textView.text
for (i in 0 until nb_line_to_del)
{
do
{
end_of_line_idx++
}
while (end_of_line_idx < char_seq.length && char_seq[end_of_line_idx] != '\n')
}
if (end_of_line_idx < char_seq.length)
{
log_textView.editableText.delete(0, end_of_line_idx + 1)
}
else
{
log_textView.text = ""
}
}
}
I made personnal adjustment...

I think you are using TextView.append(string) then it will add to old text.
If you are setting using setText it will replace the old text

This is an old one, but I just found looking for a solution to my own problem.
I was able to remove all TextViews from a LinearLayout using nameoflayout.removeAllViews();
There is another method that will allow you to remove views from specified places in the layout using ints, it's: nameoflayout.removeViews(start, count); so I'm sure you could create a time out for how long textviews remain visible.

No, android API doesn't provide any functionally that delete oldest lines from textview automatically till API level 25. you need to do it logically.

Try to write a function that takes an old string on TextView and add new string to it, then get substring last strings that TextView capable. And set it to TextView. Something like this:
String str = textview.getText();
str += newstring;
int ln = str.length();
ln = ln-250;
if (ln<0) ln=0;
str = str.substring(ln);
textview.setText(str);

reference Vincent Hiribarren answer.
make it simple-->
TextView _debugTextView;
//if excess 20 lines keep new 200 chars
if(_debugTextView.getLineCount() >20) _debugTextView.getEditableText().delete(0,_debugTextView.getText().length()-200);

Related

Trying to clear one character from TextView

I made a calculator app and I made a clear Button that clears the TextView.
private TextView _screen;
private String display = "";
private void clear() {
display = "";
currentOperator = "";
result = "";
}
I got this code from Tutorial and set the clear button onClick to onClickClear, so it do that part of the code and it works. Now I have made this code delete only one number at a time and it don't work. What can be done to delete only one number at a time?
public void onClickdel(View v) {
display = "";
}
Below code will delete one char from textView.
String display = textView.getText().toString();
if(!TextUtils.isEmpty(display)) {
display = display.substring(0, display.length() - 1);
textView.setText(display);
}
You are modifying the string and not the textview.
To clear the TextView use:
_screen.setText("");
To remove the last character:
String str = _screen.getText().toString();
if(!str.equals(""))
str = str.substring(0, str.length() - 1);
_screen.setText(str);
String display = textView.getText().toString();
if(!display.isEmpty()) {
textView.setText(display.substring(0, display.length() - 1));
}

After spliting String into ArryList<String> i cannot read the text from any cell

I am an android noobie. What I am trying to do is to make this String an ArrayList. This is done. When i Print it On (with tv.setText) , the result is what i need but in this if i have right below i cannot find the "1".
The result i want to have is to store the text between the noumbers inside another ArrayList but to go there i have to be able to read the strings from the ArrayList.
public class MainActivity extends AppCompatActivity {
String text = "1Hello12People22Paul22Jackie21Anna12Fofo2";
TextView tv;
List<String> chars = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView)findViewById(R.id.tv);
PrinThemNow();
}
public void PrinThemNow(){
chars = Arrays.asList(text.split(""));
tv.setText(toString().valueOf(chars));
for(int i=0;i<chars.size();i++){
if(toString().valueOf(chars.get(i)) == " 1"){
Toast.makeText(this,"I found One",Toast.LENGTH_LONG).show();
//This if is not working while the TV's text shows " 1"
}
}
}
}
First, just a tip, from string to char[] you can use
char[] chars = myString.toCharArray();
because it has no sense to save a char array as a string ArrayList
but now the problem. you have your string and you wanna print the text between the numbers.
It's not really clear what is your goal but lets try.
I will suppose you used the char[] because it's 10 times better and easier
case 1) you wanna print text betweens "1"s
//lets loop the chars
bool firstOneFound = false;
int firstOccurrence = -1;
int secondOccurrence = -1;
int i = 0;
for(char c : chars){
//is it equals to 1?
if(c.equals('1')){
//check if we are already after the first 1
if(firstOneFound){
//if yes, we found the final one
secondOccurrence = i;
break;
}
else{
//this is the first occurrence
firstOccurrence = i;
firstOneFound = true;
}
}
i++;
}
if(firstOccurrence != -1 && secondOccurrence != -1){
String myFinalString = myString.subString(firstOccurrence, secondOccurrence);
}
case 2) you wanna print all text except numbers (maybe with a space instead)
for(char c : chars){
//check if it's a number
if(c >= '0' && c <= '9'){
//replace the number with anything else
c = ' '; //if you wanna have it as a space
}
}
//print the final string
String myFinalString = new String(chars);
NOTE:
You can also use ArrayList of string, just replace ' with "
hope it helps

Don't change varriable value after first call

I'm new android/java programmer and I can't find anywhere how to set default varriable value only on first call. My console log delete after second call. My code looks like:
public class Ftp {
[...]
//Console
String console_strings[] = new String [15];
int console_line = 0;
//
[...]
public void drawConsole(String msg){
CharSequence time = DateFormat.format("hh:mm:ss", d.getTime());
String message = "["+time+"] "+msg;
TextView console = (TextView)((Activity)context).findViewById(R.id.console);
String newString = "";
for(int i = 0; i < console_strings.length; i++){
if(console_strings[i] != null)
newString += console_strings[i] + "\n";
else
{
console_strings[i] = message;
newString += console_strings[i] + "\n";
break;
}
}
console.setText(newString);
}
}
Whenever I want to add something to the console, it delete old text value.
There are multiple ways to do this. The problem you are having is that you are setting the entire textview's text at once. You could do one of a few things. You could do
console.setText(console.getText() + newString);
or
console.append(newString);
Both of these would work. There are multiple other ways, but those should do it for you.
TextView has also an append method

Looping through two views to check for equality

I have two identical views with a number of editTexts. In one, pre-defined answers are populated in the editTexts (but not shown to the user). In the second, the user starts with all blank editTexts, and then fills them out in an attempt to make them the same as the pre-defined answers.
So I want to loop through the user's view, checking it against the pre-defined one, until an inequality is found, in which case the method will return false.
My code is below. Inside the onCreate I have a buttonListener (when the user is ready to check answers)
btnSolution.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(checkAnswer() == true){
Toast.makeText(getBaseContext(), "all good!", Toast.LENGTH_LONG).show();
}
else {
Toast.makeText(getBaseContext(), "no good", Toast.LENGTH_LONG).show();
}
}
});
the checkAnswer() method is then defined as follows
public boolean checkAnswer() {
final int ROW_COUNT = 15;
final int COL_COUNT = 10;
final String ROWS[] = {"R1","R2","R3","R4","R5","R6","R7","R8","R9","R10","R11","R12","R13","R14","R15"};
final String COLS[] = {"C1","C2","C3","C4","C5","C6","C7","C8","C9","C10"};
for(int i=0; i<ROW_COUNT; i++) {
for(int j=0; j<COL_COUNT; j++) {
String a = ROWS[i];
String b = COLS[j];
int editTextBaseId = getResources().getIdentifier("box" + a + b, "id", getPackageName());
int editTextAnswerId = getResources().getIdentifier("boxA" + a + b, "id", getPackageName());
EditText editTextBase = (EditText)findViewById(editTextBaseId);
EditText editTextAnswer = (EditText)findViewById(editTextAnswerId);
String textBase = editTextBase.getText().toString();
String textAnswer = editTextAnswer.getText().toString();
if(textBase.equals(textAnswer)) {
}
else {
return false;
}
}
}
return true;
}
Unfortunately when I try and run this I am getting a crash and the following error in my LogCat
12-17 00:05:02.075: E/SKIA(16370): FimgApiStretch:stretch failed
Any obvious errors?
That's not an error itself. I guess you're using a Samsung as your target device, if so, don't worry about it.
In the other hand, maybe it's better to compare only the strings. All those findViewById are inneficient.
Looking at your code:
EditText editTextAnswer = (EditText)findViewById(editTextAnswerId);
Do you have both views in the same layout, and the one with the answers is hidden? I mean, if you have the view with blank editTexts as the content of your activity, you can't find the editText with the answer as it's in other xml (assuming you did it as a different xml).

HTML.fromHtml line breaks disappearing

I am taking Spanned Text from an EditText box and converting it to a HTML tagged string using HTML.toHtml. This works fine. I have verified that the string is correct and contains a <br> in the appropriate location. However, when I got to convert the tagged string back to a spanned text to populate a TextView or EditText using HTML.fromHtml the <br> (or multiple ones if they are present) at the end of the first paragraph disappear. This means that if a users entered text with multiple line breaks and wanted to keep that formatting it gets lost.
I attached a picture to help illustrate this. The first EditText is the user input, the TextView Below it is the HTML.tohtml result of the EditText above it, the EditText below it is populated using HTML.fromHtml using the string in the TextView above it. As you can see the line breaks have disappeared and so have the extra lines. Furthermore, when the spanned text of the second edit text is run through the HTML.toHtml it now produces a different HTML tagged String.
I would like to be able to take the HTML tagged String from the first EditText and populate other TextViews or EditTexts without losing line breaks and formatting.
I also had this problem and I could not find an easy "transform" or something alike solution. Note something important, when the user presses "enter" java produces the special character \n but in HTML there is no such format for line breaking. It is the <br />.
So what I have done was to replace some specific CharSequences, from the plain text, by the alternative HTML format. In my case there was only the "enter" character so it was not that messy.
I had similar problem when I was trying to save/restore editText content to db. The problem is in Html.toHtml, it somehow skips line brakes:
String src = "<p dir=\"ltr\">First line</p><p dir=\"ltr\">Second<br/><br/><br/></p><p dir=\"ltr\">Third</p>";
EditText editText = new EditText(getContext());
// All line brakes are correct after this
editText.setText(new SpannedString(Html.fromHtml(src)));
String result = Html.toHtml(editText.getText()); // Here breaks are lost
// Output :<p dir="ltr">First line</p><p dir="ltr">Second<br></p><p dir="ltr">Third</p>
I've solved this by using custom toHtml function to serialize spanned text, and replaced all '\n' with "< br/>:
public class HtmlParser {
public static String toHtml(Spannable text) {
final SpannableStringBuilder ssBuilder = new SpannableStringBuilder(text);
int start, end;
// Replace Style spans with <b></b> or <i></i>
StyleSpan[] styleSpans = ssBuilder.getSpans(0, text.length(), StyleSpan.class);
for (int i = styleSpans.length - 1; i >= 0; i--) {
StyleSpan span = styleSpans[i];
start = ssBuilder.getSpanStart(span);
end = ssBuilder.getSpanEnd(span);
ssBuilder.removeSpan(span);
if (span.getStyle() == Typeface.BOLD) {
ssBuilder.insert(start, "<b>");
ssBuilder.insert(end + 3, "</b>");
} else if (span.getStyle() == Typeface.ITALIC) {
ssBuilder.insert(start, "<i>");
ssBuilder.insert(end + 3, "</i>");
}
}
// Replace underline spans with <u></u>
UnderlineSpan[] underSpans = ssBuilder.getSpans(0, ssBuilder.length(), UnderlineSpan.class);
for (int i = underSpans.length - 1; i >= 0; i--) {
UnderlineSpan span = underSpans[i];
start = ssBuilder.getSpanStart(span);
end = ssBuilder.getSpanEnd(span);
ssBuilder.removeSpan(span);
ssBuilder.insert(start, "<u>");
ssBuilder.insert(end + 3, "</u>");
}
replace(ssBuilder, '\n', "<br/>");
return ssBuilder.toString();
}
private static void replace(SpannableStringBuilder b, char oldChar, String newStr) {
for (int i = b.length() - 1; i >= 0; i--) {
if (b.charAt(i) == oldChar) {
b.replace(i, i + 1, newStr);
}
}
}
}
Also it turned out that this way is faster in about 4 times that default Html.toHtml(): I've made a benchmark with about 20 pages and 200 spans:
Editable ed = editText.getText(); // Here is a Tao Te Ching :)
String result = "";
DebugHelper.startMeasure("Custom");
for (int i = 0; i < 10; i++) {
result = HtmlParserHelper.toHtml(ed);
}
DebugHelper.stopMeasure("Custom"); // 19 ms
DebugHelper.startMeasure("Def");
for (int i = 0; i < 10; i++) {
result = Html.toHtml(ed);
}
DebugHelper.stopMeasure("Def"); // 85 ms
Replace /n => < br>< br>
example
< p>hi< /p>
< p>j< /p>
to:
< p>hi< /p>< br>< br>< p>j< /p>

Categories

Resources