我们现在要做的一件事情:当在登录界面的时候,点击“《用户协议》”的时候,能够进行页面跳转,而非勾选协议。
这里的思路其实也很好理解,我们把勾选框右侧的文本中的“《用户协议》”这几个字符进行特殊处理即可

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".CheckBoxActivity">
<EditText
android:id="@+id/et_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入用户名"
android:inputType="text"
android:maxLines="16" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword"
android:maxLength="16" />
<CheckBox
android:id="@+id/cb_agreement"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="勾选,表示同意《用户协议》"
android:textSize="16sp" />
<Button
android:id="@+id/btn_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="登录" />
</LinearLayout> //拿到xml中定义的CheckBox
CheckBox cbArgreement = findViewById(R.id.cb_agreement);
/**
* 处理富文本
* 1:获取文本
* 2:改成超链接
* 3:设置点击后的监听事件,效果为页面跳转
*/
String text = cbArgreement.getText().toString();//勾选表示同意《用户协议》
SpannableString spannableString = new SpannableString(text);
ClickableSpan clickableSpan = new ClickableSpan() {
//设置一个点击事件监听事件
@Override
public void onClick(@NonNull View widget) {
Toast.makeText(CheckBoxActivity.this,"假装显示了一个用户协议",Toast.LENGTH_SHORT).show();
}
};
spannableString.setSpan(clickableSpan,7,13, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
cbArgreement.setText(spannableString);
//允许文本控件显示一个可以点击的链接
cbArgreement.setMovementMethod(LinkMovementMethod.getInstance());//这行代码才是精髓
//但是也暴露出来了一个问题,点击《用户协议》这个超链接的时候会触发CheckBox的监听,这明显是不太合理的上面这段代码非常折磨,我们慢慢一点一点分析,啃下来,加油
①获取CheckBox中的文本
②设置一个监听事件
③Span把标记文本和监听器关联在一起
④设置一个超链接(提问,传入的是整个text文本,最后怎么会只有“《用户协议》”成了超链接,不明白)
⑤最后一行代码固定用法(也不太懂)
在点击《用户协议》的时候会有弹窗:假装显示了一个用户协议


译为字符序列
charSequence是一个接口,代表一段可以被读取的字符序列,常见的实现类
String:不可变的字符序列(一旦创建就不能修改)。StringBuilder:可变的字符序列(可动态增删改,但只能修改字符内容,不能加样式)。SpannableString:带「标记」功能的可变字符序列(不仅能存字符,还能给指定范围的字符添加样式、点击事件等)。

span是SpannabledString的绝技,可以译为标记
// 1. 先拿到原始字符串(比如 CheckBox 上的文本 "勾选,表示同意《用户协议》" )
String string = cbAgreement.getText().toString();
// 2. 用原始字符串创建 SpannableString 对象
SpannableString spannableString = new SpannableString(string); //1:Android当中专门处理富文本效果
SpannableString spannableString = new SpannableString(text);创建SpannableString对象接收了一个String类型的静态文本;这一步其实就是把“普通的字符串”包装成一个“富文本容器”,这样后续就可以通过spannableString.setSpan(...)方法,对这个容器当中的某一段文字(通过 start、end 索引指定范围 )添加各种特殊效果(比如点击事件、颜色 )。
Android 里的 TextView、CheckBox 等控件,虽然能显示普通文本,但如果想让部分文字有特殊样式或交互(比如 “用户协议” 可点击 ),直接用 String 做不到。
SpannableString 就是专门用来解决这个问题的:先把普通字符串包装成 SpannableString ,再给局部加效果,最后设置回控件显示,就能实现 “富文本” 效果。
类比:按钮监听和CheckBox监听
//按钮
Button loginButton = findViewById(R.id.btn_login);
loginButton.setOnClickListener(new View.OnClickListener() {setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//这里按钮的监听器的相关代码
}
});
//CheckBox复选框
CheckBox cbArgreement = findViewById(R.id.cb_agreement);
cbArgreement.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
}
}); ClickableSpan clickableSpan = new ClickableSpan() {
//设置一个点击事件监听事件
@Override
public void onClick(@NonNull View widget) {
Toast.makeText(CheckBoxActivity.this,"假装显示了一个用户协议",Toast.LENGTH_SHORT).show();
}
};设置弹窗并显示
spannableString.setSpan(clickableSpan,7,13, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
what 被标记的标记(这里就是clickableSpan)
起始下标和结束下标 左闭右开区间 [start,end) 初始范围的规则 跟最后一个参数flags的区间没有关系
exclusive 包含 inclusive 不包含
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE(最常用)区间为:左开右开
举例说明:
假设你给 《用户协议》 设了 SPAN_EXCLUSIVE_EXCLUSIVE,然后在 《 前面插入一个 “淦”,那么新插入的 “淦”不会被算作 Span 的一部分,只有原来的 《用户协议》 仍保持 Span 效果。
注:原来的 《 索引从 7 变成了 8,但 《用户协议》 本身的 Span 范围会自动调整为 8 ≤ 索引 < 14(跟着字符移动),依然保持标记。
我们一般也希望,编辑后的文本不要影响到之前的span标记,flags参数采用的多为左开右开区间。(实操效果很难展示出来,略)
cbArgreement.setText(spannableString);
//允许文本控件显示一个可以点击的链接
cbArgreement.setMovementMethod(LinkMovementMethod.getInstance());//这行代码才是精髓我们设置好了spannableString(包括文本样式,事件标记,监听事件),现在需要把它通过setText()设置给CheckBox(TextView父类),TextView就会根据设置进行渲染。
setMovementMethod() 为CheckBox设置文本移动方法,使超链接能够响应文本监听器
LinkMovementMethod.getInstance() 获取实例,返回一个单例对象
点击用户协议,勾选功能也被调用了。说明两个事件被同一个监听器监听了,我们要把他俩给分开,思路把xml中TextView和CheckBox给分开装饰,再在Java中分别处理监听逻辑

主要是把CheckBox和TextView放到一个LinearLayout线性布局下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".CheckBoxActivity">
<EditText
android:id="@+id/et_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入用户名"
android:inputType="text"
android:maxLines="16" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword"
android:maxLength="16" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/cb_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="勾选,表示同意"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_agreement"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="《用户协议》"
android:textSize="16sp" />
</LinearLayout>
<Button
android:id="@+id/btn_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="登录" />
</LinearLayout>注意第十七行代码;太恶心了,猜测原因:start和end的设置越界,所以导致空指针异常,进而跳转不到登录的页面(一点击按钮就闪退),一个块一个块排查出来的。
spannableString.setSpan(clickableSpan,0,text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); /**
* 处理富文本
* 1:获取文本
* 2:改成超链接
* 3:设置点击后的监听事件,效果为页面跳转
*/
TextView tv_agreement = findViewById(R.id.tv_agreement);
String text = tv_agreement.getText().toString();//《用户协议》
SpannableString spannableString = new SpannableString(text);
ClickableSpan clickableSpan = new ClickableSpan() {
//设置一个点击事件监听事件
@Override
public void onClick(@NonNull View widget) {
Toast.makeText(CheckBoxActivity.this,"假装显示了一个用户协议",Toast.LENGTH_SHORT).show();
}
};
spannableString.setSpan(clickableSpan,0,text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv_agreement.setText(spannableString);
//允许文本控件显示一个可以点击的链接
tv_agreement.setMovementMethod(LinkMovementMethod.getInstance());//这行代码才是精髓
//但是也暴露出来了一个问题,点击《用户协议》这个超链接的时候会触发CheckBox的监听,这明显是不太合理的
/**
* 实时监听勾选状态
*/
CheckBox cb_box = findViewById(R.id.cb_box);
cb_box.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
//方式三
if(isChecked){
Toast.makeText(CheckBoxActivity.this,"感谢您勾选协议",Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(CheckBoxActivity.this,"请勾选协议",Toast.LENGTH_SHORT).show();
}
}
});