1.简介
android的自动填充功能自定义简单学习
1.1.如何实现
- 添加一个服务,继承AutofillService,需要重写两个方法,一个用来保存数据,一个用来读取数据
- 添加一个配置页面,非必须
- 清单文件配置,参考1.2
1.2.清单文件配置
- label就是自动填充服务显示的名字,不写的话就是app的名字,参考1.3.1的图片
- permission是必须的固定的
- action也是必须的固定的
- meta-data指定配置文件,非必须,指定配置页面,1.3.1的图片里会显示配置按钮,否则不显示
ini
<activity
android:name=".ui.MyAutofillSettings"
android:exported="true" />
<service
android:name=".service.MyAutofillService"
android:exported="true"
android:label="myautofill"
android:permission="android.permission.BIND_AUTOFILL_SERVICE">
<intent-filter>
<action android:name="android.service.autofill.AutofillService" />
</intent-filter>
<meta-data
android:name="android.autofill"
android:resource="@xml/service_configuration" />
</service>
>1.服务配置
service_configuration.xml
- 主要配置settingsActivity的值,就是我们自己写的配置页面
xml
<?xml version="1.0" encoding="utf-8"?>
<autofill-service xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.example.myapp2023.ui.MyAutofillSettings" />
1.3.查看
settings >> Passwords&accounts >> Autofill servcie
>1.图片
- 点击1进去可以选择不同的自动填充服务,
- 点击2就进入对应的填充服务的设置页面,就是1.2.1设置的页面
1.4.使用
>1.启用
- 先参考1.3.1把autofill service改成我们自己的,也可以代码里通过Intent跳转到对应的页面设置
- 随便一个页面的编辑框,如果设置了autofill属性,那么当编辑框聚焦的时候,就会触发2.1
1.5.测试
>1.布局
- EditText 添加 autofillHints
- EditText 添加 importantForAutofill,参考小节3
ini
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dp">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="user name"
android:autofillHints="username"
android:importantForAutofill="yes"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="password"
android:autofillHints="password"
android:importantForAutofill="yes"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_name" />
<Button
android:id="@+id/btn_submit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="submit"
android:layout_marginTop="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_password" />
</androidx.constraintlayout.widget.ConstraintLayout>
>2.保存数据方法
有两种:
- 退出页面,如果数据发生了变化,系统会自动弹框提示的,见补充3
- 手动操作,调用autofillManager的commit方法即可。
scss
val autofillManager =getSystemService(AutofillManager::class.java)
findViewById<View>(R.id.btn_submit).setOnClickListener {
autofillManager.commit()
}
>3.保存数据的弹框
点击update,那么会执行2.2的方法,如果点击No thanks,那么不会走2.2的方法
2.AutofillService
- 自定义的服务
kotlin
open class MyAutofillService : AutofillService() {
//key就是autofillHint的值,value就是编辑框的id,
private val fillId = HashMap<String, AutofillId>()
private lateinit var sp: SharedPreferences
private var saveSp = false //是否需要保存数据
override fun onCreate() {
super.onCreate()
sp = PreferenceManager.getDefaultSharedPreferences(baseContext);
}
2.1.onFillRequest
- SaveInfo就是设置我们要保存哪些数据,参考1.5.3标题会显示保存哪些数据
- Dataset就是我们要填充啥数据
- AutofillValue有四种类型,参考2.4以及3.3,这里就演示了下text的
scss
override fun onFillRequest(request: FillRequest, cancellationSignal: CancellationSignal, callback: FillCallback) {
saveSp = false
//只处理填充上下文集合的最后一条数据,就是我们当前的页面数据
parseStructure(request.fillContexts.last().structure)//参考2.3
val dataset = Dataset.Builder().apply {
var check = false
//这个就是提示视图,参考补充1
val presentation = RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
setTextViewText(android.R.id.text1, "myAutofill")
}
//找到username类型的AutofillId,如果本地有保存数据的话,那么设置数据
fillId[View.AUTOFILL_HINT_USERNAME]?.apply {
sp.getString(View.AUTOFILL_HINT_USERNAME, null)?.let {
//这里就是自动填充的数据,第一个是控件id,第二个是要填充的数据,第三个是提示框
setValue(this, AutofillValue.forText(it), presentation)
check = true
}
}
//同上
fillId[View.AUTOFILL_HINT_PASSWORD]?.apply {
sp.getString(View.AUTOFILL_HINT_PASSWORD, null)?.let {
setValue(this, AutofillValue.forText(it), presentation)
check = true
}
}
//数据为空,build方法会报错,所以这里加了个空数据
//你也可以先判断是否有已保存的数据再决定是否创建dataset
if (!check) {
setValue(fillId.values.last(), AutofillValue.forText(""))
}
}.build()
val fillResponse = FillResponse.Builder()
//SaveInfo,第一个参数是支持保存的数据类型,第二个参数是要保存的节点AutofillId集合
.setSaveInfo(SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME or SaveInfo.SAVE_DATA_TYPE_PASSWORD,
fillId.values.toTypedArray()).build()).addDataset(dataset).build()
callback.onSuccess(fillResponse)
}
>1.提示视图
随便点击一个支持autofill的编辑框,就会弹出我们设置的提示视图,如下图
>2.填充后效果
点击提示按钮后,数据就会被自动填充,填充后有个高亮的背景显示,这时候修改下数据,背景颜色就恢复了
2.2.onSaveRequest
- 这个方法就是用来保存数据的。
- 解析所有的节点,如果支持autofill,那么我们保存对应的数据,至于数据咋保存那是你的事了。
- 我这里测试用的sp
kotlin
override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
saveSp = true
//参考2.3,和2.1一样都是解析所有的节点,这里就是保存下数据
parseStructure(request.fillContexts.last().structure)
callback.onSuccess()
}
2.3.parseStructure
kotlin
private fun parseStructure(structure: AssistStructure) {
(0 until structure.windowNodeCount).forEach {
val node = structure.getWindowNodeAt(it).rootViewNode
parseNode(node)
}
}
private fun parseNode(node: ViewNode) {
//先看节点是否设置了自动填充提示
if (node.autofillHints?.isNotEmpty() == true) {
node.autofillHints?.forEach {
if (saveSp) {//需要保存数据的时候,获取节点的数据进行保存
node.text?.apply {
sp.edit().putString(it, this.toString()).commit()
}
}
node.autofillId?.apply {
fillId.put(it, this)//把节点的autofillHint和id放入map
}
}
}
//迭代解析
if (node.childCount > 0) {
(0 until node.childCount).forEach {
parseNode(node.getChildAt(it))
}
}
}
2.4.AutofillValue
>1.forText
less
public static AutofillValue forText(@Nullable CharSequence value) {
return value == null ? null : new AutofillValue(AUTOFILL_TYPE_TEXT,
TextUtils.trimNoCopySpans(value));
}
>2.forToggle
typescript
public static AutofillValue forToggle(boolean value) {
return new AutofillValue(AUTOFILL_TYPE_TOGGLE, value);
}
>3.forList
arduino
public static AutofillValue forList(int value) {
return new AutofillValue(AUTOFILL_TYPE_LIST, value);
}
>4.forDate
arduino
public static AutofillValue forDate(long value) {
return new AutofillValue(AUTOFILL_TYPE_DATE, value);
}
3.View
3.1.autofillHint
java
//邮件地址
public static final String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress";
//a user's real name.
public static final String AUTOFILL_HINT_NAME = "name";
//用户名
public static final String AUTOFILL_HINT_USERNAME = "username";
public static final String AUTOFILL_HINT_PASSWORD = "password";
//电话号码
public static final String AUTOFILL_HINT_PHONE = "phone";
//邮件地址
public static final String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress";
//邮政编码
public static final String AUTOFILL_HINT_POSTAL_CODE = "postalCode";
//信用卡号
public static final String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber";
//信用卡安全吗
public static final String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode";
//信用卡过期时间,这是个日期,和下边的年月日分开的二选一
public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE =
"creditCardExpirationDate";
//信用卡过期时间,月份,建议用数字,从1开始
public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH =
"creditCardExpirationMonth";
//信用卡过期时间,年,
public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR =
"creditCardExpirationYear";
//信用卡过期时间,日
public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay";
//自动密码,就是说用户没有设置上边的AUTOFILL_HINT_PASSWORD,系统自己添加的这个
public static final String AUTOFILL_HINT_PASSWORD_AUTO = "passwordAuto";
3.2.IMPORTANT_FOR_AUTOFILL
arduino
//系统自己决定
public static final int IMPORTANT_FOR_AUTOFILL_AUTO = 0x0;
//当前view以及遍历它的child都是支持autofill
public static final int IMPORTANT_FOR_AUTOFILL_YES = 0x1;
//此视图不需要自动填充,但是遍历它的child
public static final int IMPORTANT_FOR_AUTOFILL_NO = 0x2;
//自己支持自动填充,不包括它的child
public static final int IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS = 0x4;
//此视图不支持自动填充,不包括它的child
public static final int IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS = 0x8;
3.3.AutofillType
less
@IntDef(prefix = { "AUTOFILL_TYPE_" }, value = {
AUTOFILL_TYPE_NONE,
AUTOFILL_TYPE_TEXT,
AUTOFILL_TYPE_TOGGLE,
AUTOFILL_TYPE_LIST,
AUTOFILL_TYPE_DATE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AutofillType {}
>1.View
默认的自动填充类型是NONE,其他子类覆写这个方法
csharp
public @AutofillType int getAutofillType() {
return AUTOFILL_TYPE_NONE;
}
>2.RadioGroup|AbsSpinner
csharp
public @AutofillType int getAutofillType() {
return isEnabled() ? AUTOFILL_TYPE_LIST : AUTOFILL_TYPE_NONE;
}
>3.TextView
csharp
public @AutofillType int getAutofillType() {
return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
}
>4.CompoundButton
csharp
public @AutofillType int getAutofillType() {
return isEnabled() ? AUTOFILL_TYPE_TOGGLE : AUTOFILL_TYPE_NONE;
}
>5.DatePicker|TimePicker
csharp
public @AutofillType int getAutofillType() {
return isEnabled() ? AUTOFILL_TYPE_DATE : AUTOFILL_TYPE_NONE;
}