How do I make a portion of a Checkbox's text clickable?

AndroidCheckboxHyperlink

Android Problem Overview


I'm trying to create a link in my textbox's adjacent text. This link however is not a URL, but should act as a button so that I can perform a few tasks in the onItemClick event. I'm basically connecting this to a view that shows our End User License Agreement (hard coded).

How can I accomplish this?

Thanks in advance.

Android Solutions


Solution 1 - Android

You may want only part of the text to be a clickable link, while the rest of the checkbox behaves as usual, i.e. you can click the other text to toggle the state.

You can set up your checkbox like so:

CheckBox checkBox = (CheckBox) findViewById(R.id.my_check_box);

ClickableSpan clickableSpan = new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        // Prevent CheckBox state from being toggled when link is clicked
        widget.cancelPendingInputEvents();
        // Do action for link text...
    }
    @Override
    public void updateDrawState(TextPaint ds) {
        super.updateDrawState(ds);
        // Show links with underlines (optional)
        ds.setUnderlineText(true);
    }
};

SpannableString linkText = new SpannableString("Link text");
linkText.setSpan(clickableSpan, 0, linkText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
CharSequence cs = TextUtils.expandTemplate(
    "CheckBox text with link: ^1 , and after link", linkText);

checkBox.setText(cs);
// Finally, make links clickable
checkBox.setMovementMethod(LinkMovementMethod.getInstance());

Solution 2 - Android

The following code worked for me on KitKat. I am yet to test on below versions of Android.

String checkBoxText = "I agree to all the <a href='http://www.redbus.in/mob/mTerms.aspx' > Terms and Conditions</a>";

checkBoxView.setText(Html.fromHtml(checkBoxText));
checkBoxView.setMovementMethod(LinkMovementMethod.getInstance());

Solution 3 - Android

There actually is an elegant solution, using CheckBox and single TextView. Along with a combinations of TextView.setClickable(), Intent Filter, and TextView.setMovementMethod().

You have main view (here, I called it ClickableTextViewExample):

package id.web.freelancer.example;

import android.app.Activity;
import android.os.Bundle;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.widget.CheckBox;
import android.widget.TextView;

public class ClickableTextViewExampleActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        
        CheckBox checkbox = (CheckBox)findViewById(R.id.checkBox1);
        TextView textView = (TextView)findViewById(R.id.textView2);
        
        checkbox.setText("");
        textView.setText(Html.fromHtml("I have read and agree to the " +
        		"<a href='id.web.freelancer.example.TCActivity://Kode'>TERMS AND CONDITIONS</a>"));
        textView.setClickable(true);
        textView.setMovementMethod(LinkMovementMethod.getInstance());
    }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <CheckBox
            android:id="@+id/checkBox1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="CheckBox" />

        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="TextView"
            android:clickable="true" />

    </LinearLayout>

</LinearLayout>

TCActivity.java

package id.web.freelancer.example;

import android.app.Activity;
import android.os.Bundle;

public class TCActivity extends Activity {
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
        setContentView(R.layout.tc);
	}

}

tc.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

	    <TextView
	    android:id="@+id/tcView"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:text="Terms and conditions" />
    

</LinearLayout>

and the final piece of codes that glue it all, the AndroidManifest.xml:

<activity android:name="TCActivity">
    <intent-filter>
        <category android:name="android.intent.category.DEFAULT" />
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="id.web.freelancer.example.TCActivity" />  
    </intent-filter>            
</activity>

Here comes, the explanations:

textView.setText(Html.fromHtml("I have read and agree to the " +
                     "<a href='id.web.freelancer.example.TCActivity://Kode'>TERMS AND CONDITIONS</a>"));
textView.setClickable(true);
textView.setMovementMethod(LinkMovementMethod.getInstance());

> setClickable will allow you to click on textView. But not the HREF link. To do that, you will have to use setMovementMethod() and set it to LinkMovementMethod.

After that, you need to catch the URL. I did this using intent-filter in AndroidManifest.xml

<action android:name="android.intent.action.VIEW" />
<data android:scheme="id.web.freelancer.example.TCActivity" />  

> It catch VIEW command and it only filter URL starting with id.web.freelancer.example.TCActivity://

Here's the package for you to try it out and here's the github repository. Hope this helped

Solution 4 - Android

Kotlin version (through an extension) of Daniel Schuler's answer :

fun CheckBox.addClickableLink(fullText: String, linkText: SpannableString, callback: () -> Unit) {
    val clickableSpan = object : ClickableSpan() {
        override fun onClick(widget: View) {
            widget.cancelPendingInputEvents() // Prevent CheckBox state from being toggled when link is clicked
            callback.invoke()
        }

        override fun updateDrawState(ds: TextPaint) {
            super.updateDrawState(ds)
            ds.isUnderlineText = true // Show links with underlines
        }
    }
    linkText.setSpan(clickableSpan, 0, linkText.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
    val fullTextWithTemplate = fullText.replace(linkText.toString(), "^1", false)
    val cs = TextUtils.expandTemplate(fullTextWithTemplate, linkText)

    text = cs
    movementMethod = LinkMovementMethod.getInstance() // Make link clickable
}

Usage :

yourCheckBox.addClickableLink(
    fullText = "This link must be clickable",
    linkText = SpannableString("This link")
) {
    // Do whatever you want when onClick()
}

Solution 5 - Android

I had the same problem and wanted to have more than one clickable links in the text of a checkbox without loosing the ability to click anywhere in the text (where there is no URL) to select/deselect the checkbox.

The difference to the other answers to this question is that with this solution you can have multiple clickable links in the checkbox text and those links don't have to be at the end of the text.

The layout looks similar to the one in ariefbayu's answer:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="16dp">
    
    <CheckBox
        android:id="@+id/tosCheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:checked="false" />
    
    <TextView
        android:id="@+id/tosTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/tosCheckBox"
        android:layout_centerVertical="true"
        android:clickable="true" />

</RelativeLayout>

I now set the text programmatically. The text I want to display is:

"I have read and accepted the <a href='https://www.anyurl.com/privacy'>privacy statement</a> and <a href='https://www.anyurl.com/tos'>terms of service.</a>"

As it contains HTML, I first convert it to a Spanned. To make the links clickable, I additionally set the movement method of the TextView to LinkMovementMethod:

mTosTextView = (TextView) findViewById(R.id.tosTextView);
mTosTextView.setText(Html.fromHtml(getString(R.string.TOSInfo)));
mTosTextView.setMovementMethod(LinkMovementMethod.getInstance());

And here comes the more tricky part. So far, the CheckBox does not get selected when pressing the TextView. To achive this, I added a touch handler to the TextView:

mTosCheckBox = (CheckBox) findViewById(R.id.tosCheckBox);
mTosTextView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        CharSequence text = mTosTextView.getText();
        
        // find out which character was touched
        int offset = getOffsetForPosition(mTosTextView, event.getX(), event.getY());

        // check if this character contains a URL
        URLSpan[] types = ((Spanned)text).getSpans(offset, offset, URLSpan.class);

        if (types.length > 0) {
            // a link was clicked, so don't handle the event
            Log.d("Some tag", "link clicked: " + types[0].getURL());
            return false;
        }

        // no link was touched, so handle the touch to change 
        // the pressed state of the CheckBox
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mTosCheckBox.setPressed(true);
                break;

            case MotionEvent.ACTION_UP:
                mTosCheckBox.setChecked(!mTosCheckBox.isChecked());
                mTosCheckBox.setPressed(false);
                break;

            default:
                mTosCheckBox.setPressed(false);
                break;
        }
        return true;
    }
});

Finally, as you probably noticed, there is no method getOffsetForPosition(...) yet. If you're targeting API level 14+, you can simply use getOffsetForPosition(), as pointed out by Dheeraj V.S.. As I target API level 8+, I used an implementation that I found here: https://stackoverflow.com/q/11601139/378977.

public int getOffsetForPosition(TextView textView, float x, float y) {
    if (textView.getLayout() == null) {
        return -1;
    }
    final int line = getLineAtCoordinate(textView, y);
    final int offset = getOffsetAtCoordinate(textView, line, x);
    return offset;
}

private int getOffsetAtCoordinate(TextView textView2, int line, float x) {
    x = convertToLocalHorizontalCoordinate(textView2, x);
    return textView2.getLayout().getOffsetForHorizontal(line, x);
}

private float convertToLocalHorizontalCoordinate(TextView textView2, float x) {
    x -= textView2.getTotalPaddingLeft();
    // Clamp the position to inside of the view.
    x = Math.max(0.0f, x);
    x = Math.min(textView2.getWidth() - textView2.getTotalPaddingRight() - 1, x);
    x += textView2.getScrollX();
    return x;
}

private int getLineAtCoordinate(TextView textView2, float y) {
    y -= textView2.getTotalPaddingTop();
    // Clamp the position to inside of the view.
    y = Math.max(0.0f, y);
    y = Math.min(textView2.getHeight() - textView2.getTotalPaddingBottom() - 1, y);
    y += textView2.getScrollY();
    return textView2.getLayout().getLineForVertical((int) y);
}

Solution 6 - Android

Requirements:

only part of the text to be a clickable link, while the rest of the Checkbox behaves as usual:

  1. Prevent CheckBox state from being toggled when link is clicked
  2. Remove ripple effect from CheckBox

Here is the Kotlin version:

interface HtmlAnchorClickListener {
    fun onHyperLinkClicked(name: String)
}

fun addClickableSpan(linkableTextView: TextView?, htmlString: String, listener: HtmlAnchorClickListener) {
    linkableTextView?.let {
        val sequence = HtmlCompat.fromHtml(htmlString, HtmlCompat.FROM_HTML_MODE_LEGACY)
        Log.d("addClickableSpan", "sequence = $sequence")
        val spannableString = SpannableStringBuilder(sequence)
        val urls = spannableString.getSpans(0, sequence.length, URLSpan::class.java)
        urls.forEach { span ->
            with(spannableString) {
                val start = getSpanStart(span)
                val end = getSpanEnd(span)
                val flags = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                val linkColor = linkableTextView.context.getColor(R.color.light_blue)

                val clickable = object : ClickableSpan() {

                    override fun onClick(view: View) {
                        // Prevent CheckBox state from being toggled when link is clicked
                        linkableTextView.cancelPendingInputEvents()
                        removeRippleEffectFromCheckBox(linkableTextView)
                        listener.onHyperLinkClicked(span.url)
                    }

                    override fun updateDrawState(textPaint: TextPaint) {
                        textPaint.color = linkColor
                        textPaint.isUnderlineText = true
                    }
                }
                setSpan(clickable, start, end, flags)
                setSpan(ForegroundColorSpan(linkColor), start, end, flags)
                removeSpan(span)
            }

            with(it) {
                text = spannableString
                linksClickable = true
                movementMethod = LinkMovementMethod.getInstance()
            }
        }
    }
}

fun removeRippleEffectFromCheckBox(textView: TextView) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        var drawable = textView.background
        if (drawable is RippleDrawable) {
            drawable = drawable.findDrawableByLayerId(0)
            textView.background = drawable
        }
    }
}

Usage:

private fun setUpTermsOfUseHyperLink() {
    val checkBoxText =
        "I agree to all the <a href='http://www.redbus.in/mob/mTerms.aspx' > Terms and Conditions</a>"

    addClickableSpan(cbAccept, checkBoxText, object : HtmlAnchorClickListener {
        override fun onHyperLinkClicked(name: String) {
            Toast.makeText(context!!, name, Toast.LENGTH_LONG).show()
        }
    })
}

Solution 7 - Android

Create a CheckBox with no text and add two TextViews next to it. The first is a non-clickable view with text like "I have read and agree to the ". The second is a clickable view with text like "TERMS AND CONDITIONS". Place the TextViews side by side without any margin. Notice the extra space in the end of the first view for natural text alignment. This way you could style both texts as you like.

Sample xml code:

<RelativeLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <CheckBox
        android:id="@+id/terms_check"
        android:text=""
        android:layout_marginLeft="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/terms_text"
        android:layout_toRightOf="@id/terms_check"
        android:text="I have read and agree to the "
        android:layout_marginLeft="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/terms_link"
        android:layout_toRightOf="@id/terms_text"
        android:text="TERMS AND CONDITIONS"
        android:textColor="#00f"
        android:onClick="onClick"
        android:clickable="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</RelativeLayout>


Then add an onClick() handler in the code. Voilá.

public class SignUpActivity extends Activity {

    public void onClick(View v) {
        ...
    }  
}

Solution 8 - Android

I didn't like the solution with the checkBox + textView as your custom view will extend a ViewGroup and not CheckBox thus forcing you to wrap CheckBox behavior.

It was important to me that the custom CheckBox can be used in xml exactly like a regular one.

The acceptable behavior for me was that this CheckBox will only be toggled when you press on it's box and not on it's text.

So I've extended CheckBox, and in order to achieve this behavior I've played with the whole touch mechanism, the full code is below, and an explanation right after it for anyone who like to know how it works.

public class CheckBoxWithLinks extends CheckBox {


public CheckBoxWithLinks(Context context) {
    super(context);
}

public CheckBoxWithLinks(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public CheckBoxWithLinks(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

@Override
public boolean performClick() {
    if ( !onTextClick)
        return super.performClick();
    return false;
}

private boolean onTextClick = false;
@Override
public boolean onTouchEvent(MotionEvent event) {
    onTextClick = !isLeftDrawableClick(event) && !isRightDrawableClick(event);
    return super.onTouchEvent(event);
}

private boolean isRightDrawableClick(MotionEvent event) {
    return event.getX() >= getRight() - getTotalPaddingRight();
}

private boolean isLeftDrawableClick(MotionEvent event) {
    return event.getX() <= getTotalPaddingLeft();
}
}

it relays on the fact that performClick method is call internally by the TextView mechanism that CheckBox extends, the ClickableSpan is also called by the TextView Mechanism. so what happens is that when you touch your CheckBox's text it will call both.

So What I've done is detect if the click was in the text area, if so we will disable the perfomClick thus disabling the toggle. but the clickable span will still be called.

Usage:

You still need to add a clickable span and setMovementMethod as before, just like a regular TextView.

Solution 9 - Android

If you look for a solution with the URL, i suggest you to use follow solution. With CheckBox and TextView.

	final TextView tvTerms = (TextView)findViewById(R.id.tvTerms);
	
	Pattern pattern = Pattern.compile(getString(R.string.terms_and_conds));
	TransformFilter transFilter = new TransformFilter() {
	@Override
	public String transformUrl(Matcher match, String url) {
		return "";
	}}; 
	Linkify.addLinks(tvTerms, pattern, Constants.URL_TERMS_AND_CONDS, null, transFilter);

where URL_TERMS_AND_CONDS = "yourUrl.com"; and R.string.terms_and_conds = id to the resource with the clickable string.

Solution 10 - Android

Here is a simple code snippet to make checkbox spannable string clickable in kotlin:

        val myText = ... // your string.
        val spannableStr = SpannableString(myText)

        val clickableText1 = object : ClickableSpan() {
            override fun onClick(widget: View) {
                widget.cancelPendingInputEvents()
                doMyWorkHere()
            }

            override fun updateDrawState(text: TextPaint) {
                super.updateDrawState(text)
                text.color = Color.RED
                text.isUnderlineText = true
            }
        }

        val clickableText2 = object : ClickableSpan() {
            override fun onClick(widget: View) {
                widget.cancelPendingInputEvents()
                doMySecondWork()
            }

            override fun updateDrawState(textPaint: TextPaint) {
                super.updateDrawState(textPaint)
                textPaint.color = COLOR.BLUE
                textPaint.isUnderlineText = false
            }
        }

        spannableStr1.setSpan(clickableText1, 10, 20, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        spannableStr2.setSpan(clickableText2, 30, 40, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

        myCheckBox.text = spannableStr
        myCheckBox.movementMethod = LinkMovementMethod.getInstance()

Happy Coding :)

Solution 11 - Android

Please try this one

String checkBoxText = "I agree to the Yunolearning <a href='https://blog.google/intl/en-in/' > Blogs</a> and <a href='https://about.google/stories/' > Stories</a>";

MaterialCheckBox singleCheckbox = new MaterialCheckBox(this);
singleCheckbox.setTag(formField.getName());
singleCheckbox.setText(Html.fromHtml(checkBoxText));
singleCheckbox.setMovementMethod(LinkMovementMethod.getInstance());

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPaulGView Question on Stackoverflow
Solution 1 - AndroidDaniel SchulerView Answer on Stackoverflow
Solution 2 - AndroidGopinathView Answer on Stackoverflow
Solution 3 - AndroidariefbayuView Answer on Stackoverflow
Solution 4 - AndroidPhilView Answer on Stackoverflow
Solution 5 - AndroidFloView Answer on Stackoverflow
Solution 6 - AndroidFrancis BaconView Answer on Stackoverflow
Solution 7 - AndroidJarno ArgillanderView Answer on Stackoverflow
Solution 8 - AndroidndoriView Answer on Stackoverflow
Solution 9 - AndroidvalidcatView Answer on Stackoverflow
Solution 10 - AndroidRahul RainaView Answer on Stackoverflow
Solution 11 - AndroidItz PkyadavView Answer on Stackoverflow