android13#autofill

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.保存数据方法

有两种:

  1. 退出页面,如果数据发生了变化,系统会自动弹框提示的,见补充3
  2. 手动操作,调用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;
    }
相关推荐
千里马学框架6 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台6 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone6 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc6 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo6 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077006 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼6 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone6 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen6 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone6 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui