实现市面上常见app的用户协议对话框的效果:

  1. 该对话框无法关闭
  2. 显示协议文本
  3. 文本中的超链接跳转到指定url
  4. 点不同意关闭app
  5. 点同意继续下一步
  6. 偏好设置:如果同意了下次来就不会显示对话框

对话框UI构建

几个点注意:

  1. 这个类是继承了我们自己封装的BaseDialogFragment(这个在Fragment通用控制器封装有详细介绍)
  2. Fragment 获取实例不能直接new,而是去 newInstance 方法里面构造。

codes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.fragment.app.FragmentManager;

import com.example.testandroid.R;
import com.example.testandroid.fragment.BaseDialogFragment;
import com.example.testandroid.superutils.ScreenUtil;

/**
* 服务条款和隐私协议对话框
*/
public class TermServiceDialogFragment extends BaseDialogFragment {



//修改对话框宽度的,如果不需要可以直接去掉
@Override
public void onResume() {
super.onResume();
//修改宽度,默认比AlertDialog.Builder 显示对话框宽度窄
ViewGroup.LayoutParams params = getDialog().getWindow().getAttributes();

params.width = (int) (ScreenUtil.getScreenWith(getContext()) * 0.9);
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
getDialog().getWindow().setAttributes((android.view.WindowManager.LayoutParams) params);
}

/**
* 显示对话框
* @param fragmentManager
* @param onAgreementClickListener 同意按钮点击回调
*/
public static void show(FragmentManager fragmentManager, View.OnClickListener onAgreementClickListener) {
//创建Fragment
TermServiceDialogFragment fragment = newInstance();
//展示
fragment.show(fragmentManager,"TermServiceDialogFragment");
}

/**
* 返回当前 DialogFragment 显示的布局
* @param inflater inflater
* @param container 容器
* @param savedInstanceState 保存的实例状态
* @return
*/
@Override
protected View getLayoutView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_dialog_term_service,container,false);
}
public static TermServiceDialogFragment newInstance() {

Bundle args = new Bundle();

TermServiceDialogFragment fragment = new TermServiceDialogFragment();
fragment.setArguments(args);
return fragment;
}
}

用到的时候展示

1
TermServiceDialogFragment.show(getSupportFragmentManager(),null);

对话框逻辑处理

  1. html处理&超链接
  2. 不能关闭
  3. 不同意按钮
  4. 同意回调外层界面

html处理

1
2
3
4
5
6
7
protected void initDatum() {
super.initDatum();
Spanned content = Html.fromHtml(getString(R.string.term_service_privacy_content));

SpannableStringBuilder result = SuperTextUtil.setHtmlLinkClick(content,null);
contentView.setText(result);
}

这里的 SuperTextUtil 类是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.example.testandroid.superutils.text;

import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.view.View;
import android.widget.TextView;

import androidx.annotation.NonNull;

/**
* 文本相关工具类
*/
public class SuperTextUtil {
/**
* 设置文本点击
*
* @param data html文本
* @param listener 这个文本点击的监听器
* @return 返回处理后的html文本
*/
public static SpannableStringBuilder setHtmlLinkClick(Spanned data, OnLinkClickListener listener) {
SpannableStringBuilder sb = new SpannableStringBuilder(data);
URLSpan[] spans = sb.getSpans(0, sb.length(), URLSpan.class);

for (URLSpan span : spans) {
int start = sb.getSpanStart(span);
int end = sb.getSpanEnd(span);
int flags = sb.getSpanFlags(span);

sb.setSpan(new SuperClickableSpan() {
@Override
public void onClick(@NonNull View widget) {
//这里拦截的没用
listener.onLinkClick(span.getURL());
}
}, start, end, flags);
}

return sb;
}

/**
* 设置富文本,超链接颜色
*
* @param view 超链接个体
* @param color 超链接的颜色
*/
public static void setLinkColor(TextView view, int color) {
//设置后才可以点击
view.setMovementMethod(LinkMovementMethod.getInstance());

//链接的颜色
view.setLinkTextColor(color);

}

/**
* 链接点击监听器
*/
public interface OnLinkClickListener {
void onLinkClick(String data);
}
}

上面只是显示html文本。

下面是这段SuperClickableSpan

里面是让超链接那个地方,去掉下划线。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.example.testandroid.superutils.text;

import android.text.TextPaint;
import android.text.style.ClickableSpan;

import androidx.annotation.NonNull;

/**
* 自定义ClickableSpan
* 目的是去除下划线
*/
public abstract class SuperClickableSpan extends ClickableSpan {
/**
* 更新绘制状态
*
* @param ds 哪一个绘制状态DrawState
*/
@Override
public void updateDrawState(@NonNull TextPaint ds) {

//只设置颜色
ds.setColor(ds.linkColor);

//去掉下划线
ds.setUnderlineText(false);
}

}

到这里,content文字就可以正常显示了。

且点击就会用系统默认浏览器打开 content 里面的超链接。

然后颜色设置为灰色:

1
SuperTextUtil.setLinkColor(contentView,getActivity().getColor(R.color.link));

这句放在 Fragment 的 initView 里面

到这里,content就完全处理好了

不能关闭

指的是在这个对话框弹出来的时候,点击对话框外or按返回键对话框会关闭。

1
2
3
4
5
6
7
8
9
10
public class TermServiceDialogFragment extends BaseDialogFragment {
@Override
protected void initViews() {
super.initViews();

//点击弹窗外不能关闭对话框
setCancelable(false);

}
}

不同意按钮

在 fragment 里面

1
2
3
4
5
6
7
8
9
10
11
12
13
protected void initListeners() {
super.initListeners();

//不同意按钮点击
disagree.setOnClickListener(view -> {
//关闭对话框
dismiss();

//杀死app
SuperProcessUtil.killApp();
});
}

这个杀死app是一个工具类:

1
2
3
4
5
6
7
8
9
10
11
/**
* 进程工具类
*/
public class SuperProcessUtil {
/**
* 杀死当前应用
*/
public static void killApp() {
Process.killProcess(Process.myPid());
}
}

同意按钮

这个如果同意了,结果想回调到外面去处理。

所以外面是这样的:image-20230528155704452

直接调用 fragment 里面的 show 方法。传一个监听器进去。

这个监听器在 fragment 里面被保存起来了。

image-20230528155817441

然后我们在 fragment 的initListeners

1
2
3
4
5
6
7
8
9
//同意按钮点击
primary.setOnClickListener(view -> {
//关闭对话框
dismiss();

//当用户点击同意之后,监听回调到外面的界面处理了。
onAgreementClickListener.onClick(view);

});

直接丢外面去了。

所以整个的一个流程就是:

  1. 外面在展示dialogfragment的时候就写一个监听器。
  2. fragment直接保存为类变量
  3. 在同意按钮被点的时候,调用这个监听器的onClick方法
  4. 这个onClick方法在外面实现的

这样就实现了,结果在外面监听。

本质就是传了一个监听器,然后合适的地方去调用监听器的onClick回到外面

阶段性成果

偏好设置

也就是说:

第一次来就要同意这个,如果已经同意了就不用再提示了。

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;

/**
* 偏好设置工具类
* 是否登录了,是否显示引导界面,用户Id
*/
public class DefaultPreferenceUtil {

/**
* 偏好设置文件名称
*/

private static final String KEY_MOBILE_NETWORK_PLAY = "mobile_network_play";

private static final String TERMS_SERVICE = "TERMS_SERVICE";

private static DefaultPreferenceUtil instance;
private final Context context;
private final SharedPreferences preference;

//region basicsetting
/**
* 构造方法
*
* @param context
*/
public DefaultPreferenceUtil(Context context) {
//保存上下文(getApplicationContext代表整个app,就脱离activity。避免内存泄漏)
this.context = context.getApplicationContext();

//这样写有内存泄漏
//因为当前工具类不会马上释放
//如果当前工具类引用了界面实例
//当界面关闭后
//因为界面对应在这里还有引用
//所以会导致界面对象不会被释放
//this.context = context;

//获取系统默认偏好设置,在设置界面保存的值就可以这样获取
preference = PreferenceManager.getDefaultSharedPreferences(this.context);

//自定义名称
//preference = this.context.getSharedPreferences(NAME, Context.MODE_PRIVATE);
}

/**
* 获取偏好设置单例
*
* @param context
* @return
*/
public synchronized static DefaultPreferenceUtil getInstance(Context context) {
//保存上下文
if (instance == null) {
instance = new DefaultPreferenceUtil(context);
}
return instance;
}
//endregion

/**
* 设置同意了用户协议
*/
public void setAcceptTermsServiceAgreement() {
putBoolean(TERMS_SERVICE, true);
}

/**
* 获取是否同意了用户条款
*
* @return
*/
public boolean isAcceptTermsServiceAgreement() {
return preference.getBoolean(TERMS_SERVICE, false);
}

/**
* 保存boolean
*
* @param key
* @param value
*/
private void putBoolean(String key, boolean value) {
preference.edit().putBoolean(key, value).apply();
}

}

业务处理

在SplashActivity界面

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void initDatum() {
super.initDatum();

if (DefaultPreferenceUtil.getInstance(getHostActivity()).isAcceptTermsServiceAgreement()){
//已经同意用户协议 -> 下一步
prepareNext();
}else {
//展示用户协议对话框
showTermsServiceAgreementDialog();
}
}

同时记得如果,同意按钮点击了,记得标记:

1
2
3
4
5
6
7
8
9
/**
* 显示同意服务条款对话框
*/
private void showTermsServiceAgreementDialog() {
TermServiceDialogFragment.show(getSupportFragmentManager(), view -> { DefaultPreferenceUtil.getInstance(getHostActivity()).setAcceptTermsServiceAgreement();
prepareNext();
});

}

实现结果