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: Determining which word is clicked in an android textview.
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);
}