如何使复选框文本的一部分可点击?

Posted

技术标签:

【中文标题】如何使复选框文本的一部分可点击?【英文标题】:How do I make a portion of a Checkbox's text clickable? 【发布时间】:2012-01-01 08:17:33 【问题描述】:

我正在尝试在我的文本框的相邻文本中创建一个链接。但是,此链接不是 URL,而是应充当按钮,以便我可以在 onItemClick 事件中执行一些任务。我基本上是将其连接到显示我们的最终用户许可协议(硬编码)的视图。

我怎样才能做到这一点?

提前致谢。

【问题讨论】:

【参考方案1】:

您可能只希望文本的一部分成为可点击的链接,而复选框的其余部分与往常一样,即您可以点击其他文本来切换状态。

您可以像这样设置您的复选框:

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());

【讨论】:

非常感谢丹尼尔,这正是我想要的!有没有办法在 API 这应该是公认的答案。 widget.cancelPendingInputEvents() 正在变魔术!!! 工作正常,但即使在单击 ClickableSpan 时,也会为 CheckBox drawable 选择按下的状态,即使它没有被切换。关于如何避免这种情况的任何建议? @devrocca 我的解决方案是使用此链接上的代码gist.github.com/Jeevuz/b775e0d5f0dabdcaefe4 消除复选框的涟漪效应@ 这不是完美的解决方案,但它工作得很好,因为复选框已经有了“选中/取消选中”动画 这应该是公认的答案。此外,这里是@Mattia Ruggiero 消除波纹的方法的 kotlin 版本: checkBox.apply background = (background as?RippleDrawable)?.findDrawableByLayerId(0) 【参考方案2】:

以下代码在 KitKat 上对我有用。我还没有在以下版本的 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());

【讨论】:

谁能在低版本上试试这个?这个解决方案很完美! @Gopinath 是的,我可以确认这可以一直追溯到 Android 2.2.2(Froyo,Samsung GT-7510)。我喜欢这个,解决方案是如此简单和优雅,它确实(正如 X.X_Mass_Developer 所说)完美! 将此添加到我的应用程序中。它正在尝试使用您的 URL 字符串的名称启动一个活动。 我遇到了一个错误:按下复选框的下部会启动 URL 而不是切换检查 @Xiao 这是LinkMovementMethod的问题,你可以用这个来解决:github.com/saket/Better-Link-Movement-Method【参考方案3】:

实际上有一个优雅的解决方案,使用CheckBox 和单个TextView。以及TextView.setClickable()、Intent Filter 和TextView.setMovementMethod() 的组合。

你有主视图(这里,我称之为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());
    

ma​​in.xml

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

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_
        android:layout_ >

        <CheckBox
            android:id="@+id/checkBox1"
            android:layout_
            android:layout_
            android:text="CheckBox" />

        <TextView
            android:id="@+id/textView2"
            android:layout_
            android:layout_
            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_
    android:layout_
    android:orientation="vertical" >

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


</LinearLayout>

还有最后一段代码,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>

来了,解释:

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 将允许您点击textView。但不是 HREF 链接。为此,您必须使用 setMovementMethod() 并将其设置为 LinkMovementMethod

之后,您需要捕获 URL。我在 AndroidManifest.xml

中使用intent-filter 做到了这一点
<action android:name="android.intent.action.VIEW" />
<data android:scheme="id.web.freelancer.example.TCActivity" />  

它捕获 VIEW 命令,它只过滤以 id.web.freelancer.example.TCActivity:// 开头的 URL

Here's the package for you to try it out 和 here's the github repository。希望这有帮助

【讨论】:

不错的解决方案@silent! :) 在这里总能学到新东西。我认为我的答案仍然简单得多,更容易维护,并且开发人员对结果有更多的控制权。我想知道,哪一个在空间/性能方面更有效?想法?不过,做得很好。 我真的不知道。永远不要试图查看哪个是优化的:D。不过,我的解决方案将使布局方面变得容易。只有一个 TextView。 效果很好,看起来很流畅。再次感谢。 抱歉,但为了真正的优雅,你不能超越@Gopinath 下面的三行解决方案 - 它只使用一个控件并完成所有需要的操作。 当您在&lt;data&gt; 中传递大写字母时,您将收到警告Scheme matching is case sensitive and should only use lower-case characters 。您可以使用id.web.freelancer.example.termsactivity 并在&lt;a href=''&gt; 中传递相同的内容【参考方案4】:

Daniel Schuler 回答的 Kotlin 版本(通过 extension)

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

用法:

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

【讨论】:

男人。这是很棒的扩展! 我尝试了整个,它使整个复选框可点击,并且忽略了可扩展的东西【参考方案5】:

我遇到了同样的问题,希望在复选框的文本中拥有多个可点击链接,而不会失去点击文本中任意位置(没有 URL 的地方)来选择/取消选择复选框的能力。

与此问题其他答案的不同之处在于,使用此解决方案,您可以在复选框文本中包含多个可点击链接,并且这些链接不必位于文本末尾。

布局看起来类似于ariefbayu的答案:

<RelativeLayout
    android:layout_
    android:layout_
    android:layout_marginBottom="16dp">

    <CheckBox
        android:id="@+id/tosCheckBox"
        android:layout_
        android:layout_
        android:layout_centerVertical="true"
        android:checked="false" />

    <TextView
        android:id="@+id/tosTextView"
        android:layout_
        android:layout_
        android:layout_toRightOf="@id/tosCheckBox"
        android:layout_centerVertical="true"
        android:clickable="true" />

</RelativeLayout>

我现在以编程方式设置文本。我要显示的文字是:

"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>"

由于它包含 HTML,我首先将其转换为 Spanned。为了使链接可点击,我另外将 TextView 的移动方法设置为 LinkMovementMethod:

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

这里是更棘手的部分。到目前为止,在按下 TextView 时 CheckBox 没有被选中。为了实现这一点,我在 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;
    
);

最后,您可能已经注意到,目前还没有方法 getOffsetForPosition(...)。如果您的目标是 API 级别 14+,您可以简单地使用 getOffsetForPosition()、as pointed out by Dheeraj V.S.。当我以 API 级别 8+ 为目标时,我使用了在这里找到的实现: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);

【讨论】:

【参考方案6】:

要求:

只有部分文本是可点击的链接,而 Checkbox 的其余部分则照常运行:

    防止在单击链接时切换 CheckBox 状态 从 CheckBox 中移除波纹效果

这是 Kotlin 版本:

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
        
    

用法:

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()
        
    )

【讨论】:

【参考方案7】:

创建一个不带文本的CheckBox,并在其旁边添加两个TextView。第一个是不可点击视图,其中包含“我已阅读并同意”之类的文字。第二个是可点击 视图,其中包含“条款和条件”等文本。将TextViews 并排放置,没有任何边距。注意第一个视图末尾的额外空间,用于自然文本对齐。这样您就可以根据自己的喜好设置两种文本的样式。

示例 xml 代码:

<RelativeLayout
    android:layout_
    android:layout_>
    <CheckBox
        android:id="@+id/terms_check"
        android:text=""
        android:layout_marginLeft="10dp"
        android:layout_
        android:layout_/>
    <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_
        android:layout_/>
    <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_
        android:layout_/>
</RelativeLayout>

然后在代码中添加一个onClick() 处理程序。瞧。

public class SignUpActivity extends Activity 

    public void onClick(View v) 
        ...
      

【讨论】:

这是个好主意。但理想情况下,只有一部分相邻文本应该是可点击的。例如:我已阅读并同意条款和条件”。 如何并排添加两个TextViews:不可复制的“我已阅读并同意”,旁边是可点击的“条款和条件”。注意第一个视图末尾的额外空间,用于自然文本对齐。这样您就可以根据自己的喜好设置两个文本的样式。 谢谢 Jarno,知道如何设置我的 XML 来执行此操作吗? 刚刚更新了 :) 虽然我身边没有编译器所以我无法验证但应该没问题。 这对 i18n 来说是一场噩梦。【参考方案8】:

我不喜欢 checkBox + textView 的解决方案,因为您的自定义视图将扩展 ViewGroup 而不是 CheckBox 从而迫使您包装 CheckBox 行为。

自定义 CheckBox 可以在 xml 中使用,这对我来说很重要。

对我来说可接受的行为是,只有当您按下它的框而不是它的文本时才会切换此 CheckBox。

所以我扩展了 CheckBox,为了实现这种行为,我使用了整个触摸机制,下面是完整的代码,后面有一个解释,供任何想知道它是如何工作的人使用。

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();


它基于这样一个事实,即performClick方法由CheckBox扩展的TextView机制在内部调用,ClickableSpan也由TextView机制调用。 所以发生的情况是,当您触摸 CheckBox 的文本时,它会同时调用两者。

所以我所做的是检测点击是否在文本区域,如果是,我们将禁用 perfomClick 从而禁用切换。但仍会调用可点击的 span。

用法:

你仍然需要像以前一样添加一个可点击的 span 和 setMovementMethod,就像一个常规的 TextView。

【讨论】:

【参考方案9】:

如果您使用 URL 寻找解决方案,我建议您使用以下解决方案。使用CheckBoxTextView

    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);

在哪里 URL_TERMS_AND_CONDS = "yourUrl.com";R.string.terms_and_conds = 带有可点击字符串的资源的 ID。

【讨论】:

【参考方案10】:

这里有一个简单的代码 sn-p 可以在 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()

快乐编码:)

【讨论】:

【参考方案11】:

请试试这个

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());

【讨论】:

以上是关于如何使复选框文本的一部分可点击?的主要内容,如果未能解决你的问题,请参考以下文章

如何使复选框作为多选选项?

使复选框和单选标签可点击

使复选框和单选标签可点击

试图有一个链接+复选框,点击时可点击和文本都发生变化

如何更改复选框输入样式,以便用户点击标签而不是实际框?

如何对项目的复选框和文本以不同的方式处理点击事件? (PyQt/PySide/Qt)