I have three buttons and hichever button is selected first is marked with an X whilst the second button selected will be marked with an O and the last button is marked as X again.
If an X is marked then its colour is white, if it's a O then it is marked grey.
Now what I want to do is use the saved instance so that when I rotate the phone, the colours stay the same as they were. What is actually happening is that if I rotate the phone then which ever latest selection was made, all the text reverts to that colour.
So if I press for X and the O and rotate the phone, both X and O will be displayed as the grey colour which is O's colours.
If I then select the last X and rotate the phone, all the letters will be marked as a white colour which is X's colour.
I am unsure if it's the set colour that is causing it or that is remembers who's move it was previously and sets the colour according to that, my question is how to solve it so that all the letters keep their colour on rotation?
private boolean playerOneMove = true;
private Button[][] buttons = new Button[1][3];
private static final String TEXT_COLOR = "textColor";
private String textColor;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_player2);
btnObj1 = findViewById(R.id.button_00);
btnObj2 = findViewById(R.id.button_01);
btnObj3 = findViewById(R.id.button_02);
if (savedInstanceState != null) {
textColor = savedInstanceState.getString(TEXT_COLOR);
if(btnObj1 != null) {
btnObj1.setTextColor(Color.parseColor(textColor));
}
if (btnObj2 != null) {
btnObj2.setTextColor(Color.parseColor(textColor));
}
if (btnObj3 != null) {
btnObj3.setTextColor(Color.parseColor(textColor));
}
for (int i = 0; i < 1; i++) {
for (int j = 0; j < 3; j++) {
String buttonID = "button_" + i + j;
int resID = getResources().getIdentifier(buttonID, "id", getPackageName());
buttons[i][j] = findViewById(resID);
buttons[i][j].setOnClickListener(this);
}
}
}
#Override
public void onClick(View v) {
if (!((Button) v).getText().toString().equals("")) {
return;
}
if (playerOneMove) {
((Button) v).setText("X");
textColor = "#e8e5e5";
((Button) v).setTextColor(Color.parseColor(textColor));
} else {
((Button) v).setText("O");
textColor = "#737374";
((Button) v).setTextColor(Color.parseColor(textColor));
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("playerOneMove", playerOneMove);
outState.putString(TEXT_COLOR, textColor);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) { ;
super.onRestoreInstanceState(savedInstanceState);
playerOneMove = savedInstanceState.getBoolean("playerOneMove");
textColor = savedInstanceState.getString(TEXT_COLOR);
}
The behavior you see it's normal because you save a single textColor which will always be the one which was last set by the user. Instead you could simply iterate over the buttons array and save each button's text color:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
for (int i = 0; i < 1; i++) {
for (int j = 0; j < 3; j++) {
String buttonID = "button_" + i + j;
Button btn = buttons[i][j];
outState.putCharSequence(buttonID, btn.getText());
}
}
}
Then in onCreate()(remove the onRestoreInstanceState() method) restore the button's state:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_player2);
int playerX = Color.parseColor("#e8e5e5");
int playerO = Color.parseColor("#737374");
for (int i = 0; i < 1; i++) {
for (int j = 0; j < 3; j++) {
String buttonID = "button_" + i + j;
int resID = getResources().getIdentifier(buttonID, "id", getPackageName());
buttons[i][j] = findViewById(resID);
buttons[i][j].setOnClickListener(this);
if (savedInstanceState != null) {
String btnState = savedInstanceState.getCharSequence(buttonID);
if (btnState.equals("X")) {
// this is player X
buttons[i][j].setTextColor(playerX);
} else if (btnState.equals("O")) {
// this is player O
buttons[i][j].setTextColor(playerO);
} else {
// unclicked btn, do you have another color?
}
}
}
}
If you have a greater number of buttons, it may make more sense to group the buttons statuses in a list and save that instead of the individual buttons states.
It's look like a common question, but I didn't find the answer yet after a long search.
My problem is that I tried to receive data from a web service and after I get all the data I need to update the UI with these data.
The difference from what I found is that also the UI takes time (about 5 sec), so in all examples I saw, the data received in doInBackground() and all the UI work done in onPostExecute() and before the UI work done the ProgressDialog is dismissed so it is look like it worked(the ProgressDialog doesn't stuck) but after it disappear I wait more 5 second to the UI appear.
This is part of may code that I tried:
private class RequestResponse extends AsyncTask<String,String,String>
{
private ProgressDialog dialog = new ProgressDialog(MainActivity.this);
protected String doInBackground(String... uri)
{
String response="";
HttpClient client = new DefaultHttpClient();
HttpResponse httpResponse;
InputStream content;
BufferedReader buffer;
HttpGet request = new HttpGet(uri[0]);
try
{
httpResponse = client.execute(request);
content = httpResponse.getEntity().getContent();
buffer = new BufferedReader(new InputStreamReader(content));
String s = "";
while ((s = buffer.readLine()) != null)
{
response += s;
}
ProcessResponse(response);//Received data from server
return response;
}
catch (Exception e)
{
response = e.getMessage();
return response;
}
}
#SuppressWarnings("unused")
protected void onProgressUpdate(Integer... progress) {}
protected void onPreExecute()
{
super.onPreExecute();
this.dialog = new ProgressDialog(MainActivity.this);
this.dialog.setMessage("Processing...");
this.dialog.setCancelable(false);
this.dialog.isIndeterminate();
this.dialog.setInverseBackgroundForced(true);
this.dialog.show();
btnSearch.setEnabled(false);
txtSearch.setEnabled(false);
isFirstViewClick=false;
isSecondViewClick=false;
isThirdViewClick=false;
}
protected void onPostExecute(String result)
{
this.dialog.dismiss();
AddDataToView();// Do UI work
btnSearch.setEnabled(true);
txtSearch.setEnabled(true);
}
}
I tried also using Progressbar instead of ProgressDialog but the results are the same.
Update:
This is the code for Update UI, I'll the most of the code from this web
but I added 1 more level. in the site there is no function for this use the just do it in onCreate().
private void AddDataToView()
{
//Adds data into first row
for (int i = 0; i < mainList.size(); i++)
{
LayoutInflater inflater = null;
inflater = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View mLinearView = inflater.inflate(R.layout.row_first, null);
final TextView mProductName = (TextView) mLinearView.findViewById(R.id.textViewName);
final RelativeLayout mLinearFirstArrow=(RelativeLayout)mLinearView.findViewById(R.id.linearFirst);
final ImageView m
ImageArrowFirst=(ImageView)mLinearView.findViewById(R.id.imageFirstArrow);
final LinearLayout mLinearScrollSecond=(LinearLayout)mLinearView.findViewById(R.id.linear_scroll);
//checkes if menu is already opened or not
if(isFirstViewClick==false)
{
mLinearScrollSecond.setVisibility(View.GONE);
mImageArrowFirst.setBackgroundResource(R.drawable.arw_lt);
}
else
{
mLinearScrollSecond.setVisibility(View.VISIBLE);
mImageArrowFirst.setBackgroundResource(R.drawable.arw_down);
}
//Handles onclick effect on list item
mLinearFirstArrow.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.v("Event", "MotionEvent: " + event.getAction());
if(event.getAction() == MotionEvent.ACTION_UP)
{
if(isFirstViewClick==false)
{
isFirstViewClick=true;
mImageArrowFirst.setBackgroundResource(R.drawable.arw_down);
mLinearScrollSecond.setVisibility(View.VISIBLE);
}
else
{
isFirstViewClick=false;
mImageArrowFirst.setBackgroundResource(R.drawable.arw_lt);
mLinearScrollSecond.setVisibility(View.GONE);
}
}
return true;
}
});
final String name = mainList.get(i).getpName();
mProductName.setText(name);
//Adds data into second row
for (int j = 0; j < mainList.get(i).getmRevisionsList().size(); j++)
{
LayoutInflater inflaterSecond = null;
inflaterSecond = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View mLinearViewSecond = inflaterSecond.inflate(R.layout.row_second, null);
TextView mSubItemNameSecond = (TextView) mLinearViewSecond.findViewById(R.id.textViewTitle);
final RelativeLayout mLinearSecondArrow=(RelativeLayout)mLinearViewSecond.findViewById(R.id.linearSecond);
final ImageView mImageArrowSecond=(ImageView)mLinearViewSecond.findViewById(R.id.imageSecondArrow);
final LinearLayout mLinearScrollThird=(LinearLayout)mLinearViewSecond.findViewById(R.id.linear_scroll_third);
//checkes if menu is already opened or not
if(isSecondViewClick==false)
{
mLinearScrollThird.setVisibility(View.GONE);
mImageArrowSecond.setBackgroundResource(R.drawable.arw_lt);
}
else
{
mLinearScrollThird.setVisibility(View.VISIBLE);
mImageArrowSecond.setBackgroundResource(R.drawable.arw_down);
}
//Handles onclick effect on list item
mLinearSecondArrow.setOnTouchListener(new OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
Log.v("Event", "MotionEvent: " + event.getAction());
if(event.getAction() == MotionEvent.ACTION_UP)
{
if(isSecondViewClick==false)
{
isSecondViewClick=true;
mImageArrowSecond.setBackgroundResource(R.drawable.arw_down);
mLinearScrollThird.setVisibility(View.VISIBLE);
}
else
{
isSecondViewClick=false;
mImageArrowSecond.setBackgroundResource(R.drawable.arw_lt);
mLinearScrollThird.setVisibility(View.GONE);
}
}
return true;
}
});
final String RevNum= mainList.get(i).getmRevisionsList().get(j).getpRevisionNumber();
mSubItemNameSecond.setText(RevNum);
//Adds data into Third row
for (int k = 0; k < mainList.get(i).getmRevisionsList().get(j).getmDefinitionsList().size(); k++)
{
LayoutInflater inflaterThird = null;
inflaterThird = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View mLinearViewThird = inflaterThird.inflate(R.layout.row_third, null);
TextView mSubItemNameThird = (TextView) mLinearViewThird.findViewById(R.id.textViewTitle);
final RelativeLayout mLinearThirdArrow=(RelativeLayout)mLinearViewThird.findViewById(R.id.linearThird);
final ImageView mImageArrowThird =(ImageView)mLinearViewThird.findViewById(R.id.imageThirdArrow);
final LinearLayout mLinearScrollFourth=(LinearLayout)mLinearViewThird.findViewById(R.id.linear_scroll_fourth);
//checkes if menu is already opened or not
if(isThirdViewClick==false)
{
mLinearScrollFourth.setVisibility(View.GONE);
mImageArrowThird.setBackgroundResource(R.drawable.arw_lt);
}
else
{
mLinearScrollFourth.setVisibility(View.VISIBLE);
mImageArrowThird.setBackgroundResource(R.drawable.arw_down);
}
//Handles onclick effect on list item
mLinearThirdArrow.setOnTouchListener(new OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
Log.v("Event", "MotionEvent: " + event.getAction());
if(event.getAction() == MotionEvent.ACTION_UP)
{
if(isThirdViewClick==false)
{
isThirdViewClick=true;
mImageArrowThird.setBackgroundResource(R.drawable.arw_down);
mLinearScrollFourth.setVisibility(View.VISIBLE);
}
else
{
isThirdViewClick=false;
mImageArrowThird.setBackgroundResource(R.drawable.arw_lt);
mLinearScrollFourth.setVisibility(View.GONE);
}
}
return true;
}
});
final String DefName= mainList.get(i).getmRevisionsList().get(j).getmDefinitionsList().get(k).getpDefinitionName();
mSubItemNameThird.setText(DefName);
//Adds items in subcategories
for (int l = 0; l < mainList.get(i).getmRevisionsList().get(j).getmDefinitionsList().get(k).getmItemArray().size(); l++)
{
LayoutInflater inflaterFourth = null;
inflaterFourth = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View mLinearViewFourth = inflaterFourth.inflate(R.layout.row_fourth, null);
TextView mItemNameFourth = (TextView) mLinearViewFourth.findViewById(R.id.textViewItemName);
final String itemName = mainList.get(i).getmRevisionsList().get(j).getmDefinitionsList().get(k).getmItemArray().get(l).getItemName();
mItemNameFourth.setText(itemName);
mLinearScrollFourth.addView(mLinearViewFourth);
}
mLinearScrollThird.addView(mLinearViewThird);
}
mLinearScrollSecond.addView(mLinearViewSecond);
}
mLinearListView.addView(mLinearView);
}
}
hey how can i know that the below checkbox when clicked is from which row of the list.
is there any way of knowing that. Every time yo is returning false which is its default value.
checkBox.setOnClickListener( new View.OnClickListener() {
public void onClick(View v) {
CheckBox cb = (CheckBox) v ;
callBlockOptions callBlockOptions = (callBlockOptions) cb.getTag();
callBlockOptions.setChecked( cb.isChecked() );
String yo;
if(callBlockOptions.getPosition()=="0")
{
callBlockOptions.setAllCalls();
}
if(callBlockOptions.getAllCalls() ==true)
yo="true";
else
yo="false";
Toast.makeText(getContext(), callBlockOptions.getPosition(), Toast.LENGTH_LONG).show();
}
});
}
After seeing your code, one thing you might want to try is setting listeners for the individual children within the ListView. You could do something like this:
ListView listView = (ListView)findViewById(R.id.listView);
int count = listView.getChildCount();
for (int x = 0; x < count; x++)
{
Class<? extends View> c = listView.getChildAt(x).getClass();
if (c == CheckBox.class)
{
CheckBox checkBox = (CheckBox)(listView.getChildAt(x));
checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener(){
#Override
public void onCheckedChanged(CompoundButton compoutButton, boolean isChecked) {
// TODO Auto-generated method stub
callBlockOptions.isChecked = isChecked;
}
});
}
else if (c == TextView.class)
{
TextView textView = (TextView)(listView.getChildAt(x));
textView.setOnEditorActionListener(new OnEditorActionListener(){
#Override
public boolean onEditorAction(TextView tView, int arg1, KeyEvent arg2) {
// TODO Auto-generated method stub
callBlockOptions.name = tView.getText().toString();
return true;
}
});
}
You probably want your other class to be like this:
public class callBlockOptions {
public static String name;
public static Boolean isChecked;
}
Then you can get and set name and isChecked like this:
callBlockOptions.isChecked = false;
Boolean boolVal = callBlockOptions.isChecked;
I have 80 EditText fields (cube[i]) and want to read what is in inside the text fields when the text field loses focus.
I can detect when any of the EditTexts (cube) loses focus but I cannot detect exactly which one, Im trying to find which cube is focused on.
the line "EditText cube = (EditText) v.getClass();" is giving me an error
Maybe I can use the View v?
for (int i = 0; i < cube.length; i++) {
cube[i].setOnFocusChangeListener(new OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
EditText cube = (EditText) v.getClass();
String s = cube.getText().toString();
//cubecolor();
}
}
});
}
}
Any help is appreciated.
while creating set some tag to the editText like this (pseudo code)
EditText edit = new EditText(context);
edit.setTag(Integer.valueOf(i)); // i is within the for loop;
Now during the onFocus get the tag
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
EditText cube = (EditText) v;
Integer tag = (Integer)cube.getTag();
//code to sort out which cube based on tag
String s = cube.getText().toString();
//cubecolor();
}
}
This worked, Thanks Dante.
for (int i = 0; i < cube.length; i++) {
cube[i].setTag(Integer.valueOf(i)); // give cubes tags
}
for (int i = 0; i < cube.length; i++) {
cube[i].setOnFocusChangeListener(new OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
Integer tag = (Integer) v.getTag();
String s = cube[tag].getText().toString();
Log(TAG, " Content" + s);
revert_cubecolor(tag);
}
}
});
}
I have a textview that can contain clickable links. When one of this links is clicked, I want to start an activity. This works fine, but it should also be possible to click the whole textview and start another activity.
So that's my current solution:
TextView tv = (TextView)findViewById(R.id.textview01);
Spannable span = Spannable.Factory.getInstance().newSpannable("test link span");
span.setSpan(new ClickableSpan() {
#Override
public void onClick(View v) {
Log.d("main", "link clicked");
Toast.makeText(Main.this, "link clicked", Toast.LENGTH_SHORT).show();
} }, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(span);
tv.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("main", "textview clicked");
Toast.makeText(Main.this, "textview clicked", Toast.LENGTH_SHORT).show();
}
});
tv.setMovementMethod(LinkMovementMethod.getInstance());
The problem is, that when I set an OnClickListener, everytime I click on a link first the listener for the whole textview and then the one for the ClickableSpan is called.
Is there a way to prevent android from calling the listener for the whole textview, when a link is clicked? Or to decide in the listener for the whole view, if a link was clicked or not?
Found a workaround that is quite straight forward. Define ClickableSpan on all the text areas that are not part of the links and handle the click on them as if the text view was clicked:
TextView tv = (TextView)findViewById(R.id.textview01);
Spannable span = Spannable.Factory.getInstance().newSpannable("test link span");
span.setSpan(new ClickableSpan() {
#Override
public void onClick(View v) {
Log.d("main", "link clicked");
Toast.makeText(Main.this, "link clicked", Toast.LENGTH_SHORT).show();
} }, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// All the rest will have the same spannable.
ClickableSpan cs = new ClickableSpan() {
#Override
public void onClick(View v) {
Log.d("main", "textview clicked");
Toast.makeText(Main.this, "textview clicked", Toast.LENGTH_SHORT).show();
} };
// set the "test " spannable.
span.setSpan(cs, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// set the " span" spannable
span.setSpan(cs, 6, span.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(span);
tv.setMovementMethod(LinkMovementMethod.getInstance());
Hope this helps (I know this thread is old, but in case anyone sees it now...).
This is a quite easy solution.. This worked for me
textView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ClassroomLog.log(TAG, "Textview Click listener ");
if (textView.getSelectionStart() == -1 && textView.getSelectionEnd() == -1) {
// do your code here this will only call if its not a hyperlink
}
}
});
Matthew suggested subclassing TextView and with that hint a came up with a rather ugly workaround. But it works:
I've created a "ClickPreventableTextView" which I use when I have clickablespans in a TextView that should be clickable as a whole.
In its onTouchEvent method this class calls the onTouchEvent method of MovementMethod before calling onTouchEvent on its base TextView class. So it is guaranted, that the Listener of the clickablespan will be invoked first. And I can prevent invoking the OnClickListener for the whole TextView
/**
* TextView that allows to insert clickablespans while whole textview is still clickable<br>
* If a click an a clickablespan occurs, click handler of whole textview will <b>not</b> be invoked
* In your span onclick handler you first have to check whether {#link ignoreSpannableClick} returns true, if so just return from click handler
* otherwise call {#link preventNextClick} and handle the click event
* #author Lukas
*
*/
public class ClickPreventableTextView extends TextView implements OnClickListener {
private boolean preventClick;
private OnClickListener clickListener;
private boolean ignoreSpannableClick;
public ClickPreventableTextView(Context context) {
super(context);
}
public ClickPreventableTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ClickPreventableTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public boolean onTouchEvent(MotionEvent event) {
if (getMovementMethod() != null)
getMovementMethod().onTouchEvent(this, (Spannable)getText(), event);
this.ignoreSpannableClick = true;
boolean ret = super.onTouchEvent(event);
this.ignoreSpannableClick = false;
return ret;
}
/**
* Returns true if click event for a clickable span should be ignored
* #return true if click event should be ignored
*/
public boolean ignoreSpannableClick() {
return ignoreSpannableClick;
}
/**
* Call after handling click event for clickable span
*/
public void preventNextClick() {
preventClick = true;
}
#Override
public void setOnClickListener(OnClickListener listener) {
this.clickListener = listener;
super.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if (preventClick) {
preventClick = false;
} else if (clickListener != null)
clickListener.onClick(v);
}
}
The listener for the clickable span now looks like that
span.setSpan(new ClickableSpan() {
#Override
public void onClick(View v) {
Log.d("main", "link clicked");
if (widget instanceof ClickPreventableTextView) {
if (((ClickPreventableTextView)widget).ignoreSpannableClick())
return;
((ClickPreventableTextView)widget).preventNextClick();
}
Toast.makeText(Main.this, "link clicked", Toast.LENGTH_SHORT).show();
} }, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
For me the main disadvantage is, that now getMovementMethod().onTouchEvent will be called twice (TextView calls that method in it's onTouchEvent method). I don't know if this has any side effects, atm it works as expected.
The code is work for me and that is from source code of LinkMovementMethod
tv.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
TextView tv = (TextView) v;
if (event.action == MotionEvent.ACTION_UP) {
int x = (int) event.getX();
int y = (int) event.getY();
Layout layout = tv.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] link = contentSpan.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
link[0].onClick(tv);
} else {
//do other click
}
}
return true;
}
});
Solved something very similar in a very nice way.
I wanted to have text that has a link which is clickable!! and i wanted to be able to press the text Where there is no link and have a on click listener in it.
I took the LinkMovementMethod from grepcode and changed it a little Copy and past this class and copy the bottom and it will work :
import android.text.Layout;
import android.text.NoCopySpan;
import android.text.Selection;
import android.text.Spannable;
import android.text.method.MovementMethod;
import android.text.method.ScrollingMovementMethod;
import android.text.style.ClickableSpan;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
public class
CustomLinkMovementMethod
extends ScrollingMovementMethod
{
private static final int CLICK = 1;
private static final int UP = 2;
private static final int DOWN = 3;
public abstract interface TextClickedListener {
public abstract void onTextClicked();
}
TextClickedListener listener = null;
public void setOnTextClickListener(TextClickedListener listen){
listener = listen;
}
#Override
public boolean onKeyDown(TextView widget, Spannable buffer,
int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
if (event.getRepeatCount() == 0) {
if (action(CLICK, widget, buffer)) {
return true;
}
}
}
return super.onKeyDown(widget, buffer, keyCode, event);
}
#Override
protected boolean up(TextView widget, Spannable buffer) {
if (action(UP, widget, buffer)) {
return true;
}
return super.up(widget, buffer);
}
#Override
protected boolean down(TextView widget, Spannable buffer) {
if (action(DOWN, widget, buffer)) {
return true;
}
return super.down(widget, buffer);
}
#Override
protected boolean left(TextView widget, Spannable buffer) {
if (action(UP, widget, buffer)) {
return true;
}
return super.left(widget, buffer);
}
#Override
protected boolean right(TextView widget, Spannable buffer) {
if (action(DOWN, widget, buffer)) {
return true;
}
return super.right(widget, buffer);
}
private boolean action(int what, TextView widget, Spannable buffer) {
boolean handled = false;
Layout layout = widget.getLayout();
int padding = widget.getTotalPaddingTop() +
widget.getTotalPaddingBottom();
int areatop = widget.getScrollY();
int areabot = areatop + widget.getHeight() - padding;
int linetop = layout.getLineForVertical(areatop);
int linebot = layout.getLineForVertical(areabot);
int first = layout.getLineStart(linetop);
int last = layout.getLineEnd(linebot);
ClickableSpan[] candidates = buffer.getSpans(first, last, ClickableSpan.class);
int a = Selection.getSelectionStart(buffer);
int b = Selection.getSelectionEnd(buffer);
int selStart = Math.min(a, b);
int selEnd = Math.max(a, b);
if (selStart < 0) {
if (buffer.getSpanStart(FROM_BELOW) >= 0) {
selStart = selEnd = buffer.length();
}
}
if (selStart > last)
selStart = selEnd = Integer.MAX_VALUE;
if (selEnd < first)
selStart = selEnd = -1;
switch (what) {
case CLICK:
if (selStart == selEnd) {
return false;
}
ClickableSpan[] link = buffer.getSpans(selStart, selEnd, ClickableSpan.class);
if (link.length != 1)
return false;
link[0].onClick(widget);
break;
case UP:
int beststart, bestend;
beststart = -1;
bestend = -1;
for (int i = 0; i < candidates.length; i++) {
int end = buffer.getSpanEnd(candidates[i]);
if (end < selEnd || selStart == selEnd) {
if (end > bestend) {
beststart = buffer.getSpanStart(candidates[i]);
bestend = end;
}
}
}
if (beststart >= 0) {
Selection.setSelection(buffer, bestend, beststart);
return true;
}
break;
case DOWN:
beststart = Integer.MAX_VALUE;
bestend = Integer.MAX_VALUE;
for (int i = 0; i < candidates.length; i++) {
int start = buffer.getSpanStart(candidates[i]);
if (start > selStart || selStart == selEnd) {
if (start < beststart) {
beststart = start;
bestend = buffer.getSpanEnd(candidates[i]);
}
}
}
if (bestend < Integer.MAX_VALUE) {
Selection.setSelection(buffer, beststart, bestend);
return true;
}
break;
}
return false;
}
public boolean onKeyUp(TextView widget, Spannable buffer,
int keyCode, KeyEvent event) {
return false;
}
#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);
ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true;
} else {
Selection.removeSelection(buffer);
if (action == MotionEvent.ACTION_UP) {
if(listener != null)
listener.onTextClicked();
}
}
}
return super.onTouchEvent(widget, buffer, event);
}
public void initialize(TextView widget, Spannable text) {
Selection.removeSelection(text);
text.removeSpan(FROM_BELOW);
}
public void onTakeFocus(TextView view, Spannable text, int dir) {
Selection.removeSelection(text);
if ((dir & View.FOCUS_BACKWARD) != 0) {
text.setSpan(FROM_BELOW, 0, 0, Spannable.SPAN_POINT_POINT);
} else {
text.removeSpan(FROM_BELOW);
}
}
public static MovementMethod getInstance() {
if (sInstance == null)
sInstance = new CustomLinkMovementMethod();
return sInstance;
}
private static CustomLinkMovementMethod sInstance;
private static Object FROM_BELOW = new NoCopySpan.Concrete();
}
Then in your code where the text view is add:
CustomLinkMovementMethod link = (CustomLinkMovementMethod)CustomLinkMovementMethod.getInstance();
link.setOnTextClickListener(new CustomLinkMovementMethod.TextClickedListener() {
#Override
public void onTextClicked() {
Toast.makeText(UserProfileActivity.this, "text Pressed", Toast.LENGTH_LONG).show();
}
});
YOUR_TEXTVIEW.setMovementMethod(link);
It's quite simple, you can cancell textview's pending intent about click in ClickableSpan callback
span.setSpan(new ClickableSpan() {
#Override
public void onClick(View v) {
tv.cancelPendingInputEvents() //here new line, textview will not receive click event
Log.d("main", "link clicked");
Toast.makeText(Main.this, "link clicked", Toast.LENGTH_SHORT).show();
} }, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(span);
I think that this involves subclassing TextView and changing its behavior, unfortunately. Have you thought about trying to put a background behind the TextView and attaching an onClickListener to it?
copy below function
private fun setClickableHighLightedText(
tv: TextView,
textToHighlight: String,
onClickListener: View.OnClickListener?
) {
val tvt = tv.text.toString()
var ofe = tvt.indexOf(textToHighlight, 0)
val clickableSpan = object : ClickableSpan() {
override fun onClick(textView: View) {
onClickListener?.onClick(textView)
}
override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
//set color of the text
ds.color = getColor(R.color.black)
//draw underline base on true/false
ds.isUnderlineText = false
}
}
val wordToSpan = SpannableString(tv.text)
var ofs = 0
while (ofs < tvt.length && ofe != -1) {
ofe = tvt.indexOf(textToHighlight, ofs)
if (ofe == -1)
break
else {
wordToSpan.setSpan(
clickableSpan,
ofe,
ofe + textToHighlight.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
tv.setText(wordToSpan, TextView.BufferType.SPANNABLE)
tv.movementMethod = LinkMovementMethod.getInstance()
}
ofs = ofe + 1
}
}
use above function and
pass textview,clickble string
setClickableHighLightedText(tvTest,"test") {
showMessage("click")
}