How can I link some text of a TextView to an Activity? - android

I would like to link some text on a TextView to an Activity. This is the TextView that I have:
<TextView
android:id="#+id/termsLink"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/terms"
android:layout_weight="4"/>
where #string/terms is:
<string name="terms">Accept terms & conditions..</string>
If I had a link to a webpage I would do it like this:
TextView link = (TextView) findViewById(R.id.termsLink);
link.setMovementMethod(LinkMovementMethod.getInstance());
but I do not know how to start an Activity when I press the link as when it is a real link (that it links a webpage).
EDIT: Please note that I do not have to handle the onClick event in the full text because the link is only on the part "terms & conditions".
EDIT 2: I have tried using two TextView as suggested on the comments and one of the answers below to make the same effect. But sometimes (depending on the screen) the "terms & conditions" part occupy two lines because it does not fill properly on the available space so the second line it is shown on the second TextView and not on the begining of the second line.
The effect is similar to this:
Accept terms &
conditions.
and I would like that it would be like this:
Accept terms &
conditions.
Thanks in advance!

Create a helper class with inner onClick listener
public class ClickSpan extends ClickableSpan {
private String url;
private OnClickListener listener;
public ClickSpan(String url, OnClickListener listener) {
this.url = url;
this.listener = listener;
}
#Override
public void onClick(View widget) {
if (listener != null) listener.onClick(url);
}
public interface OnClickListener {
void onClick(String url);
}
}
Then convert existing span into clickable one
public static Spannable createClickableSpans(Spanned original, ClickSpan.OnClickListener listener) {
SpannableString result = new SpannableString(original);
URLSpan[] spans = result.getSpans(0, result.length(), URLSpan.class);
for (URLSpan span : spans) {
int start = result.getSpanStart(span);
int end = result.getSpanEnd(span);
int flags = result.getSpanFlags(span);
result.removeSpan(span);
result.setSpan(new ClickSpan(span.getURL(), listener), start, end, flags);
}
return result;
}
So, final usage would be like
TextView link = (TextView) findViewById(R.id.termsLink);
link.setMovementMethod(LinkMovementMethod.getInstance());
link.setText(createClickableSpans((Spanned)link.getText(), new ClickSpan.OnClickListener(){
#Override
public void onClick(String url){
//Handle URL on text view click
}
}));

To make only part of a TextView clickable, you can use a spannable inside the TextView and set an onClick event listener. From here, you launch the activity with an intent as usual. You can limit the clickable section of the text by specifying the character positions (start to end)
Checkout this answer by #becomputer06
How to set the part of the text view is clickable

You should probably separate the text into 2 text views one with the terms and condition and one with just the accept.It would make things cleaner and easier. The following TextView is assuming its just for accept.
In the layouts corresponding java class(example: activity_main -> MainActivity):
public void start_activity(View view){
Intent newActivityIntent = new Intent(this,NewActivity.class);
startActivity(newActivityIntent);
}
NewActivity.class is just the name of the activity you want to start.
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
....
}
public void start_activity(View view){
.....
}
}

Related

How to handle click of weblinks within a textual content set to a TextView

I am working on chat project. For showing chat messages I am using the following layout
<TextView
android:id="#+id/messageText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="web"
android:longClickable="true"
android:textColor="#000000"
tools:text="hello hello hello hello" />
And on java side, I am simply formatted the message text and then set it to the TextView.
I am facing 2 main issues:
When I click on weblink in the message, screen scrolls either top or bottom. This does not happen when I click the non-weblink portion of the message.
Click doesn't navigate to the corresponding webpage. With too much difficulty, sometimes it happens that I am able to navigate to the webpage.
Things I have tried:
I tried using the setMovementMethod(context) method after setting the text but didn't work.
I also tried things like removing the autoLink="web" from layout as suggested by some.
I also tried setLinksClickable(true) and setAutoMask(0) but nothing is working.
I have spent quite some time on it now. Can someone help me here.
Thanks in advance.
you must use an inherited class from ClickableSpan, as such setLinksClickable TextView's method is not enough, each link in the text assigned to TextView must have the click event.
The solution is coded in Xamarin, but i thinks is easily translatable to Java.
With this function, we check de html string text, and we get the links and make each of them clickable with MakeLinkClickable function.
public static void SetTextAsHtmlWithOverrideLinks(Android.Widget.TextView textview, string text)
{
var sequence = text.GetTextAsHtml();
var strBuilder = new Android.Text.SpannableStringBuilder(sequence);
var urls = strBuilder.GetSpans(0, sequence.Length(), Java.Lang.Class.FromType(typeof(Android.Text.Style.URLSpan)));
foreach (Android.Text.Style.URLSpan span in urls) {
MakeLinkClickable(strBuilder, span);
}
textview.MovementMethod = Android.Text.Method.LinkMovementMethod.Instance;
textview.TextFormatted = strBuilder;
}
This function makes each link clickable with LinkSpan class, which has our action on link click
public static void MakeLinkClickable(Android.Text.SpannableStringBuilder strBuilder, Android.Text.Style.URLSpan span)
{
strBuilder.SetSpan(new LinkSpan(span), strBuilder.GetSpanStart(span), strBuilder.GetSpanEnd(span),strBuilder.GetSpanFlags(span));
strBuilder.RemoveSpan(span);
}
public class LinkSpan : Android.Text.Style.ClickableSpan
{
public Android.Text.Style.URLSpan Link { get; }
public LinkSpan(Android.Text.Style.URLSpan span) {
Link = span;
}
public override void OnClick(Android.Views.View widget)
{
if (widget is Android.Widget.TextView textView)
// Here open url or another action with your own method;
widget.Invalidate();
}
}
This function converts our html text to a spannable text to handle it.
public static Android.Text.ISpanned GetTextAsHtml(this string input)
{
if (input == null)
input = string.Empty;
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.N)
return Android.Text.Html.FromHtml(input,Android.Text.FromHtmlOptions.ModeLegacy);
else
return Android.Text.Html.FromHtml(input);
}
Use:
SetTextAsHtmlWithOverrideLinks(yourtextView, theHTMLTextString);

ClickableSpan avoid touch propagation to parent view

I have a SpannableString set as the text of my TextView which is inside another view that has a click handler. I've got ClickableSpans inside my text which are, you are right, clickable.
My problem is that I need touch events to be captured in case of a click inside the clickable span and not to propagate to parent view (as the parent has another click handler).
The container view is simply a feed of posts in my app, and those posts can contain hashtags/mentions/links etc. If a user taps a hashtag, they should go to the hashtag which is handled by the clickable span, otherwise, they should go to the post, which is handled by the container itself.
Is there any straightforward mechanism to implement this functionality?
I've came up with a terrible, anti-pattern solution that works well.
In my app class, I've defined:
public static boolean shouldIgnoreNextTouchEvent = false;
In my ClickableSpans click handler, I've set a global flag to avoid next touch event to true:
ClickableSpan clickableSpan = new ClickableSpan() {
#Override
public void onClick(View widget) {
App.shouldIgnoreNextTouchEvent = true;
...
}
}
Then in my parent view's handler:
#Override
public void onClick() {
if(App.shouldIgnoreNextTouchEvent){
App.shouldIgnoreNextTouchEvent = false;
return;
}
...
}
I know it's not a good solution but it works like a charm.
Alternately,
Add a tag to the widget that generated the click event
`
ClickableSpan clickableSpan = new ClickableSpan() {
#Override
public void onClick(View widget) {
widget.setTag("hashtag"); // or anything else
}
}
`
In the parent, check if the tag is present. If it is, this has been consumed by the ClickableSpan
`
#Override
public void onClick(View view) {
String tag = (String) view.getTag();
if(tag.equals("hashtag"){
// Already consumed by child (ClickableSpan)
return;
}
// rest of your code ...
}
`

How do I display a text or a portion in a TextView as a clickable link

There are a lot of answers about how to enable links in a TextView, but I couldn't find anything about how to make [a portion of] text in a TextView look like a link with a custom action. In other words I want to fake a link because the action isn't important enough to get a button. I don't want it to look like a main action on the page.
For example, in my about dialog I want to show the text "Open Source Licenses", have it look like a link, but launch my OpenSourceLicensesActivity instead of an actual URL.
I eventually got my answer from posts by those who were experiencing issues with the above, so I'm providing an answer to this specific question here.
The method below will make the portion of the text from start to end clickable.
public static void makeClickableLink (#NonNull final TextView textView,
#NonNull final CharSequence text,
final int start,
final int end,
#NonNull final ClickableSpan onClick) {
final SpannableString span = new SpannableString (text);
span.setSpan (onClick, start, end, 0);
textView.setText (span);
textView.setMovementMethod (LinkMovementMethod.getInstance ());
}
The provided ClickableSpan lets you handle the click event as such:
final TextView tv = (TextView)findViewById (android.R.id.text1);
final ClickableSpan link = new ClickableSpan () {
#Override public void onClick (final View widget) {
context.startActivity (new Intent (context, OpenSourceLicensesActivity.class));
}
};
makeClickableLink (tv, "Click here for OSS Licenses", 5, 9, link);
The above should makehere clickable, launching your activity.

Enabling click action for links in Android Textview

I need to enable click action for links in Textview and open the webpage in app window. Linkify will open in browser.But I want to open in a webview in my app. Please help me.
There's a blog post that talks about extending ClickableSpan.
First extending the ClickableSpan class:
static class InternalURLSpan extends ClickableSpan {
OnClickListener mListener;
public InternalURLSpan(OnClickListener listener) {
mListener = listener;
}
#Override
public void onClick(View widget) {
mListener.onClick(widget);
}
}
The blog talks about the clickable link doing something in your activity. You can easily adapt this approach to open the URL in a WebView.
It would essentially look something like this:
SpannableString ss = new SpannableString("....")
ss.setSpan(new InternalURLSpan(new OnClickListener() {
public void onClick(View v) {
// Your code to open the link in a WebView here.
}
}), x, y, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
You would just set the SpannableString object as the text of your TextView:
textView.setText(ss);
You can underline textview through string.xml file
<string name="hello_world"><u>Hello world!<u></string>
Now you can set onClickListener on your textView and can redirect user to your webView Activity instead of browser app.

Control onclicklistener in autolink enabled textview

I am using a TextView for which I have set autolink="web" property in XML file. I have also implemented the onClickListener for this TextView. The problem is, when the text in TextView contains a hyperlink, and if I touch that link, the link opens in browser but simultaneously the onClickListener triggers too. I don't want that.
What I want is, if I touch the hyperlink the clickListener should not fire. It should only fire if I touch the part of the text that is not hyperlinked. Any suggestion?
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
}
}
});
It may be a late reply, but may be useful to those who are searching for a solution.
one of the #CommonsWare post helps to intercept autolink OnClick event.
private void fixTextView(TextView tv) {
SpannableString current = (SpannableString) tv.getText();
URLSpan[] spans =
current.getSpans(0, current.length(), URLSpan.class);
for (URLSpan span : spans) {
int start = current.getSpanStart(span);
int end = current.getSpanEnd(span);
current.removeSpan(span);
current.setSpan(new DefensiveURLSpan(span.getURL()), start, end,
0);
}
}
public static class DefensiveURLSpan extends URLSpan {
private String mUrl;
public DefensiveURLSpan(String url) {
super(url);
mUrl = url;
}
#Override
public void onClick(View widget) {
// openInWebView(widget.getContext(), mUrl); // intercept click event and do something.
// super.onClick(widget); // or it will do as it is.
}
}
Apply above code simply as below. It will go through all linkable texts and replace click events to above event handler.
fixTextView(textViewContent);
You can set the property android:linksClickable="false" in your TextView, in conjuction with android:autoLink="web"; this makes the links visible, but not clickable.
if you wish, you can use the next code which allows to customize the clickable links within the string ( based on this post ) :
usage:
final TextView textView = (TextView) findViewById(R.id.textView);
final Spanned text = Html.fromHtml(getString(...));
textView.setText(text);
textView.setMovementMethod(new LinkMovementMethodExt());
LinkMovementMethodExt.java
public class LinkMovementMethodExt extends LinkMovementMethod {
private static LinkMovementMethod sInstance;
public static MovementMethod getInstance() {
if (sInstance == null)
sInstance = new LinkMovementMethodExt();
return sInstance;
}
#Override
public boolean onTouchEvent(final TextView widget, final Spannable buffer, final MotionEvent event) {
final int action = event.getAction();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
final int x = (int) event.getX() - widget.getTotalPaddingLeft() + widget.getScrollX();
final int y = (int) event.getY() - widget.getTotalPaddingTop() + widget.getScrollY();
final Layout layout = widget.getLayout();
final int line = layout.getLineForVertical(y);
final int off = layout.getOffsetForHorizontal(line, x);
final ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
//do something with the clicked item...
return true;
}
}
return super.onTouchEvent(widget, buffer, event);
}
}
Kotlin version:
Similar to older answers in Java. Simply:
In Layout Editor/XML, add the types of things you'd like to hyperlink via the autoLink property.
<TextView
...
android:autoLink="web|phone|email" />
Add an onClickListener to your TextView in Kotlin code to handle clicks on the plain text part. Check to make sure the person didn't click on a link by checking selectionStart and selectionEnd.
binding.messageText.setOnClickListener { view ->
if (binding.messageText.selectionStart == -1 && binding.messageText.selectionEnd == -1) {
// do whatever you want when they click on the plain text part
}
}
Use textView.getSelectionStart() and textView.getSelectionEnd().If u click any text other than link textView.getSelectionStart() and textView.getSelectionEnd() will be -1 .So by using a if condition in onClickListner you can block the onClick action when link is clicked .
//inside onClickListner
if(textView.getSelectionStart()==-1&&textView.getSlectionEnd==-1){
//onClick action
}
private void fixTextView(TextView tv) {
SpannableString current = (SpannableString) tv.getText();
URLSpan[] spans =
current.getSpans(0, current.length(), URLSpan.class);
for (URLSpan span : spans) {
int start = current.getSpanStart(span);
int end = current.getSpanEnd(span);
current.removeSpan(span);
current.setSpan(new DefensiveURLSpan(span.getURL()), start, end,
0);
}
}
public static class DefensiveURLSpan extends URLSpan {
public final static Parcelable.Creator<DefensiveURLSpan> CREATOR =
new Parcelable.Creator<DefensiveURLSpan>() {
#Override
public DefensiveURLSpan createFromParcel(Parcel source) {
return new DefensiveURLSpan(source.readString());
}
#Override
public DefensiveURLSpan[] newArray(int size) {
return new DefensiveURLSpan[size];
}
};
private String mUrl;
public DefensiveURLSpan(String url) {
super(url);
mUrl = url;
}
#Override
public void onClick(View widget) {
// openInWebView(widget.getContext(), mUrl); // intercept click event and do something.
// super.onClick(widget); // or it will do as it is.
}
}
You would then call fixTextView(textViewContent); on the view after it is declared (via inflation or findViewById) or added to the window (via addView)
This includes the missing requirement to set a CREATOR when extending a Parcelable.
It was proposed as an edit, but rejected. Unfortunately, now future users will have to find out the original one is incomplete first. Nice one, reviewers!
Instead of using a onClickListener, you can try this.
private void addLink() {
tvLink = (TextView) findViewById(R.id.tvInfo2);
String strURL = UrlLoader.getCodeUrl();
// Make the url string clicable and take action in its onclick
SpannableString spanUrl = SpannableString.valueOf(strURL);
spanUrl.setSpan(new InternalURLSpan(new OnClickListener() {
public void onClick(View v) {
//Do Some action
}
}), 0, spanUrl.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tvLink.setText(spanUrl);
// We probably also want the user to jump to your link by moving the
// focus (e.g. using the trackball), which we can do by setting the
// proper movement method:
MovementMethod m = tvLink.getMovementMethod();
if ((m == null) || !(m instanceof LinkMovementMethod)) {
if (tvLink.getLinksClickable()) {
tvLink.setMovementMethod(LinkMovementMethod.getInstance());
}
}
}
Also in the layout XML file , dont forget to add
<TextView android:layout_width="wrap_content" android:linksClickable="true"
android:layout_height="wrap_content" android:id="#+id/tvInfo2" android:text="#string/url_link" />
Just adding
textView.setMovementMethod(CustomLinkMovementMethod.getInstance());
to #binary's answer for those whose the method did not work with them

Categories

Resources