EditText with SpannableStringBuilder and ImageSpan doesn't works fine - android

I'm trying to put emoticons inside a EditText. I've managed to do it and it works fine but I have a problem when I try to delete these emoticons from the EditText using the soft keyboard. I can't do this action with a single delete button's click. When I insert a new ImageSpan I replace an imageId for it but when I try to delete de icon I have to delete all the imageId characters before delete the image.
String fileName = "emoticon1.png";
Drawable d = new BitmapDrawable(getResources(), fileName);
String imageId = "[" + fileName + "]";
int cursorPosition = content.getSelectionStart();
int end = cursorPosition + imageId.length();
content.getText().insert(cursorPosition, imageId);
SpannableStringBuilder ss = new SpannableStringBuilder(content.getText());
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);
ss.setSpan(span, cursorPosition, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
content.setText(ss, TextView.BufferType.SPANNABLE);
content.setSelection(end);
I need to remove the emoticons with a single delete button's click. Could you help me, please?
Thanks!

This is the implementation to handle emoticons inside a EditText. This implementation uses the TextWatcher to monitor the EditText changes and detect if some emoticon was removed when some text is deleted.
Note that this implementation also verifies if a text selection was deleted (not only the delete key).
To avoid issues with text prediction when typing a text, it is recommended to surround the emoticon text with spaces (the text prediction can join the emoticon text with the adjacent text).
package com.takamori.testapp;
import java.util.ArrayList;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.Editable;
import android.text.Spanned;
import android.text.TextWatcher;
import android.text.style.ImageSpan;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;
public class MainActivity extends Activity {
private EmoticonHandler mEmoticonHandler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EditText editor = (EditText) findViewById(R.id.messageEditor);
// Create the emoticon handler.
mEmoticonHandler = new EmoticonHandler(editor);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_insert_emoticon:
// WARNING: The emoticon text shall be surrounded by spaces
// to avoid issues with text prediction.
mEmoticonHandler.insert(" :-) ", R.drawable.smile);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private static class EmoticonHandler implements TextWatcher {
private final EditText mEditor;
private final ArrayList<ImageSpan> mEmoticonsToRemove = new ArrayList<ImageSpan>();
public EmoticonHandler(EditText editor) {
// Attach the handler to listen for text changes.
mEditor = editor;
mEditor.addTextChangedListener(this);
}
public void insert(String emoticon, int resource) {
// Create the ImageSpan
Drawable drawable = mEditor.getResources().getDrawable(resource);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
ImageSpan span = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
// Get the selected text.
int start = mEditor.getSelectionStart();
int end = mEditor.getSelectionEnd();
Editable message = mEditor.getEditableText();
// Insert the emoticon.
message.replace(start, end, emoticon);
message.setSpan(span, start, start + emoticon.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
#Override
public void beforeTextChanged(CharSequence text, int start, int count, int after) {
// Check if some text will be removed.
if (count > 0) {
int end = start + count;
Editable message = mEditor.getEditableText();
ImageSpan[] list = message.getSpans(start, end, ImageSpan.class);
for (ImageSpan span : list) {
// Get only the emoticons that are inside of the changed
// region.
int spanStart = message.getSpanStart(span);
int spanEnd = message.getSpanEnd(span);
if ((spanStart < end) && (spanEnd > start)) {
// Add to remove list
mEmoticonsToRemove.add(span);
}
}
}
}
#Override
public void afterTextChanged(Editable text) {
Editable message = mEditor.getEditableText();
// Commit the emoticons to be removed.
for (ImageSpan span : mEmoticonsToRemove) {
int start = message.getSpanStart(span);
int end = message.getSpanEnd(span);
// Remove the span
message.removeSpan(span);
// Remove the remaining emoticon text.
if (start != end) {
message.delete(start, end);
}
}
mEmoticonsToRemove.clear();
}
#Override
public void onTextChanged(CharSequence text, int start, int before, int count) {
}
}
}

Related

Custom URLSpan don't trigger onClick event, using kotlin [duplicate]

Ok. These are my problems.
I need to user regular expressions to filet out everything except for letters and then I need to encase the found words within a $word tag.
With this str = str.replaceAll(pattern, "$0");.
right now I am filtering all of the right elements (punctuation, numbers etc) but its encasing every letter within each word in an a tag not the word. so how do I use the regular expression to group the letters to a word?
from "(a tag open)t(a close)(a tag open)h(a close)(a tag open)i(a close)(a tag open)s(a close) (a tag open)i(a close)(a tag open)s(a close) (a tag open)w(a close)(a tag open)r(a close)(a tag open)o(a close)(a tag open)n(a close)(a tag open)g(a close)";
to :
"(a tag open)This(a close) (a tag open)is(a close) (a tag open)right(a close)";
then I'm making them clickable and I need to catch the click event and get the position on screen on the clicked word as I want to use the clicked event to make a tool tip show up just below the clicked word. thank you for your help.
public class MainActivity extends Activity {
public String text = "This is just a sentence to test you. 23 this is another number23!g?";
public TextView tv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = explode(text);
tv = (TextView) findViewById(R.id.tv1);
tv.setLinksClickable(true);
tv.setMovementMethod(LinkMovementMethod.getInstance());
Spanned article = Html.fromHtml(text, null, null);
setHTML(article);
}
public void setHTML(Spanned html) {
SpannableString message = new SpannableString(html.toString());
Object[] spans = html.getSpans(0, html.length(), Object.class);
for (Object span : spans) {
int start = html.getSpanStart(span);
int end = html.getSpanEnd(span);
int flags = html.getSpanFlags(span);
if (span instanceof URLSpan) {
URLSpan urlSpan = (URLSpan) span;
span = new CallbackSpan(urlSpan.getURL());
}
message.setSpan(span, start, end, flags);
}
tv.setText(message);
}
public String explode(String str){
String pattern = "([a-zA-Z])";
str = str.replaceAll(pattern, "$0");
return str;
}
private final class CallbackSpan extends ClickableSpan {
private String m_data;
private String url_main;
public CallbackSpan(String url) {
m_data = url.substring(0);
url_main = url;
}
public void onClick(View view) {
TextView item = (TextView)findViewById(R.id.tv2);
item.setText(url_main + " was clicked.");
Log.d("item", url_main);
}
}
}
Latest code ,pls see link form github
message.setSpan(span, start, end, flags);
You need remove origin span before set new span.
Please see below
The onClick() of ClickableSpan is not working for URLSpan?
EDIT
You can capture any span click event by extends LinkMovementMethod
import android.os.Handler;
import android.os.Message;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.widget.TextView;
import java.lang.ref.WeakReference;
public class LinkMovementMethodExt extends LinkMovementMethod {
public static final int LinkMovementMethod_Down = 1001;
public static final int LinkMovementMethod_Up = 2002;
private static LinkMovementMethod sInstance;
private Class mSpanClass = null;
private WeakReference<Handler> mWeakReference = null;
public static MovementMethod getInstance(Handler handler, Class spanClass) {
if (sInstance == null) {
sInstance = new LinkMovementMethodExt();
((LinkMovementMethodExt) sInstance).mWeakReference = new WeakReference<>(handler);
((LinkMovementMethodExt) sInstance).mSpanClass = spanClass;
}
return sInstance;
}
#Override
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
/**
* get you interest span
*/
Object[] spans = buffer.getSpans(off, off, mSpanClass);
if (spans.length != 0) {
if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer, buffer.getSpanStart(spans[0]), buffer.getSpanEnd(spans[0]));
MessageSpan obj = new MessageSpan();
obj.setObj(spans);
obj.setView(widget);
Handler handler = mWeakReference.get();
if (handler != null) {
Message message = handler.obtainMessage();
message.obj = obj;
message.what = LinkMovementMethod_Down;
message.sendToTarget();
return true;
}
return false;
} else if (action == MotionEvent.ACTION_UP) {
Handler handler = mWeakReference.get();
if (handler != null) {
MessageSpan obj = new MessageSpan();
obj.setView(widget);
Message message = handler.obtainMessage();
message.obj = obj;
message.what = LinkMovementMethod_Up;
message.sendToTarget();
return true;
}
return false;
}
}
}
return super.onTouchEvent(widget, buffer, event);
}
public boolean canSelectArbitrarily() {
return true;
}
public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode,
KeyEvent event) {
return false;
}
textView.setMovementMethod(LinkMovementMethodExt.getInstance());
Edit for "android developer"
It is better way to add Handler property for LinkMovementMethodExt class.
You can capture all Spanned which are delivered as Message object.
Snip code in onTouchEvent method:
Message message = Handler.obtainMessage();
message.obj = buffer.getSpans(off, off, Spanned.class);//get all spanned
message.what = 111;//which value ,it is up to you
message.sendToTarget(); //send message to you target handler
You can handler expected spanned in you handler class. May be it is flexible way to handle .
Hope to help you.
Above textview text is <a href='/a'>aaaa</a>123456<a href='/b'>bbbb</b>7890
I understand you requirement : Click 'aaaa',you want get its href value '/a', click 'bbbb',get its href '/b'; Do not trigger default action which is opened in web browser.
If my understand is right, you can do like this:
Set LinkMovementMethod for textview, etc:textview.setMovementMethod(LinkMovementMethodExt.getInstance(handler, URLSpan.class));
Get interest span, here is URLSpan.
In you handler handleMessage method, you can do like this:
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
int what = msg.what;
if (what == 100) {
Object[] spans = (Object[])msg.obj;
for (Object span : spans) {
if (span instanceof URLSpan) {
System.out.println(((URLSpan) span).getURL());
}
}
}
};
};
Download demo code
MainActivity has color property which you can assign which color value as you like.
How to do?
Step1, get current click span.
Step2, set BackgroundColorSpan for current click span
Assuming You want click individual words on the textview?. I din't understand the converts all letters to words in your comment.
The below can be used to click on individual works and it is displayed in a toast.
public class MainActivity extends Activity {
TextView _tv;
String[] each;
SpannableString ss1;
Button b;
EditText et;
String s;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
b= (Button) findViewById(R.id.button1);
et = (EditText) findViewById(R.id.ed);
_tv = (TextView) findViewById( R.id.tv );
b.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
s=et.getText().toString();
_tv.setText("");
for(int i=0;i<s.length();i++)
{
each = s.split("\\s+");
}
for(int i=0;i<each.length;i++)
{
System.out.println("................"+each[i]);
ss1= new SpannableString(each[i]);
//StyleSpan boldSpan = new StyleSpan( Typeface.BOLD );
//spannable.setSpan( boldSpan, 41, 52, Spannable.SPAN_INCLUSIVE_INCLUSIVE );
ss1.setSpan(new MyClickableSpan(each[i]), 0, ss1.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
_tv.append(ss1);
_tv.append(" ");
}
_tv.setMovementMethod(LinkMovementMethod.getInstance());
}
});
}
class MyClickableSpan extends ClickableSpan{
String clicked;
public MyClickableSpan(String string) {
// TODO Auto-generated constructor stub
clicked =string;
}
//clickable span
public void onClick(View textView) {
//do something
Toast.makeText(MainActivity.this,clicked ,
Toast.LENGTH_SHORT).show();
}
#Override
public void updateDrawState(TextPaint ds) {
ds.setColor(Color.BLACK);//set text color
ds.setUnderlineText(false); // set to false to remove underline
}
}
}

Change future text to bold in EditText in Android

I am making a basic text editor app for Android and currently working on formatting the text.
I have an EditText named text_area where the user types his text and a ToggleButton called bold that sets the text to bold. Initially, using the EditText.setTypeface method, all of the text in text_area would change to bold when the button is on. Using the answer provided in this question, I was able to change only the selected text to bold.
What I really want to do though is that when the button is pressed, all the previously typed text (normal and/or bold) remain unchanged, and whatever the user types next is typed in bold.
Here's my code (Could someone also tell me what the code under the else statement does):
bold.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v) {
if(bold.isChecked()==true) {
Spannable str = textarea.getText();
if(textarea.getSelectionEnd() > textarea.getSelectionStart())
str.setSpan(new StyleSpan(android.graphics.Typeface.BOLD),
textarea.getSelectionStart(), textarea.getSelectionEnd(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
else
str.setSpan(new StyleSpan(android.graphics.Typeface.BOLD),
textarea.getSelectionEnd(),
textarea.getSelectionStart(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
});
I was stuck on the same issue, and after hours of trying, this is what I came up with.
First, whenever you check the bold/italics whatever styled checkbox you want to apply, you want to get the current cursor position.
//if bold is checked
YourEditText.getSelectionStart();
YourEditText.getSelectionEnd();
both gives you the current cursor position of the EditText if no text is highlighted.
Store this value into a variable.
int lastCursorPosition = YourEditText.getSelectionStart();
Then, I overrode the onTextChanged function of the EditText. Since we only want to set span from the last cursor position to the end of wherever change is made, we set the span from lastCursorPosition to the end of the text.
int endOfString = YourEditText.getText().toString().length();
StyleSpan ss = new StyleSpan(Typeface.BOLD);
str.setSpan(ss, lastCursorPosition, endOfString, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
While doing this, I ran into another problem. Whenever I applied another span to another part of the text, previous styles disappeared. I fixed this by creating new StyleSpan for each time a new style was applied. Minimal code to understand:
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;
private int currentTypeface;
private int lastCursorPosition;
...
#Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
Spannable str = this.getText();
StyleSpan ss;
int endOfString = text.toString().length();
//current typeface is determined by bold, italics, checkboxes, etc
switch(currentTypeface) {
case TYPEFACE_NORMAL:
ss = new StyleSpan(Typeface.NORMAL);
break;
case TYPEFACE_BOLD:
ss = new StyleSpan(Typeface.BOLD);
break;
case TYPEFACE_ITALICS:
ss = new StyleSpan(Typeface.ITALIC);
break;
case TYPEFACE_BOLD_ITALICS:
ss = new StyleSpan(Typeface.BOLD_ITALIC);
break;
default:
ss = new StyleSpan(Typeface.NORMAL);
}
str.setSpan(ss, lastCursorPosition, endOfString, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
Full TextArea class I've written:
public class TextArea extends EditText {
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;
private int currentTypeface;
private int lastCursorPosition;
private int tId;
public TextArea(Context context) {
super(context);
lastCursorPosition = this.getSelectionStart();
}
public TextArea(Context context, AttributeSet attrs) {
super(context, attrs);
}
public int gettId() {
return tId;
}
public void settId(int tId) {
this.tId = tId;
}
public void changeTypeface(int tfId) {
currentTypeface = tfId;
lastCursorPosition = this.getSelectionStart();
}
#Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
Spannable str = this.getText();
StyleSpan ss;
int endLength = text.toString().length();
switch(currentTypeface) {
case TYPEFACE_NORMAL:
ss = new StyleSpan(Typeface.NORMAL);
break;
case TYPEFACE_BOLD:
ss = new StyleSpan(Typeface.BOLD);
break;
case TYPEFACE_ITALICS:
ss = new StyleSpan(Typeface.ITALIC);
break;
case TYPEFACE_BOLD_ITALICS:
ss = new StyleSpan(Typeface.BOLD_ITALIC);
break;
default:
ss = new StyleSpan(Typeface.NORMAL);
}
str.setSpan(ss, lastCursorPosition, endLength, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
From MainActivity.java
TextArea t = new TextArea(context);
int typefaceStyle = TextArea.TYPEFACE_NORMAL;
CheckBox boldCheckbox = (CheckBox) findViewById(R.id.post_bold_checkbox);
boldCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
boldChecked = isChecked;
if(italicsChecked && boldChecked) {
typefaceStyle = TextArea.TYPEFACE_BOLD_ITALICS;
} else if (boldChecked){
typefaceStyle = TextArea.TYPEFACE_BOLD;
} else if (italicsChecked) {
typefaceStyle = TextArea.TYPEFACE_ITALICS;
} else {
typefaceStyle = TextArea.TYPEFACE_NORMAL;
}
t.changeTypeface(typefaceStyle);
}
});
My very first reply on StackOverflow! Hope this helped :-)

android textView and spannable get a word size

I have a textview in my app that change the size of a word when click on it, my problem is that if i click this word one time it become bigger, if i click again his size become the double. I need to make it smaller in the second click, by checking the text size of this spannable word before make it bigger.
private ClickableSpan getClickableSpan(final String word,final int start, final int end) {
return new ClickableSpan() {
final String mWord;
{
mWord = word;
}
#Override
public void onClick(View widget) {
TextView tv = (TextView) widget;
// TODO add check if tv.getText() instanceof Spanned
Spanned s = (Spanned) tv.getText();
int start = s.getSpanStart(this);
int end = s.getSpanEnd(this);
Log.d("Sd", "onClick [" + s.subSequence(start, end) + "]");
spans.setSpan(new RelativeSizeSpan(3f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);//check the size before make it bigger how to do so??
Toast.makeText(widget.getContext(), s.subSequence(start, end), Toast.LENGTH_SHORT)
.show();
}
#Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
ds.setColor(Color.WHITE);
}
};
}
I would use a simple boolean isBig and check it.
if (isBig) {
// make textview smaller
isBig = false;
} else {
// make textview bigger
isBig = true;
}

Android: Change the background color of a ClickableSpan when clicked

I have a SpannableString with a ClickableSpan as follows
for (int i = 0; i < items.size(); i++) {
final SpannableString span = new SpannableString(items.get(i));
final int index=i;
span.setSpan(new ClickableSpan() {
#Override
public void onClick(View widget) {
}
#Override
public void updateDrawState(TextPaint ds) {
ds.setColor(Color.LTGRAY);
ds.setUnderlineText(false);
}
}, 0, span.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
builder.append(span);
}
//my text view
txt.setText(builder);
txt.setMovementMethod(LinkMovementMethod.getInstance());
what I want to do is to change the foreground color of the span when clicked.
how can I do this ?
If you're looking to get rid of the green highlight on selection, this is what you want to know:
Apparently, overriding public void updateDrawState(TextPaint ds) in your custom class would not affect the highlight color. It is only used for setting the underline color (or hiding/showing it).
All you need to do is:
textView.setHighlightColor(Color.TRANSPARENT);
where textView is what contains the ClickableSpan.
Hope it works for all of you.
Feel free to ask any related question.
I extended the clickableSpan class and passed it a flag that lets me know that I should highlight it.
SpannableStringBuilder tag;
.... tag.setSpan(new WordSpan(i, tokens[i], wordtohighlitedID) {
.....
import android.graphics.Color;
import android.text.TextPaint;
import android.text.style.ClickableSpan;
import android.view.View;
public class WordSpan extends ClickableSpan
{
private int id;
private TextPaint textpaint;
public boolean shouldHilightWord = false;
public WordSpan(int anID, String txt, int selected) {
id =anID;
// if the word selected is the same as the ID set the highlight flag
if(selected == id) {
shouldHilightWord = true;
}
}
#Override
public void updateDrawState(TextPaint ds) {
textpaint = ds;
ds.setColor(ds.linkColor);
if(shouldHilightWord){
textpaint.bgColor = Color.GRAY;
textpaint.setARGB(255, 255, 255, 255);
}
//Remove default underline associated with spans
ds.setUnderlineText(false);
}
public void changeSpanBgColor(View widget){
shouldHilightWord = true;
updateDrawState(textpaint);
widget.invalidate();
}
#Override
public void onClick(View widget) {
// TODO Auto-generated method stub
}
/**
* This function sets the span to record the word number, as the span ID
* #param spanID
*/
public void setSpanTextID(int spanID){
id = spanID;
}
/**
* Return the wordId of this span
* #return id
*/
public int getSpanTextID(){
return id;
}
}
Use this:
view.setSelector(new ColorDrawable(Color.BLUE));

Ellipsize only a section in a TextView

I was wondering if it is possible to abbreviate only a portion of a string in a TextView. What I would like to do is something like this:
Element with short title (X)
Element with a very lo...(X)
The title should be ellipsized, but the X must be always visible. In my case, is not possible to use more than one TextView. Do you think there is a simple way of doing this?
Thanks!
I really needed a clean solution for a project so after searching around and not finding any solutions I felt I liked, I took some time to write this up.
Here is an implementation of a TextView with enhanced ellipsis control. The way it works is by using Android's Spanned interface. It defines an enum you can use to tag the specific section of text you'd like to be ellipsized if needed.
Limitations:
Does not support ellipsis at MIDDLE. This should be easy to add if it's really needed (I didn't).
This class will always render the text onto one line, as it only supports a single line of text. Others are welcome to extend it if that's needed (but it's a far harder problem).
Here's a sample of the usage:
FooActivity.java
class FooActivity extends Activity {
/**
* You can do this however you'd like, this example uses this simple
* helper function to create a text span tagged for ellipsizing
*/
CharSequence ellipsizeText(String text) {
SpannableString s = new SpannableString(text);
s.setSpan(TrimmedTextView.EllipsizeRange.ELLIPSIS_AT_END, 0, s.length(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
return s;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.foo_layout);
TextView textView = (TextView) findViewById(R.id.textView4);
SpannableStringBuilder text = new SpannableStringBuilder();
text.append(ellipsizeText("This is a long string of text which has important information "));
text.append("AT THE END");
textView.setText(text);
}
}
res/layouts/foo_layout.xml
<com.example.text.TrimmedTextView
android:id="#+id/textView4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"/>
That's it
Here's an example of the result:
The Implementation
package com.example.text;
import android.content.Context;
import android.text.Editable;
import android.text.Layout;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;
public class TrimmedTextView extends TextView {
public static enum EllipsizeRange {
ELLIPSIS_AT_START, ELLIPSIS_AT_END;
}
private CharSequence originalText;
private SpannableStringBuilder builder = new SpannableStringBuilder();
/**
* This allows the cached value of the original unmodified text to be
* invalidated whenever set externally.
*/
private final TextWatcher textCacheInvalidator = new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
originalText = null;
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
};
public TrimmedTextView(Context context) {
this(context, null, 0);
}
public TrimmedTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TrimmedTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
addTextChangedListener(textCacheInvalidator);
Log.v("TEXT", "Set!");
}
/**
* Make sure we return the original unmodified text value if it's been
* custom-ellipsized by us.
*/
public CharSequence getText() {
if (originalText == null) {
return super.getText();
}
return originalText;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Layout layout = getLayout();
CharSequence text = layout.getText();
if (text instanceof Spanned) {
Spanned spanned = (Spanned) text;
int ellipsisStart;
int ellipsisEnd;
TruncateAt where = null;
ellipsisStart = spanned.getSpanStart(EllipsizeRange.ELLIPSIS_AT_START);
if (ellipsisStart >= 0) {
where = TruncateAt.START;
ellipsisEnd = spanned.getSpanEnd(EllipsizeRange.ELLIPSIS_AT_START);
} else {
ellipsisStart = spanned.getSpanStart(EllipsizeRange.ELLIPSIS_AT_END);
if (ellipsisStart >= 0) {
where = TruncateAt.END;
ellipsisEnd = spanned.getSpanEnd(EllipsizeRange.ELLIPSIS_AT_END);
} else {
// No EllipsisRange spans in this text
return;
}
}
Log.v("TEXT", "ellipsisStart: " + ellipsisStart);
Log.v("TEXT", "ellipsisEnd: " + ellipsisEnd);
Log.v("TEXT", "where: " + where);
builder.clear();
builder.append(text, 0, ellipsisStart).append(text, ellipsisEnd, text.length());
float consumed = Layout.getDesiredWidth(builder, layout.getPaint());
CharSequence ellipsisText = text.subSequence(ellipsisStart, ellipsisEnd);
CharSequence ellipsizedText = TextUtils.ellipsize(ellipsisText, layout.getPaint(),
layout.getWidth() - consumed, where);
if (ellipsizedText.length() < ellipsisText.length()) {
builder.clear();
builder.append(text, 0, ellipsisStart).append(ellipsizedText)
.append(text, ellipsisEnd, text.length());
setText(builder);
originalText = text;
requestLayout();
invalidate();
}
}
}
}
You can try using something like this:
myTextView.setEllipsize(TextUtils.TruncateAt.MIDDLE);
It might not give you exactly what you want though, it may do something like this:
Element wi...title (X)
Reference Info
TruncateAt
setEllipsize

Categories

Resources