Android TextView Linkify intercepts with parent View gestures - android

The problem is that if i Linkify the textView the underliyng ScrollView don't listen the sweep Gestures I've setted.Is there a way to have Linkify without messing with the underliyng view's gestures?
I tried to override ontouchEvent and return false to ACTION_MOVE but the scrollview's gesture needs the ACTION_DOWN and ACTION_UP event to function. Is there a way to achieve that?

Linkify applies to a movementMethod to the textView LinkMovementMethod. That movement method thought it implements a scrolling vertically method it overrides any other scrolling method the parent has. Although touchEvent can be dispached to the parent, the specific parent ScrollView needed the whole sequence ACTION_DOWN , ACTION_MOVE, ACTION_UP to perform (sweep detection).
So the solution to my problem is after Linkify to remove the textView's scrolling method and handle the LinkMovementMethod link detection action in onTouchEvent of the textView.
#override
public boolean onTouchEvent(MotionEvent event) {
TextView widget = (TextView) this;
Object text = widget.getText();
if (text instanceof Spanned) {
Spannable buffer = (Spannable) text;
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;
}
}
}
return false;
}
This way i have the Link_Click detection (performed only with the user touches the link and not the whole textview) and i don't have the whole LinkMovementMethod.

#weakwire and #Ridicully answers are correct. I just created a small gist that you can re-use in your project.
This is the link: https://gist.github.com/amilcar-andrade/e4b76840da1dc92febfc

There is a small bad thing that
TextView::setText(...) method utilizing autoLink flag,
if (mAutoLinkMask != 0) {
Spannable s2;
if (type == BufferType.EDITABLE || text instanceof Spannable) {
s2 = (Spannable) text;
} else {
s2 = mSpannableFactory.newSpannable(text);
}
if (Linkify.addLinks(s2, mAutoLinkMask)) {
text = s2;
type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
/*
* We must go ahead and set the text before changing the
* movement method, because setMovementMethod() may call
* setText() again to try to upgrade the buffer type.
*/
mText = text;
// Do not change the movement method for text that support text selection as it
// would prevent an arbitrary cursor displacement.
if (mLinksClickable && !textCanBeSelected()) {
setMovementMethod(LinkMovementMethod.getInstance());
}
}
}
So I spent a time to understand,
why i'm disabling links in ListView item,
but it obtains a link sometimes!
You need to set that flag in needed value
and then call a setText(...)

Related

textIsSelectable is making TextView scroll

<TextView
android:id="#+id/myText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="5"
android:padding="16dip"
android:textIsSelectable="true"
android:textSize="16sp"/>
If android:textIsSelectable is not present, the TextView has a maxLines of 5 and you cannot get the TextView to scroll, even if there is additional text that has run off the view.
However, with the addition of android:textIsSelectable, the TextView will still have a maximum line count of 5, but you can make it scroll by tapping the bottom border of the TextView or selecting some text and dragging the text selector controls.
Is there a way to prevent the TextView from being scrollable when android:textIsSelectable is present?
Well, you might not understand what android:textIsSelectable is already doing...
According to TextView documentation (you can find it here):
To allow users to copy some or all of the TextView's value and paste
it somewhere else, set the XML attribute android:textIsSelectable to
true or call setTextIsSelectable(true).
The textIsSelectable flag allows users to make selection gestures in the TextView, which in turn triggers the system's built-in copy/paste controls.
It seems to be that scrolling is one of the main features of isSelectable flag, but ...
According to question, here is a post from: How do I completely prevent a TextView from scrolling?
So I did a little research and I don't think it's as simple as just
disabling scrolling, but there are a few things you can do/try.
The first is setEnabled(false) but this will disable links and alter
the text color.
The second, which I suggest trying, is using the scrollTo(int x, int
y) method. Just scrollTo(0,0) after setting the text of the TextView,
my guess is the large text is the only thing causing the scrolling so
this should be able to take care of it.
The third answer I found that you can try is a bit more complicated
and not exactly your question but it may work for you can be found
here.
public class LinkMovementMethodOverride implements View.OnTouchListener{
#Override
public boolean onTouch(View v, MotionEvent event) {
TextView widget = (TextView) v;
Object text = widget.getText();
if (text instanceof Spanned) {
Spanned buffer = (Spanned) text;
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 only works on Spannable text. In our case setSelection doesn't work on spanned text
//Selection.setSelection(buffer, buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0]));
}
return true;
}
}
}
return false;
}
}
"After that apply it to the target textview as touch listener: -
textview.setOnTouchListener(new LinkMovementMethodOverride());"
You can also simply try to put this lines to your TextView attributes:
android:isScrollContainer="false"
android:ellipsize="end"
Hope it help
You can create your custom non scrollable TextView
public class NonScrollableTextView extends TextView {
public NonScrollableTextView(Context context) {
super(context);
}
public NonScrollableTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NonScrollableTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public void scrollTo(int x, int y) {
//nop
}
}

Which listener should I use for the letters in a wordsearch android application?

....
case 5:
sizeperbutton = sizeOfLayout/5;
for( i = 0; i<25; i++) {
buttons[i] = new Button(this)
ll.addView(buttons[i]);
ViewGroup.LayoutParams mParams = buttons[i].getLayoutParams();
mParams.height = sizeperbutton;
mParams.width = sizeperbutton;
int j = (int)(Math.random()*26);
buttons[i].setText(letterEN[j]);
buttons[i].setTag(i);
buttons[i].setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Object tag = view.getTag();
if(motionEvent.getAction() == MotionEvent.ACTION_DOWN){
/* Set the background drawable only for the button I've clicked, and get its text and append it to a textView*/
}
if(motionEvent.getAction() == MotionEvent.ACTION_MOVE){
/* Set the background drawable all the buttons I dragged through my finger and append their letters to the same textView*/
}
if(motionEvent.getAction() == MotionEvent.ACTION_UP){
/*Release touching, check if the text which is stacked to the textView is contained by a list.*/
}
return false;
}
});
}
It creates an area with 5 buttons horizontally and vertically in my gameactivity.
All of them get a random letter and get the touch listener which is I think not adequate for clicking more objects simultaneously, it only works for just one button. I'd appreciate any sources or codes. I'd taken a long-long research but finally I had to ask for information. Here is what I want to attain: https://www.youtube.com/watch?v=NRIvZioTdMc.

android ClickableSpan intercepts the click event

I have a TextView in a Layout. It's so simple.
I put a OnClickListener in the layout and some part of the TextView is set to be ClickableSpan.
I want the ClickableSpan to do something in the onClick function when it's clicked and
when the other part of the TextView is clicked, it has to do something in the onClick functions of the OnClickListener of the layout.
Here's my code.
RelativeLayout l = (RelativeLayout)findViewById(R.id.contentLayout);
l.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "whole layout", Toast.LENGTH_SHORT).show();
}
});
TextView textView = (TextView)findViewById(R.id.t1);
textView.setMovementMethod(LinkMovementMethod.getInstance());
SpannableString spannableString = new SpannableString(textView.getText().toString());
ClickableSpan span = new ClickableSpan() {
#Override
public void onClick(View widget) {
Toast.makeText(MainActivity.this, "just word", Toast.LENGTH_SHORT).show();
}
};
spannableString.setSpan(span, 0, 5, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(spannableString);
I've also run into this problem, and thanks to the source code #KMDev mentioned, I've came up with a much cleaner approach.
First, since I'm only having a TextView that is to be made partially clickable, in fact I don't need most of the functionalities LinkMovementMethod (and its super class ScrollingMovementMethod) which adds ability to handle key press, scrolling, etc.
Instead, create a custom MovementMethod that uses the OnTouch() code from LinkMovementMethod:
ClickableMovementMethod.java
package com.example.yourapplication;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.method.BaseMovementMethod;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.view.MotionEvent;
import android.widget.TextView;
/**
* A movement method that traverses links in the text buffer and fires clicks. Unlike
* {#link LinkMovementMethod}, this will not consume touch events outside {#link ClickableSpan}s.
*/
public class ClickableMovementMethod extends BaseMovementMethod {
private static ClickableMovementMethod sInstance;
public static ClickableMovementMethod getInstance() {
if (sInstance == null) {
sInstance = new ClickableMovementMethod();
}
return sInstance;
}
#Override
public boolean canSelectArbitrarily() {
return false;
}
#Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int action = event.getActionMasked();
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 {
Selection.setSelection(buffer, buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true;
} else {
Selection.removeSelection(buffer);
}
}
return false;
}
#Override
public void initialize(TextView widget, Spannable text) {
Selection.removeSelection(text);
}
}
Then using this ClickableMovementMethod, touch event will not be consumed by movement method any more. However, TextView.setMovementMethod() which calls TextView.fixFocusableAndClickableSettings() will set clickable, long-clickable and focusable to true which will make View.onTouchEvent() consume the touch event. To fix for this, simply reset the three attributes.
So the final utility method, to accompany the ClickableMovementMethod, is here:
public static void setTextViewLinkClickable(TextView textView) {
textView.setMovementMethod(ClickableMovementMethod.getInstance());
// Reset for TextView.fixFocusableAndClickableSettings(). We don't want View.onTouchEvent()
// to consume touch events.
textView.setClickable(false);
textView.setLongClickable(false);
}
This works like a charm for me.
Click events on ClickableSpans are fired, and click outside them are passed throught to parent layout listener.
Note that if your are making your TextView selectable, I haven't tested for that case, and maybe you need to dig into the source yourself :P
The first answer to your question is that you aren't setting a click listener on your TextView which is consuming click events as user2558882 points out. After you set a click listener on your TextView, you'll see that areas outside the ClickableSpans's touch area will work as expected. However, you'll then find that when you click on one of your ClickableSpans, the TextView's onClick callback will be fired as well. That leads us to a difficult issue if having both fire is an issue for you. user2558882's reply can't guarantee that your ClickableSpan's onClick callback will be fired before your TextView's. Here's some solutions from a similar thread that are better implemented and an explanation from the source. The accepted answer that thread should work on most devices, but the comments for that answer mention certain devices having issues. It looks like some devices with custom carrier/manufacturer UIs are to blame, but that's speculation.
So why can't you guarantee onClick callback order? If you take a look at the source for TextView (Android 4.3), you'll notice that in the onTouchEvent method, boolean superResult = super.onTouchEvent(event); (super is View) is called before handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); which is the call to your movement method which then calls your ClickableSpan's onClick. Taking a look at super's (View) onTouchEvent(..), you'll notice:
// Use a Runnable and post this rather than
// performClick directly. This lets other visual
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) { // <---- In the case that this won't post,
performClick(); // it'll fallback to calling it directly
}
performClick() calls the click listener set, which in this case is our TextView's click listener. What this means, is that you won't know in what order your onClick callbacks are going to fire. What you DO know, is that your ClickableSpan and TextView click listeners WILL be called. The solution on the thread I mentioned previously, helps ensure the order so you can use flags.
If ensuring compatibility with a lot of devices is a priority, you are best served by taking a second look at your layout to see if you can avoid being stuck in this situation. There are usually lots of layout options to skirt cases like this.
Edit for comment answer:
When your TextView executes onTouchEvent, it calls your LinkMovementMethod's onTouchEvent so that it can handle calls to your various ClickableSpan's onClick methods. Your LinkMovementMethod does the following in its onTouchEvent:
#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);
}
}
return super.onTouchEvent(widget, buffer, event);
}
You'll notice that it takes the MotionEvent, gets the action (ACTION_UP: lifting finger, ACTION_DOWN: pressing down finger), the x and y coordinates of where the touch originated and then finds which line number and offset (position in the text) the touch hit. Finally, if there are ClickableSpans that encompass that point, they are retrieved and their onClick methods are called. Since we want to pass on any touches to your parent layout, you could either call your layouts onTouchEvent if you want it to do everything it does when touched, or you could call it's click listener if that implements your needed functionality. Here's where do to that:
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);
// Your call to your layout's onTouchEvent or it's
//onClick listener depending on your needs
}
}
So to review, you'll create a new class that extends LinkMovementMethod, override it's onTouchEvent method, copy and paste this source with your calls in the correct position where I commented, ensure you're setting your TextView's movement method to this new subclass and you should be set.
Edited again for possible side effect avoidance
Take a look at ScrollingMovementMethod's source (LinkMovementMethod's parent) and you'll see that it's a delegate method which calls a static method return Touch.onTouchEvent(widget, buffer, event); This means that you can just add that as your last line in the method and avoid calling super's (LinkMovementMethod's) onTouchEvent implementation which would duplicate what you're pasting in and other events can fall through as expected.
Here is an easy solution and it worked for me
You can achieve this using a work around in getSelectionStart() and getSelectionEnd() functions of the Textview class,
tv.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ClassroomLog.log(TAG, "Textview Click listener ");
if (tv.getSelectionStart() == -1 && tv.getSelectionEnd() == -1) {
//This condition will satisfy only when it is not an autolinked text
//Fired only when you touch the part of the text that is not hyperlinked
}
}
});
Declare a global boolean variable:
boolean wordClicked = false;
Declare and initialize l as final:
final RelativeLayout l = (RelativeLayout)findViewById(R.id.contentLayout);
Add an OnClickListener to textView:
textView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (!wordClicked) {
// Let the click be handled by `l's` OnClickListener
l.performClick();
}
}
});
Change span to:
ClickableSpan span = new ClickableSpan() {
#Override
public void onClick(View widget) {
wordClicked = true;
Toast.makeText(Trial.this, "just word", Toast.LENGTH_SHORT).show();
// A 100 millisecond delay to let the click event propagate to `textView's`
// OnClickListener and to let the check `if (!wordClicked)` fail
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
wordClicked = false;
}
}, 100L);
}
};
Edit:
Keeping user KMDev's answer in view, the following code will meet your specifications. We create two spans: one for the specified length: spannableString.setSpan(.., 0, 5, ..); and the other with the remainder: spannableString.setSpan(.., 6, spannableString.legth(), ..);. The second ClickableSpan(span2) performs a click on the RelativeLayout. Moreover, by overriding updateDrawState(TextPaint), we are able to give the second span a non-distinctive (non-styled) look. Whereas, first span has a link color and is underlined.
final RelativeLayout l = (RelativeLayout)findViewById(R.id.contentLayout);
l.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
Toast.makeText(Trial.this, "whole layout", Toast.LENGTH_SHORT).show();
}
});
TextView textView = (TextView)findViewById(R.id.t1);
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setHighlightColor(Color.TRANSPARENT);
SpannableString spannableString = new SpannableString(textView.getText().toString());
ClickableSpan span = new ClickableSpan() {
#Override
public void onClick(View widget) {
Toast.makeText(Trial.this, "just word", Toast.LENGTH_SHORT).show();
}
};
spannableString.setSpan(span, 0, 5, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
ClickableSpan span2 = new ClickableSpan() {
#Override
public void onClick(View widget) {
l.performClick();
}
#Override
public void updateDrawState(TextPaint tp) {
tp.bgColor = getResources().getColor(android.R.color.transparent);
tp.setUnderlineText(false);
}
};
spannableString.setSpan(span2, 6, spannableString.length(),
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(spannableString);
Special thanks to user KMDev for noticing the issues with my original answer. There's no need for performing a (faulty) check using boolean variable(s), and setting an OnclickListener for the TextView is not required.
The easiest and fastest way to implement ClickableSpan is:
new SmartClickableSpan
.Builder(this)
.regularText("I agree to all ")
.clickableText(new ClickableOptions().setText("Terms of Use").setOnClick(new
ClickableSpan() {
#Override
public void onClick(#NonNull View view) {
// Your Code..
}
}))
.into(myTextView);
Adding regular Clickable Spans in Android requires calculating sizes for each clickable text, and when it comes to adding a lot of clickable and regular words or sentences in a TextView is becomes a mess.. By using SmartClickableSpan, you'll be able to add whatever amount of clickable words or sentences without any worries of calculating length of each text on every update on it.
SmartClickableSpan Github:
https://github.com/HseinNd98/SmartClickableSpan

Capturing touch events in an android list item and prevent scrolling

I'm trying a wild idea here by putting a custom control within the items of a certain list view. The control is only "activated" if the user touches down on a certain trigger point and then they can "drag around."
My question is, what can I do in onTouchEvent(...) to prevent the listview from receiving the event and scrolling. Right now I can touch and get ahold of the control, but if I move my finger too much up or down the listview takes over and starts scrolling, then my view doesn't even receive a ACTION_UP event.
Here is my current onTouchEvent code:
if (e.getAction() == MotionEvent.ACTION_DOWN) {
Log.d("SwipeView", "onTouchEvent - ACTION_DOWN" + e.getX() + " " + e.getY());
int midX = (int)(this.getWidth() / 2);
int midY = (int)(this.getHeight() / 2);
if (Math.abs(e.getX() - midX) < 100 &&
Math.abs(e.getY() - midY) < 100) {
Log.d("SwipeView", "HEY");
setDragActive(true);
}
this.invalidate();
return true;
} else if (e.getAction() == MotionEvent.ACTION_MOVE) {
_current[0] = e.getX();
_current[1] = e.getY();
this.invalidate();
return true;
} else if (e.getAction() == MotionEvent.ACTION_UP) {
_current[0] = 0;
_current[1] = 0;
setDragActive(false);
this.invalidate();
return true;
}
I'm sure it has something to do with the event hierarchy in some fashion.
This might not be exactly what you're looking for, but it's possible to implement capture capabilities in your activity. add
private View capturedView;
public void setCapturedView(View view) { this.capturedView = view); }
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
return (this.capturedView != null) ?
this.capturedView.dispatchTouchEvent(event) :
super.dispatchTouchEvent(event);
}
to your activity, then simply pass your view on ACTION_DOWNand null on ACTION_UP. it's not exactly pretty, but it works. i'm sure there's a proper way to do this though.
finally learned the correct way to do this: requestDisallowInterceptTouchEvent

Clickable links and copy/paste menu in EditView in android

I have an EditText view in my Android app. I need "inner links" in it, this means that I need some buttons or span inside EditText and with onClick to this button I can do some actions (not redirect to web page).
I realized this buttons with ClickableSpan() like this
linkWord = "my link";
link = new SpannableString(linkWord);
cs = new ClickableSpan(){
private String w = linkWord;
#Override
public void onClick(View widget) {
wrd.setText(w);
}
};
link.setSpan(cs, 0, linkWord.length(), 0);
et.append(link);
For make this span clickable I used
et.setMovementMethod(LinkMovementMethod.getInstance());
"Inner links" works fine, but after using et.setMovementMethod() copy and paste items are disable on OnLongClick menu. And this is a problem, because I need "links" in EditText and copy text from this view in the same time.
I have idea to set in listener OnLongClickListener something like removeMovementMethod() for temporary disable "links" function and use menu with copy/paste and after coping text switch on setMovementMethod() method again. But I don't know how to realize this.
Can you help me? You may be there are some another ways...
Thank you!
I don't think that having the user switch between link and copy mode will win you a usability prize. My solution allows you to select text and open the links at the same time. To achieve this I simply extend ArrowKeyMovementMethod, which allows to select text, and add the onTouchEvent() method from the LinkMovementMethod which handles the clicking/touching of links. There's but one line of code that needs to be changed, which is the one removing the selection from the TextView when no link could be found at the coordinates the screen was touched.
Here's the complete class:
public class MyMovementMethod extends ArrowKeyMovementMethod {
private static MyMovementMethod sInstance;
public static MovementMethod getInstance() {
if (sInstance == null) {
sInstance = new MyMovementMethod ();
}
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);
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 {
that's the line we need to remove
Selection.removeSelection(buffer);
}*/
}
return super.onTouchEvent(widget, buffer, event);
}
}
Doing this is pretty safe even if the documentation states:
This interface [MovementMethod] is intended for use by the framework;
it should not be implemented directly by applications.
http://developer.android.com/reference/android/text/method/MovementMethod.html
The code above extends a documented class rather than implement the interface. All it does is adding a check to see if a link was tapped and otherwise uses the super class methods.
I solved this problem and may be this will be interesting for someone...
For clickable links inside EditText I used
et.setMovementMethod(LinkMovementMethod.getInstance());
in this case in longClick menu there are not copy/paste items.
For activate them I need back to normal EditText state, I can do it with:
et.setMovementMethod(ArrowKeyMovementMethod.getInstance());
After this method links will not work but appear normal longClick menu.
Therefore I added new item to the context menu and switched between this two options:
#Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if(et.getSelectionStart() == -1){ // in case of setMovementMethod(LinkMovementMethod.getInstance())
menu.add(0, 1, 0, "Enable copy");
}
else{
menu.add(0, 2, 0, "Enable links");
}
}
#Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case 1:
et.setMovementMethod(ArrowKeyMovementMethod.getInstance());
et.setSelection(0, 0);
//re-register EditText for context menu:
unregisterForContextMenu(et);
registerForContextMenu(et);
break;
case 2:
et.setMovementMethod(LinkMovementMethod.getInstance());
break;
}
return true;
}
Also I registered EditText for context menu:
registerForContextMenu(et);
Have a hope that this will help someone!

Categories

Resources