Bold ClickableSpan on touch - android

I have a TextView in which every word is a ClickableSpan (actually a custom subclass of ClickableSpan). When a word is touched, it should be shown in bold font style. If I set textIsSelectable(false) on the TextView, it works just fine. The word is immediately bolded. But if text is selectable, then it does not work. BUT - if I touch a word and then turn the screen off and back on, when the screen display comes back on the word is bolded. I have tried everything I can think of to force a redraw (invalidate the TextView, force call Activity's onRestart(), refreshDrawableState() on the TextView, etc). What am I missing?
Here is my subclass of ClickableSpan:
public class WordSpan extends ClickableSpan
{
int id;
private boolean marking = false;
TextPaint tp;
Typeface font;
int color = Color.BLACK;
public WordSpan(int id, Typeface font, boolean marked) {
this.id = id;
marking = marked;
this.font = font;
}
#Override
public void updateDrawState(TextPaint ds) {
ds.setColor(color);
ds.setUnderlineText(false);
if (marking)
ds.setTypeface(Typeface.create(font,Typeface.BOLD));
tp = ds;
}
#Override
public void onClick(View v) {
// Empty here -- overriden in activity
}
public void setMarking(boolean m) {
marking = m;
updateDrawState(tp);
}
public void setColor(int col) {
color = col;
}
}
Here is the WordSpan instantiation code in my Activity:
... looping through words
curSpan = new WordSpan(index,myFont,index==selectedWordId) {
#Override
public void onClick(View view) {
handleWordClick(index,this);
setMarking(true);
tvText.invalidate();
}
};
... continue loop code
And here is my custom MovementMethod:
public static MovementMethod createMovementMethod ( Context context ) {
final GestureDetector detector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onSingleTapUp ( MotionEvent e ) {
return true;
}
#Override
public boolean onSingleTapConfirmed ( MotionEvent e ) {
return false;
}
#Override
public boolean onDown ( MotionEvent e ) {
return false;
}
#Override
public boolean onDoubleTap ( MotionEvent e ) {
return false;
}
#Override
public void onShowPress ( MotionEvent e ) {
return;
}
});
return new ScrollingMovementMethod() {
#Override
public boolean canSelectArbitrarily () {
return true;
}
#Override
public void initialize(TextView widget, Spannable text) {
Selection.setSelection(text, text.length());
}
#Override
public void onTakeFocus(TextView view, Spannable text, int dir) {
if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
if (view.getLayout() == null) {
// This shouldn't be null, but do something sensible if it is.
Selection.setSelection(text, text.length());
}
} else {
Selection.setSelection(text, text.length());
}
}
#Override
public boolean onTouchEvent ( TextView widget, Spannable buffer, MotionEvent event ) {
// check if event is a single tab
boolean isClickEvent = detector.onTouchEvent(event);
// detect span that was clicked
if (isClickEvent) {
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);
WordSpan[] link = buffer.getSpans(off, off, WordSpan.class);
if (link.length != 0) {
// execute click only for first clickable span
// can be a for each loop to execute every one
if (event.getAction() == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
return true;
}
else if (event.getAction() == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
return false;
}
}
else {
}
}
// let scroll movement handle the touch
return super.onTouchEvent(widget, buffer, event);
}
};
}

Your spans are somehow becoming immutable when the text is set as selectable (TextView#setTextIsSelectable(true)). Here is a good write-up on Understanding Spans that explains mutability of spans. I also think that this post has some good explanations
I am not sure how your spans are getting to be immutable. Maybe they are mutable but just not showing somehow? It is unclear. Maybe someone has an explanation for this behavior. But, for now, here is a fix:
When you rotate your device or turn it off and back on, the spans are recreated or just reapplied. That is why you see the change. The fix is to not try to change the spans when clicked, but to reapply it with the font bolded. That way the change will take effect. You will not even need to call invalidate(). Keep track of the bolded span so it can be unbolded later when another span is clicked.
Here is the result:
Here is main activity. (Please forgive all the hard-coding, but this is just a sample.)
MainActivity.java
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
private WordSpan mBoldedSpan;
#Override
protected void onCreate(Bundle savedInstanceState) {
Typeface myFont = Typeface.DEFAULT;
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.textView);
mTextView.setTextIsSelectable(true);
mTextView.setMovementMethod(createMovementMethod(this));
SpannableString ss = new SpannableString("Hello world! ");
int[][] spanStartEnd = new int[][]{{0, 5}, {6, 12}};
for (int i = 0; i < spanStartEnd.length; i++) {
WordSpan wordSpan = new WordSpan(i, myFont, false) {
#Override
public void onClick(View view) {
// handleWordClick(index, this); // Not sure what this does.
Spannable ss = (Spannable) mTextView.getText();
if (mBoldedSpan != null) {
reapplySpan(ss, mBoldedSpan, false);
}
reapplySpan(ss, this, true);
mBoldedSpan = this;
}
private void reapplySpan(Spannable spannable, WordSpan span, boolean isBold) {
int spanStart = spannable.getSpanStart(span);
int spanEnd = spannable.getSpanEnd(span);
span.setMarking(isBold);
spannable.setSpan(span, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}
};
ss.setSpan(wordSpan, spanStartEnd[i][0], spanStartEnd[i][1],
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}
mTextView.setText(ss, TextView.BufferType.SPANNABLE);
}
// All the other code follows without modification.
}
activity_main.xml
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.04000002"
tools:text="Hello World!" />
</android.support.constraint.ConstraintLayout>
Here is version that uses a StyleSpan. The results are the same.
MainActivity.java
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
private StyleSpan mBoldedSpan;
#Override
protected void onCreate(Bundle savedInstanceState) {
Typeface myFont = Typeface.DEFAULT;
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.textView);
mTextView.setTextIsSelectable(true);
mTextView.setMovementMethod(createMovementMethod(this));
mBoldedSpan = new StyleSpan(android.graphics.Typeface.BOLD);
SpannableString ss = new SpannableString("Hello world!");
int[][] spanStartEnd = new int[][]{{0, 5}, {6, 12}};
for (int i = 0; i < spanStartEnd.length; i++) {
WordSpan wordSpan = new WordSpan(i, myFont, false) {
#Override
public void onClick(View view) {
// handleWordClick(index, this); // Not sure what this does.
Spannable ss = (Spannable) mTextView.getText();
ss.setSpan(mBoldedSpan, ss.getSpanStart(this), ss.getSpanEnd(this),
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}
};
ss.setSpan(wordSpan, spanStartEnd[i][0], spanStartEnd[i][1],
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}
mTextView.setText(ss, TextView.BufferType.SPANNABLE);
}
// All the other code follows without modification.
}

Related

How to make a textview text link clickable

Android Studio 2.3.1
I am trying to create some text that is not web or html but just some normal text that I want to look like a web link that will be clickable when clicked.
The text is this: Contains 3 reviews
And I want to make it look like a clickable web link.
private void setupTextViewAsLinkClickable() {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
mTvReviews.setMovementMethod(LinkMovementMethod.getInstance());
mTvReviews.setText(Html.fromHtml("Contains 3 reviews", Html.FROM_HTML_MODE_LEGACY));
}
else {
mTvReviews.setMovementMethod(LinkMovementMethod.getInstance());
mTvReviews.setText(Html.fromHtml("Contains 3 reviews"));
}
}
I have also tried this as well for my xml:
<TextView
android:id="#+id/tvReviews"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-condensed"
android:autoLink="all"
android:linksClickable="true"
android:clickable="true"
android:textSize="#dimen/runtime_textsize"
android:text="Contains 3 reviews" />
try with this code, its working code in my project.
SpannableString ss = new SpannableString("Android is a Software stack");
ClickableSpan clickableSpan = new ClickableSpan() {
#Override
public void onClick(View textView) {
startActivity(new Intent(MyActivity.this, NextActivity.class));
}
#Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
}
};
ss.setSpan(clickableSpan, 22, 27, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
TextView textView = (TextView) findViewById(R.id.hello);
textView.setText(ss);
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setHighlightColor(Color.TRANSPARENT);
How to set the part of the text view is clickable
You can use this class to make a textview look like web url and even clickable
public final class LinkUtils {
public static final Pattern URL_PATTERN =
Pattern.compile("((https?|ftp)(:\\/\\/[-_.!~*\\'()a-zA-Z0-9;\\/?:\\#&=+\\$,%#]+))");
public interface OnClickListener {
void onLinkClicked(final String link);
void onClicked();
}
static class SensibleUrlSpan extends URLSpan {
/**
* Pattern to match.
*/
private Pattern mPattern;
public SensibleUrlSpan(String url, Pattern pattern) {
super(url);
mPattern = pattern;
}
public boolean onClickSpan(View widget) {
boolean matched = mPattern.matcher(getURL()).matches();
if (matched) {
super.onClick(widget);
}
return matched;
}
}
static class SensibleLinkMovementMethod extends LinkMovementMethod {
private boolean mLinkClicked;
private String mClickedLink;
#Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_UP) {
mLinkClicked = false;
mClickedLink = null;
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);
ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
SensibleUrlSpan span = (SensibleUrlSpan) link[0];
mLinkClicked = span.onClickSpan(widget);
mClickedLink = span.getURL();
return mLinkClicked;
}
}
super.onTouchEvent(widget, buffer, event);
return false;
}
public boolean isLinkClicked() {
return mLinkClicked;
}
public String getClickedLink() {
return mClickedLink;
}
}
public static void autoLink(final TextView view, final OnClickListener listener) {
autoLink(view, listener, null);
}
public static void autoLink(final TextView view, final OnClickListener listener,
final String patternStr) {
String text = view.getText().toString();
if (TextUtils.isEmpty(text)) {
return;
}
Spannable spannable = new SpannableString(text);
Pattern pattern;
if (TextUtils.isEmpty(patternStr)) {
pattern = URL_PATTERN;
} else {
pattern = Pattern.compile(patternStr);
}
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
SensibleUrlSpan urlSpan = new SensibleUrlSpan(matcher.group(1), pattern);
spannable.setSpan(urlSpan, matcher.start(1), matcher.end(1),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
view.setText(spannable, TextView.BufferType.SPANNABLE);
final SensibleLinkMovementMethod method = new SensibleLinkMovementMethod();
view.setMovementMethod(method);
if (listener != null) {
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (method.isLinkClicked()) {
listener.onLinkClicked(method.getClickedLink());
} else {
listener.onClicked();
}
}
});
}
}
}
And in activity call
String testStr = "Text 。http://www.yahoo.com , Text ";
textView1.setTextSize(20);
textView1.setText(testStr);
LinkUtils.autoLink(textView1, new LinkUtils.OnClickListener() {
#Override
public void onLinkClicked(final String link) {
Log.i("Log", "Log"+link);
}
#Override
public void onClicked() {
Log.i("Log", "Log");
}
});

Android ClickableSpan not changing style onclick

I have a TextView in which all words are individually clickable. I want to begin with every word unstyled. Upon clicking a word, the word should become and remain underlined. I am able to clear the default underline, but nothing happens upon click. (I am capturing and even processing the click, but I cannot get the Span style to change).
The relevant code is below. Thanks in advance for the help.
Custom ClickableSpan:
class WordSpan extends ClickableSpan {
private TextPaint textpaint;
public boolean clicked = false;
#Override
public void updateDrawState(TextPaint ds) {
textpaint = ds;
ds.setUnderlineText(false);
if (clicked)
ds.setUnderlineText(true);
}
#Override
public void onClick(View v) {}
public void setClicked(boolean c) {
clicked = c;
updateDrawState(textpaint);
}
}
From onCreate() I am parsing a txt file and adding each word to a TextView. Within this parsing loop I have the following code:
SpannableString ss = new SpannableString(word.toString());
WordSpan clickableSpan = new WordSpan() {
#Override
public void onClick(View view) {
setClicked(true);
view.invalidate();
}};
ss.setSpan(clickableSpan, 0, word.toString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tvText.append(ss);
tvText.append(" ");
}
tvText.setMovementMethod(LinkMovementMethod.getInstance());
To make individual word clickable you will have to add multiple clickable span to the spannable string. For example to make "foo" and "bar" individually clickable in single Textview you will have to add two clickable span, one for "foo" and other for "bar" and add them to spannable string.
In the example I have split the string using space for simplicity plus you would have to write logic for click of the span.
Clickable span which removes the underline. Additionally you can configure the background and text color on click. You can remove it if you are not going use it.
import android.text.TextPaint;
import android.text.style.ClickableSpan;
public abstract class TouchableSpan extends ClickableSpan {
private boolean mIsPressed;
private int mPressedBackgroundColor;
private int mNormalTextColor;
private int mPressedTextColor;
private int mBackgroundColor;
public TouchableSpan(int normalTextColor,int backgroundColor, int pressedTextColor, int pressedBackgroundColor) {
mBackgroundColor = backgroundColor;
mNormalTextColor = normalTextColor;
mPressedTextColor = pressedTextColor;
mPressedBackgroundColor = pressedBackgroundColor;
}
public void setPressed(boolean isSelected) {
mIsPressed = isSelected;
}
#Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(mIsPressed ? mPressedTextColor : mNormalTextColor);
ds.bgColor = mIsPressed ? mPressedBackgroundColor : mBackgroundColor;
ds.setUnderlineText(!mIsPressed);
}
}
Create a LinkMovementMethod which will take care of your Span. If you remove the color provision you can alter this as well
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.MotionEvent;
import android.widget.TextView;
public class LinkTouchMovementMethod extends LinkMovementMethod {
private TouchableSpan mPressedSpan;
#Override
public boolean onTouchEvent(TextView textView, Spannable spannable, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mPressedSpan = getPressedSpan(textView, spannable, event);
if (mPressedSpan != null) {
mPressedSpan.setPressed(true);
Selection.setSelection(spannable, spannable.getSpanStart(mPressedSpan),
spannable.getSpanEnd(mPressedSpan));
}
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
TouchableSpan touchedSpan = getPressedSpan(textView, spannable, event);
if (mPressedSpan != null && touchedSpan != mPressedSpan) {
mPressedSpan.setPressed(false);
mPressedSpan = null;
Selection.removeSelection(spannable);
}
} else {
if (mPressedSpan != null) {
mPressedSpan.setPressed(false);
super.onTouchEvent(textView, spannable, event);
}
mPressedSpan = null;
Selection.removeSelection(spannable);
}
return true;
}
private TouchableSpan getPressedSpan(TextView textView, Spannable spannable, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= textView.getTotalPaddingLeft();
y -= textView.getTotalPaddingTop();
x += textView.getScrollX();
y += textView.getScrollY();
Layout layout = textView.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
TouchableSpan[] link = spannable.getSpans(off, off, TouchableSpan.class);
TouchableSpan touchedSpan = null;
if (link.length > 0) {
touchedSpan = link[0];
}
return touchedSpan;
}
}
Then you can use it in the following way:
TextView textView = (TextView)findViewById(R.id.hello_world);
String fooBar = "asdfasdfasdfasf asdfasfasfasd";
String[] clickSpans = fooBar.split(" ");
int clickSpanLength = clickSpans.length;
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
int totalLength = 0;
int normalColor = getResources().getColor(android.R.color.black);
int clickColor = getResources().getColor(android.R.color.holo_blue_bright);
String separator = " , ";
int separatorLength = separator.length();
for (int i = 0; i < clickSpanLength; i++) {
int currentWordLength = clickSpans[i].length();
spannableStringBuilder.append(clickSpans[i]);
if (i < clickSpanLength - 1) {
spannableStringBuilder.append(" , ");
}
spannableStringBuilder.setSpan(new TouchableSpan(normalColor, Color.TRANSPARENT, clickColor, Color.TRANSPARENT) {
#Override
public void onClick(View widget) {
}
}, totalLength, totalLength + currentWordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
totalLength = totalLength + currentWordLength + separatorLength;
}
textView.setText(spannableStringBuilder);
textView.setMovementMethod(new LinkTouchMovementMethod());

Custom Emoji Keyboard in Android with Images

I want to create my own emoji keyboard in android. User should be able to select this keyboard as an input method for his android phone.
I tried creating it and I am able to use it in my app but I have no idea how to make this as an input method so this keyboard will be available to all other apps in my phone.
I read somewhere that I have to create a service for that so that it bind with input service.Other than that I am not able to understand rest of the thing.
Here is what I did. Though it is different from what I want to do but is start and don't know how to proceed further.
public class MainActivity extends FragmentActivity implements EmoticonsGridAdapter.KeyClickListener {
private static final int NO_OF_EMOTICONS = 100;
private ListView chatList;
private View popUpView;
private ArrayList<Spanned> chats;
private ChatListAdapter mAdapter;
private LinearLayout emoticonsCover;
private PopupWindow popupWindow;
private int keyboardHeight;
private EditText content;
private LinearLayout parentLayout;
private boolean isKeyBoardVisible;
private Bitmap[] emoticons;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
chatList = (ListView) findViewById(R.id.chat_list);
parentLayout = (LinearLayout) findViewById(R.id.list_parent);
emoticonsCover = (LinearLayout) findViewById(R.id.footer_for_emoticons);
popUpView = getLayoutInflater().inflate(R.layout.emoticons_popup, null);
// Setting adapter for chat list
chats = new ArrayList<Spanned>();
mAdapter = new ChatListAdapter(getApplicationContext(), chats);
chatList.setAdapter(mAdapter);
chatList.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (popupWindow.isShowing())
popupWindow.dismiss();
return false;
}
});
// Defining default height of keyboard which is equal to 230 dip
final float popUpheight = getResources().getDimension(
R.dimen.keyboard_height);
changeKeyboardHeight((int) popUpheight);
// Showing and Dismissing pop up on clicking emoticons button
ImageView emoticonsButton = (ImageView) findViewById(R.id.emoticons_button);
emoticonsButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (!popupWindow.isShowing()) {
popupWindow.setHeight((int) (keyboardHeight));
if (isKeyBoardVisible) {
emoticonsCover.setVisibility(LinearLayout.GONE);
} else {
emoticonsCover.setVisibility(LinearLayout.VISIBLE);
}
popupWindow.showAtLocation(parentLayout, Gravity.BOTTOM, 0, 0);
} else {
popupWindow.dismiss();
}
}
});
readEmoticons();
enablePopUpView();
checkKeyboardHeight(parentLayout);
enableFooterView();
}
/**
* Reading all emoticons in local cache
*/
private void readEmoticons () {
emoticons = new Bitmap[NO_OF_EMOTICONS];
for (short i = 0; i < NO_OF_EMOTICONS; i++) {
emoticons[i] = getImage((i+1) + ".png");
}
}
/**
* Enabling all content in footer i.e. post window
*/
private void enableFooterView() {
content = (EditText) findViewById(R.id.chat_content);
content.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (popupWindow.isShowing()) {
popupWindow.dismiss();
}
}
});
final Button postButton = (Button) findViewById(R.id.post_button);
postButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (content.getText().toString().length() > 0) {
Spanned sp = content.getText();
chats.add(sp);
content.setText("");
mAdapter.notifyDataSetChanged();
}
}
});
}
/**
* Overriding onKeyDown for dismissing keyboard on key down
*/
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (popupWindow.isShowing()) {
popupWindow.dismiss();
return false;
} else {
return super.onKeyDown(keyCode, event);
}
}
/**
* Checking keyboard height and keyboard visibility
*/
int previousHeightDiffrence = 0;
private void checkKeyboardHeight(final View parentLayout) {
parentLayout.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
parentLayout.getWindowVisibleDisplayFrame(r);
int screenHeight = parentLayout.getRootView()
.getHeight();
int heightDifference = screenHeight - (r.bottom);
if (previousHeightDiffrence - heightDifference > 50) {
popupWindow.dismiss();
}
previousHeightDiffrence = heightDifference;
if (heightDifference > 100) {
isKeyBoardVisible = true;
changeKeyboardHeight(heightDifference);
} else {
isKeyBoardVisible = false;
}
}
});
}
/**
* change height of emoticons keyboard according to height of actual
* keyboard
*
* #param height
* minimum height by which we can make sure actual keyboard is
* open or not
*/
private void changeKeyboardHeight(int height) {
if (height > 100) {
keyboardHeight = height;
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, keyboardHeight);
emoticonsCover.setLayoutParams(params);
}
}
/**
* Defining all components of emoticons keyboard
*/
private void enablePopUpView() {
ViewPager pager = (ViewPager) popUpView.findViewById(R.id.emoticons_pager);
pager.setOffscreenPageLimit(3);
pager.setBackgroundColor(Color.WHITE);
ArrayList<String> paths = new ArrayList<String>();
for (short i = 1; i <= NO_OF_EMOTICONS; i++) {
paths.add(i + ".png");
}
EmoticonsPagerAdapter adapter = new EmoticonsPagerAdapter(MainActivity.this, paths, this);
pager.setAdapter(adapter);
// Creating a pop window for emoticons keyboard
popupWindow = new PopupWindow(popUpView, LayoutParams.MATCH_PARENT,
(int) keyboardHeight, false);
/*TextView backSpace = (TextView) popUpView.findViewById(R.id.back);
backSpace.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
KeyEvent event = new KeyEvent(0, 0, 0, KeyEvent.KEYCODE_DEL, 0, 0, 0, 0, KeyEvent.KEYCODE_ENDCALL);
content.dispatchKeyEvent(event);
}
});*/
popupWindow.setOnDismissListener(new OnDismissListener() {
#Override
public void onDismiss() {
emoticonsCover.setVisibility(LinearLayout.GONE);
}
});
}
/**
* For loading smileys from assets
*/
private Bitmap getImage(String path) {
AssetManager mngr = getAssets();
InputStream in = null;
try {
in = mngr.open("emoticons/" + path);
} catch (Exception e) {
e.printStackTrace();
}
Bitmap temp = BitmapFactory.decodeStream(in, null, null);
return temp;
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
#Override
public void keyClickedIndex(final String index) {
ImageGetter imageGetter = new ImageGetter() {
public Drawable getDrawable(String source) {
StringTokenizer st = new StringTokenizer(index, ".");
Drawable d = new BitmapDrawable(getResources(),emoticons[Integer.parseInt(st.nextToken()) - 1]);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
return d;
}
};
Spanned cs = Html.fromHtml("<img src ='"+ index +"'/>", imageGetter, null);
int cursorPosition = content.getSelectionStart();
content.getText().insert(cursorPosition, cs);
}
}
EDIT:
This is the code for custom keyboard that I have implemented but I am unable to find how to add emoji to that keyboard.
public class SimpleIME extends InputMethodService
implements KeyboardView.OnKeyboardActionListener {
private KeyboardView kv;
private Keyboard keyboard;
private View popUpView;
private boolean caps = false;
#Override
public View onCreateInputView() {
kv = (KeyboardView)getLayoutInflater().inflate(R.layout.keyboard, null);
keyboard = new Keyboard(this, R.xml.qwerty);
kv.setKeyboard(keyboard);
kv.setOnKeyboardActionListener(this);
kv.invalidateAllKeys();
popUpView = getLayoutInflater().inflate(R.layout.emoticons_popup, null);
return kv;
}
#Override
public void onKey(int primaryCode, int[] keyCodes) {
InputConnection ic = getCurrentInputConnection();
playClick(primaryCode);
switch(primaryCode){
case Keyboard.KEYCODE_DELETE :
ic.deleteSurroundingText(1, 0);
break;
case Keyboard.KEYCODE_SHIFT:
caps = !caps;
keyboard.setShifted(caps);
kv.invalidateAllKeys();
break;
case Keyboard.KEYCODE_DONE:
ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
break;
case -80 :
Log.d("smiley", "smiley pressed");
break;
default:
char code = (char)primaryCode;
if(Character.isLetter(code) && caps){
code = Character.toUpperCase(code);
}
ic.commitText(String.valueOf(code),1);
}
}
private void playClick(int keyCode){
AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE);
switch(keyCode){
case 32:
am.playSoundEffect(AudioManager.FX_KEYPRESS_SPACEBAR);
break;
case Keyboard.KEYCODE_DONE:
case 10:
am.playSoundEffect(AudioManager.FX_KEYPRESS_RETURN);
break;
case Keyboard.KEYCODE_DELETE:
am.playSoundEffect(AudioManager.FX_KEYPRESS_DELETE);
break;
default: am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD);
}
}
#Override
public void onPress(int primaryCode) {
}
#Override
public void onRelease(int primaryCode) {
}
#Override
public void onText(CharSequence text) {
}
#Override
public void swipeDown() {
}
#Override
public void swipeLeft() {
}
#Override
public void swipeRight() {
}
#Override
public void swipeUp() {
}
}
EDIT:
Can we copy an image from images list and paste it where keyboard is open??
The best implementation for an emoji keyboard I found was that of sliding emoji-Keyboard
It's a really good implementation, maybe with some redundant code, but still really good for understanding how to implement keyboards that do not fit the normal "button-to-text" keyboards.
UPDATE
Okay, I have now been able to successfully able to integrate the sliding emoji-keyboard into my own project 8Vim after a lot of re-factoring in both of the projects.
Essentially, all you are doing for the emoji keyboard is to create a view of the size of the keyboard and then populating that view with PNG files corresponding to the emoji's. each image acts like a button and delivers the appropriate emoji to the inputConnection.
UPDATE 2
I have extended the sliding emoji-keyboard and created a much cleaner version that should be easier to understand. Take a look at my emoji-keyboard

Detect if TextVIew is ellipsized before layout is shown

I have a TextView with maximun 3 lines and a "Show more" button below it. The logic is that if the text in the TextView can fit inside it, the "Show more" button is hidden; otherwise if the text cannot fit in 3 lines, the "Show more" is shown.
My way (which is not working) is that: detect if the TextView is ellipsized by using textView.getLayout().getEllipsisCount(maxNumberOfline) then hide or show the "Show more" button. But the call textView.getLayout() return null when the layout is not finish yet. I tried to put textView.getLayout().getEllipsisCount(maxNumberOfline) in onStart() and onResume() but no luck.
Does anyone have another way to do this?
Try this way,hope this will help you to solve your problem.
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textview = (TextView) findViewById(R.id.textview);
textview.setText("demotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotextdemotext");
TextViewResizable(textview,3,"See More");
}
public void TextViewResizable(final TextView tv,final int maxLine, final String expandText) {
if (tv.getTag() == null) {
tv.setTag(tv.getText());
}
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
ViewTreeObserver obs = tv.getViewTreeObserver();
obs.removeGlobalOnLayoutListener(this);
if (maxLine <= 0) {
int lineEndIndex = tv.getLayout().getLineEnd(0);
String text = tv.getText().subSequence(0,lineEndIndex - expandText.length() + 1)+ " " + expandText;
tv.setText(text);
tv.setMovementMethod(LinkMovementMethod.getInstance());
tv.setText(addClickablePartTextViewResizable(Html.fromHtml(tv.getText().toString()), tv, expandText), TextView.BufferType.SPANNABLE);
} else if (tv.getLineCount() >= maxLine) {
int lineEndIndex = tv.getLayout().getLineEnd(maxLine - 1);
String text = tv.getText().subSequence(0,lineEndIndex - expandText.length() + 1)+ " " + expandText;
tv.setText(text);
tv.setMovementMethod(LinkMovementMethod.getInstance());
tv.setText(addClickablePartTextViewResizable(Html.fromHtml(tv.getText().toString()), tv, expandText), TextView.BufferType.SPANNABLE);
}
}
});
}
private SpannableStringBuilder addClickablePartTextViewResizable(final Spanned strSpanned, final TextView tv,final String expandText) {
String str = strSpanned.toString();
SpannableStringBuilder ssb = new SpannableStringBuilder(strSpanned);
if (str.contains(expandText)) {
ssb.setSpan(new Spannable(Color.BLUE, true) {
#Override
public void onClick(View widget) {
tv.setLayoutParams(tv.getLayoutParams());
tv.setText(tv.getTag().toString(),TextView.BufferType.SPANNABLE);
tv.invalidate();
}
}, str.indexOf(expandText), str.indexOf(expandText)+ expandText.length(), 0);
}
return ssb;
}
class Spannable extends ClickableSpan {
private int color = -1;
private float fontSize = -1;
private boolean isUnderline = true;
/**
* Constructor
*/
public Spannable() {
}
/**
* Constructor
*/
public Spannable(int color) {
this.color = color;
}
/**
* Constructor
*/
public Spannable(float fontSize) {
this.fontSize = fontSize;
}
/**
* Constructor
*/
public Spannable(boolean isUnderline) {
this.isUnderline = isUnderline;
}
/**
* Constructor
*/
public Spannable(int color, boolean isUnderline) {
this.isUnderline = isUnderline;
this.color = color;
}
/**
* Constructor
*/
public Spannable(int color, float fontSize) {
this.color = color;
this.fontSize = fontSize;
}
/**
* Overrides methods
*/
#Override
public void updateDrawState(TextPaint ds) {
if (color != -1) {
ds.setColor(color);
}
if (fontSize > 0) {
ds.setTextSize(fontSize);
}
ds.setUnderlineText(isUnderline);
}
#Override
public void onClick(View widget) {
}
}
I also had this kind of problem, the following code actually fixed it:
mTextView.getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
showHideMoreButton(mTextView);
}
});
public void showHideMoreButton(TextView mTextView) {
Layout layout = mTextView.getLayout();
if (layout != null) {
int lines = layout.getLineCount();
if (lines > 0) {
int ellipsisCount = layout.getEllipsisCount(lines - 1);
if (ellipsisCount > 0) {
mShowMoreButton.setVisibility(View.VISIBLE);
}
}
}
}
until I noticed that it's not working in OS version 2.3.5. Even though the mTextView.getLineCount() is returning the correct number of lines, the layout.getEllipsisCount(lines - 1) never returns number which is greater than 0. What happened next is that the "Show More" button never appears although the TextView has already been truncated at the end. Then I realized that the implementation can be changed to the following. It's working now.
public void showHideMoreButton(TextView mTextView) {
int lines = mTextView.getLineCount();
if (lines > 2) {
mShowMoreButton.setVisibility(View.VISIBLE);
mTextView.setSingleLine(false);
mTextView.setEllipsize(TextUtils.TruncateAt.END);
mTextView.setLines(2); //no. of lines you want your textview to display
}
}

ClickableSpan with custom background Color State List (pressed state) [duplicate]

I have a TextView with multiple ClickableSpans in it. When a ClickableSpan is pressed, I want it to change the color of its text.
I have tried setting a color state list as the textColorLink attribute of the TextView. This does not yield the desired result because this causes all the spans to change color when the user clicks anywhere on the TextView.
Interestingly, using textColorHighlight to change the background color works as expected: Clicking on a span changes only the background color of that span and clicking anywhere else in the TextView does nothing.
I have also tried setting ForegroundColorSpans with the same boundaries as the ClickableSpans where I pass the same color state list as above as the color resource. This doesn't work either. The spans always keep the color of the default state in the color state list and never enter the pressed state.
Does anyone know how to do this?
This is the color state list I used:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#color/pressed_color"/>
<item android:color="#color/normal_color"/>
</selector>
I finally found a solution that does everything I wanted. It is based on this answer.
This is my modified LinkMovementMethod that marks a span as pressed on the start of a touch event (MotionEvent.ACTION_DOWN) and unmarks it when the touch ends or when the touch location moves out of the span.
public class LinkTouchMovementMethod extends LinkMovementMethod {
private TouchableSpan mPressedSpan;
#Override
public boolean onTouchEvent(TextView textView, Spannable spannable, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mPressedSpan = getPressedSpan(textView, spannable, event);
if (mPressedSpan != null) {
mPressedSpan.setPressed(true);
Selection.setSelection(spannable, spannable.getSpanStart(mPressedSpan),
spannable.getSpanEnd(mPressedSpan));
}
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
TouchableSpan touchedSpan = getPressedSpan(textView, spannable, event);
if (mPressedSpan != null && touchedSpan != mPressedSpan) {
mPressedSpan.setPressed(false);
mPressedSpan = null;
Selection.removeSelection(spannable);
}
} else {
if (mPressedSpan != null) {
mPressedSpan.setPressed(false);
super.onTouchEvent(textView, spannable, event);
}
mPressedSpan = null;
Selection.removeSelection(spannable);
}
return true;
}
private TouchableSpan getPressedSpan(
TextView textView,
Spannable spannable,
MotionEvent event) {
int x = (int) event.getX() - textView.getTotalPaddingLeft() + textView.getScrollX();
int y = (int) event.getY() - textView.getTotalPaddingTop() + textView.getScrollY();
Layout layout = textView.getLayout();
int position = layout.getOffsetForHorizontal(layout.getLineForVertical(y), x);
TouchableSpan[] link = spannable.getSpans(position, position, TouchableSpan.class);
TouchableSpan touchedSpan = null;
if (link.length > 0 && positionWithinTag(position, spannable, link[0])) {
touchedSpan = link[0];
}
return touchedSpan;
}
private boolean positionWithinTag(int position, Spannable spannable, Object tag) {
return position >= spannable.getSpanStart(tag) && position <= spannable.getSpanEnd(tag);
}
}
This needs to be applied to the TextView like so:
yourTextView.setMovementMethod(new LinkTouchMovementMethod());
And this is the modified ClickableSpan that edits the draw state based on the pressed state set by the LinkTouchMovementMethod: (it also removes the underline from the links)
public abstract class TouchableSpan extends ClickableSpan {
private boolean mIsPressed;
private int mPressedBackgroundColor;
private int mNormalTextColor;
private int mPressedTextColor;
public TouchableSpan(int normalTextColor, int pressedTextColor, int pressedBackgroundColor) {
mNormalTextColor = normalTextColor;
mPressedTextColor = pressedTextColor;
mPressedBackgroundColor = pressedBackgroundColor;
}
public void setPressed(boolean isSelected) {
mIsPressed = isSelected;
}
#Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(mIsPressed ? mPressedTextColor : mNormalTextColor);
ds.bgColor = mIsPressed ? mPressedBackgroundColor : 0xffeeeeee;
ds.setUnderlineText(false);
}
}
Much simpler solution, IMO:
final int colorForThisClickableSpan = Color.RED; //Set your own conditional logic here.
final ClickableSpan link = new ClickableSpan() {
#Override
public void onClick(final View view) {
//Do something here!
}
#Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(colorForThisClickableSpan);
}
};
All these solutions are too much work.
Just set android:textColorLink in your TextView to some selector. Then create a clickableSpan with no need to override updateDrawState(...). All done.
here a quick example:
In your strings.xml have a declared string like this:
<string name="mystring">This is my message%1$s these words are highlighted%2$s and awesome. </string>
then in your activity:
private void createMySpan(){
final String token = "#";
String myString = getString(R.string.mystring,token,token);
int start = myString.toString().indexOf(token);
//we do -1 since we are about to remove the tokens afterwards so it shifts
int finish = myString.toString().indexOf(token, start+1)-1;
myString = myString.replaceAll(token, "");
//create your spannable
final SpannableString spannable = new SpannableString(myString);
final ClickableSpan clickableSpan = new ClickableSpan() {
#Override
public void onClick(final View view) {
doSomethingOnClick();
}
};
spannable.setSpan(clickableSpan, start, finish, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mTextView.setMovementMethod(LinkMovementMethod.getInstance());
mTextView.setText(spannable);
}
and heres the important parts ..declare a selector like this calling it myselector.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#color/gold"/>
<item android:color="#color/pink"/>
</selector>
And last in your TextView in xml do this:
<TextView
android:id="#+id/mytextview"
android:background="#android:color/transparent"
android:text="#string/mystring"
android:textColorLink="#drawable/myselector" />
Now you can have a pressed state on your clickableSpan.
legr3c's answer helped me a lot. And I'd like to add a few remarks.
Remark #1.
TextView myTextView = (TextView) findViewById(R.id.my_textview);
myTextView.setMovementMethod(new LinkTouchMovementMethod());
myTextView.setHighlightColor(getResources().getColor(android.R.color.transparent));
SpannableString mySpannable = new SpannableString(text);
mySpannable.setSpan(new TouchableSpan(), 0, 7, 0);
mySpannable.setSpan(new TouchableSpan(), 15, 18, 0);
myTextView.setText(mySpannable, BufferType.SPANNABLE);
I applied LinkTouchMovementMethod to a TextView with two spans. The spans were highlighted with blue when clicked them.
myTextView.setHighlightColor(getResources().getColor(android.R.color.transparent));
fixed the bug.
Remark #2.
Don't forget to get colors from resources when passing normalTextColor, pressedTextColor, and pressedBackgroundColor.
Should pass resolved color instead of resource id here
try this custom ClickableSpan:
class MyClickableSpan extends ClickableSpan {
private String action;
private int fg;
private int bg;
private boolean selected;
public MyClickableSpan(String action, int fg, int bg) {
this.action = action;
this.fg = fg;
this.bg = bg;
}
#Override
public void onClick(View widget) {
Log.d(TAG, "onClick " + action);
}
#Override
public void updateDrawState(TextPaint ds) {
ds.linkColor = selected? fg : 0xffeeeeee;
super.updateDrawState(ds);
}
}
and this SpanWatcher:
class Watcher implements SpanWatcher {
private TextView tv;
private MyClickableSpan selectedSpan = null;
public Watcher(TextView tv) {
this.tv = tv;
}
private void changeColor(Spannable text, Object what, int start, int end) {
// Log.d(TAG, "changeFgColor " + what);
if (what == Selection.SELECTION_END) {
MyClickableSpan[] spans = text.getSpans(start, end, MyClickableSpan.class);
if (spans != null) {
tv.setHighlightColor(spans[0].bg);
if (selectedSpan != null) {
selectedSpan.selected = false;
}
selectedSpan = spans[0];
selectedSpan.selected = true;
}
}
}
#Override
public void onSpanAdded(Spannable text, Object what, int start, int end) {
changeColor(text, what, start, end);
}
#Override
public void onSpanChanged(Spannable text, Object what, int ostart, int oend, int nstart, int nend) {
changeColor(text, what, nstart, nend);
}
#Override
public void onSpanRemoved(Spannable text, Object what, int start, int end) {
}
}
test it in onCreate:
TextView tv = new TextView(this);
tv.setTextSize(40);
tv.setMovementMethod(LinkMovementMethod.getInstance());
SpannableStringBuilder b = new SpannableStringBuilder();
b.setSpan(new Watcher(tv), 0, 0, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
b.append("this is ");
int start = b.length();
MyClickableSpan link = new MyClickableSpan("link0 action", 0xffff0000, 0x88ff0000);
b.append("link 0");
b.setSpan(link, start, b.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
b.append("\nthis is ");
start = b.length();
b.append("link 1");
link = new MyClickableSpan("link1 action", 0xff00ff00, 0x8800ff00);
b.setSpan(link, start, b.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
b.append("\nthis is ");
start = b.length();
b.append("link 2");
link = new MyClickableSpan("link2 action", 0xff0000ff, 0x880000ff);
b.setSpan(link, start, b.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(b);
setContentView(tv);
This is my solution if you got many click elements (we need an interface):
The Interface:
public interface IClickSpannableListener{
void onClickSpannText(String text,int starts,int ends);
}
The class who manage the event:
public class SpecialClickableSpan extends ClickableSpan{
private IClickSpannableListener listener;
private String text;
private int starts, ends;
public SpecialClickableSpan(String text,IClickSpannableListener who,int starts, int ends){
super();
this.text = text;
this.starts=starts;
this.ends=ends;
listener = who;
}
#Override
public void onClick(View widget) {
listener.onClickSpannText(text,starts,ends);
}
}
In main class:
class Main extends Activity implements IClickSpannableListener{
//Global
SpannableString _spannableString;
Object _backGroundColorSpan=new BackgroundColorSpan(Color.BLUE);
private void setTextViewSpannable(){
_spannableString= new SpannableString("You can click «here» or click «in this position»");
_spannableString.setSpan(new SpecialClickableSpan("here",this,15,18),15,19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
_spannableString.setSpan(new SpecialClickableSpan("in this position",this,70,86),70,86, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
TextView tv = (TextView)findViewBy(R.id.textView1);
tv.setMovementMethod(LinkMovementMethod.getInstance());
tv.setText(spannableString);
}
#Override
public void onClickSpannText(String text, int inicio, int fin) {
System.out.println("click on "+ text);
_spannableString.removeSpan(_backGroundColorSpan);
_spannableString.setSpan(_backGroundColorSpan, inicio, fin, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
((TextView)findViewById(R.id.textView1)).setText(_spannableString);
}
}
King regards to the Steven's answer, if someone needs, here it's implementation in Kotlin:
abstract class TouchableSpan(
private val mNormalTextColor: Int,
private val mPressedTextColor: Int
) : ClickableSpan() {
private var isPressed = false
fun setPressed(isSelected: Boolean) {
isPressed = isSelected
}
override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
ds.color = if (isPressed) mPressedTextColor else mNormalTextColor
ds.isUnderlineText = false
}
}
class LinkTouchMovementMethod : LinkMovementMethod() {
private var pressedSpan: TouchableSpan? = null
override fun onTouchEvent(
textView: TextView,
spannable: Spannable,
event: MotionEvent
): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
pressedSpan = getPressedSpan(textView, spannable, event)
pressedSpan?.setPressed(true)
Selection.setSelection(
spannable, spannable.getSpanStart(pressedSpan),
spannable.getSpanEnd(pressedSpan)
)
} else if (event.action == MotionEvent.ACTION_MOVE) {
val touchedSpan = getPressedSpan(textView, spannable, event)
if (touchedSpan !== pressedSpan) {
pressedSpan?.setPressed(false)
pressedSpan = null
Selection.removeSelection(spannable)
}
} else {
pressedSpan?.setPressed(false)
super.onTouchEvent(textView, spannable, event)
pressedSpan = null
Selection.removeSelection(spannable)
}
return true
}
private fun getPressedSpan(
textView: TextView,
spannable: Spannable,
event: MotionEvent
): TouchableSpan? {
val x = event.x.toInt() - textView.totalPaddingLeft + textView.scrollX
val y = event.y.toInt() - textView.totalPaddingTop + textView.scrollY
val layout = textView.layout
val position = layout.getOffsetForHorizontal(layout.getLineForVertical(y), x.toFloat())
val link = spannable.getSpans(position, position, TouchableSpan::class.java)
if (link.isNotEmpty() && positionWithinTag(position, spannable, link[0])) {
return link[0]
}
return null
}
private fun positionWithinTag(position: Int, spannable: Spannable, tag: Any) =
position >= spannable.getSpanStart(tag)
&& position <= spannable.getSpanEnd(tag)
companion object {
val instance by lazy {
LinkTouchMovementMethod()
}
}
}
Place the java code as below :
package com.synamegames.orbs;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
public class CustomTouchListener implements View.OnTouchListener {
public boolean onTouch(View view, MotionEvent motionEvent) {
switch(motionEvent.getAction()){
case MotionEvent.ACTION_DOWN:
((TextView) view).setTextColor(0x4F4F4F);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
((TextView) view).setTextColor(0xCDCDCD);
break;
}
return false;
}
}
In the above code specify wat color you want .
Change the style .xml as you want.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MenuFont">
<item name="android:textSize">20sp</item>
<item name="android:textColor">#CDCDCD</item>
<item name="android:textStyle">normal</item>
<item name="android:clickable">true</item>
<item name="android:layout_weight">1</item>
<item name="android:gravity">left|center</item>
<item name="android:paddingLeft">35dp</item>
<item name="android:layout_width">175dp</item>
<item name="android:layout_height">fill_parent</item>
</style>
Try it out and say is this you want or something else . update me dude.

Categories

Resources