I am working on a EditText which can take Bold, Italics and Underline characters as mentioned in this question.
I have extended EditText and overrided the onTextChanged() method. My code works fine for the first occcurence of any bold, italics, underlined input but after my second occurence the first occurence gets changed to normal text.
Here is a gif depecting the problem
Here is the MainActivity.java
package com.example.syed.andtexteditor;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
int helperCounterB = 0;
int helperCounterI = 0;
int helperCounterU = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button boldButton = (Button) findViewById(R.id.bold_button);
boldButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Button boldButton = (Button) findViewById(R.id.bold_button);
helperCounterB++;
if (helperCounterB % 2 != 0)
//The EditText is in Bold mode when the Bold button is pressed odd-th time
boldButton.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.black));
else
boldButton.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.grey));
TextArea t = (TextArea) findViewById(R.id.textInput);
t.applyTypeface(helperCounterI, helperCounterB, helperCounterU);
}
});
Button italicsButton = (Button) findViewById(R.id.italics_button);
italicsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Button italicsButton = (Button) findViewById(R.id.italics_button);
helperCounterI++;
if (helperCounterI % 2 != 0) //The EditText is in Italics mode when the Italics button is pressed odd-th time
italicsButton.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.black));
else
italicsButton.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.grey));
TextArea t = (TextArea) findViewById(R.id.textInput);
t.applyTypeface(helperCounterI, helperCounterB, helperCounterU);
}
});
Button underlineButton = (Button) findViewById(R.id.underline_button);
underlineButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Button underlineButton = (Button) findViewById(R.id.underline_button);
helperCounterU++;
if (helperCounterU % 2 != 0)//The EditText is in Underline mode when the Underline button is pressed odd-th time
underlineButton.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.black));
else
underlineButton.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.grey));
TextArea t = (TextArea) findViewById(R.id.textInput);
t.applyTypeface(helperCounterI, helperCounterB, helperCounterU);
}
});
}
}
And here is the extended EditText class i.e TextArea.java
package com.example.syed.andtexteditor;
import android.content.Context;
import android.graphics.Typeface;
import android.support.v7.widget.AppCompatEditText;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.CharacterStyle;
import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
import android.util.AttributeSet;
import android.util.Log;
/**
* Created by Syed on 29-05-2017.
*/
public class TextArea extends AppCompatEditText {
Context c;
public static final int TYPEFACE_NORMAL = 0;
public static final int TYPEFACE_BOLD = 1;
public static final int TYPEFACE_ITALICS = 2;
public static final int TYPEFACE_BOLD_ITALICS = 3;
public static final int TYPEFACE_UNDERLINE = 4;
public static final int TYPEFACE_BOLD_UNDERLINE = 5;
public static final int TYPEFACE_ITALICS_UNDERLINE = 6;
public static final int TYPEFACE_BOLD_ITALICS_UNDERLINE = 7;
private int currentTypeface;
private int lastCursorPosition;
private StyleSpan normalspan = new StyleSpan(Typeface.NORMAL);
private StyleSpan boldspan = new StyleSpan(Typeface.BOLD);
private StyleSpan italicspan = new StyleSpan(Typeface.ITALIC);
private StyleSpan boldItalicspan = new StyleSpan(Typeface.BOLD_ITALIC);
private UnderlineSpan underlinespan = new UnderlineSpan();
public TextArea(Context context) {
super(context);
c = context;
lastCursorPosition = this.getSelectionStart();
}
public TextArea(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void changeTypeface(int tfId) {
currentTypeface = tfId;
lastCursorPosition = this.getSelectionStart();
}
public void applyTypeface(int helperCounterI, int helperCounterB, int helperCounterU) {
int min = 0;
int max = this.getText().length();
if (this.isFocused()) {
final int selStart = this.getSelectionStart();
final int selEnd = this.getSelectionEnd();
min = Math.max(0, Math.min(selStart, selEnd));
max = Math.max(0, Math.max(selStart, selEnd));
}
Spannable s = this.getText();
Editable selectedText = new SpannableStringBuilder(s, min, max);
SpannableStringBuilder s1 = new SpannableStringBuilder(s, 0, min);
String selectedTextString = selectedText.toString();
SpannableStringBuilder selectedSpannedString = new SpannableStringBuilder(selectedTextString);
Log.d(VIEW_LOG_TAG, "Helper Counter I: " + helperCounterI + " Helper Counter B: " + helperCounterB);
if (helperCounterI % 2 != 0 && helperCounterB % 2 != 0 && helperCounterU % 2 == 0) {
if (this.getSelectionEnd() == this.getSelectionStart()) {
this.changeTypeface(TextArea.TYPEFACE_BOLD_ITALICS);
}
//ignore this part as there are no issues with this
else {
StyleSpan styleSpan = new StyleSpan(Typeface.BOLD_ITALIC);
selectedSpannedString.setSpan(styleSpan, min, max, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
SpannableStringBuilder s3 = new SpannableStringBuilder(s, max, s.length());
CharSequence finalSpannable = TextUtils.concat(s1, selectedSpannedString, s3);
this.setText(finalSpannable);
}
} else if (helperCounterI % 2 != 0 && helperCounterB % 2 == 0 && helperCounterU % 2 == 0) {
if (this.getSelectionEnd() == this.getSelectionStart()) {
this.changeTypeface(TextArea.TYPEFACE_ITALICS);
}
//ignore this part as there are no issues with this
else {
StyleSpan styleSpan = new StyleSpan(Typeface.ITALIC);
selectedSpannedString.setSpan(styleSpan, min, max, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
SpannableStringBuilder s3 = new SpannableStringBuilder(s, max, s.length());
CharSequence finalSpannable = TextUtils.concat(s1, selectedSpannedString, s3);
this.setText(finalSpannable);
}
} else if (helperCounterI % 2 == 0 && helperCounterB % 2 != 0 && helperCounterU % 2 == 0) {
if (this.getSelectionEnd() == this.getSelectionStart()) {
this.changeTypeface(TextArea.TYPEFACE_BOLD);
}
//ignore this part as there are no issues with this
else {
StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
selectedSpannedString.setSpan(styleSpan, min, max, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
SpannableStringBuilder s3 = new SpannableStringBuilder(s, max, s.length());
CharSequence finalSpannable = TextUtils.concat(s1, selectedSpannedString, s3);
this.setText(finalSpannable);
}
} else if (helperCounterB % 2 == 0 && helperCounterI % 2 == 0 && helperCounterU % 2 == 0) {
this.changeTypeface(TextArea.TYPEFACE_NORMAL);
} else if (helperCounterU % 2 != 0 && helperCounterI % 2 == 0 && helperCounterB % 2 == 0) {
if (this.getSelectionEnd() == this.getSelectionStart()) {
this.changeTypeface(TYPEFACE_UNDERLINE);
}
//ignore this part as there are no issues with this
else {
selectedSpannedString.setSpan(new UnderlineSpan(), min, max, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
SpannableStringBuilder s3 = new SpannableStringBuilder(s, max, s.length());
CharSequence finalSpannable = TextUtils.concat(s1, selectedSpannedString, s3);
this.setText(finalSpannable);
}
} else if (helperCounterU % 2 != 0 && helperCounterI % 2 == 0 && helperCounterB % 2 != 0) {
if (this.getSelectionEnd() == this.getSelectionStart()) {
this.changeTypeface(TYPEFACE_BOLD_UNDERLINE);
}
//ignore this part as there are no issues with this
else {
selectedSpannedString.setSpan(new StyleSpan(Typeface.BOLD), min, max, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
selectedSpannedString.setSpan(new UnderlineSpan(), min, max, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
SpannableStringBuilder s3 = new SpannableStringBuilder(s, max, s.length());
CharSequence finalSpannable = TextUtils.concat(s1, selectedSpannedString, s3);
this.setText(finalSpannable);
}
} else if (helperCounterU % 2 != 0 && helperCounterI % 2 != 0 && helperCounterB % 2 == 0) {
if (this.getSelectionEnd() == this.getSelectionStart()) {
this.changeTypeface(TYPEFACE_ITALICS_UNDERLINE);
} else
//ignore this part as there are no issues with this
{
selectedSpannedString.setSpan(new StyleSpan(Typeface.ITALIC), min, max, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
selectedSpannedString.setSpan(new UnderlineSpan(), min, max, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
SpannableStringBuilder s3 = new SpannableStringBuilder(s, max, s.length());
CharSequence finalSpannable = TextUtils.concat(s1, selectedSpannedString, s3);
this.setText(finalSpannable);
}
} else if (helperCounterU % 2 != 0 && helperCounterI % 2 != 0 && helperCounterB % 2 != 0) {
if (this.getSelectionEnd() == this.getSelectionStart()) {
this.changeTypeface(TYPEFACE_BOLD_ITALICS_UNDERLINE);
}
//ignore this part as there are no issues with this
else {
selectedSpannedString.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), min, max, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
selectedSpannedString.setSpan(new UnderlineSpan(), min, max, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
SpannableStringBuilder s3 = new SpannableStringBuilder(s, max, s.length());
CharSequence finalSpannable = TextUtils.concat(s1, selectedSpannedString, s3);
this.setText(finalSpannable);
}
}
}
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
Log.d(VIEW_LOG_TAG, "Start: " + start + " Length before: " + lengthBefore + " Length After: " + lengthAfter + " TextLength: " + text.length());
CharacterStyle ss = null;
UnderlineSpan ss1 = null;
int endLength = text.toString().length();
switch (currentTypeface) {
case TYPEFACE_NORMAL:
ss = normalspan;
break;
case TYPEFACE_BOLD:
ss = boldspan;
break;
case TYPEFACE_ITALICS:
ss = italicspan;
break;
case TYPEFACE_BOLD_ITALICS:
ss = boldItalicspan;
break;
case TYPEFACE_UNDERLINE:
ss = underlinespan;
break;
case TYPEFACE_BOLD_UNDERLINE:
ss = boldspan;
ss1 = underlinespan;
break;
case TYPEFACE_ITALICS_UNDERLINE:
ss = italicspan;
ss1 = underlinespan;
break;
case TYPEFACE_BOLD_ITALICS_UNDERLINE:
ss = boldItalicspan;
ss1 = underlinespan;
break;
}
if (lastCursorPosition > endLength)
return;
Log.d(TextArea.class.getSimpleName(), new Integer(lastCursorPosition).toString() + new Integer(endLength).toString());
this.getText().setSpan(ss, lastCursorPosition, endLength, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
this.getText().setSpan(ss1, lastCursorPosition, endLength, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
Your problem is that you are re-using instances of the spans. Per the documentation of setSpan (emphasis mine):
So you simply need to create new Spans each time you want to attribute text.
Hope that helps!