Android 8 (Oreo, SDK 26)增加了自動填入的框架,使我們在填寫表單時更快速較不易出錯。
本篇中使用基礎元件(EditText, SharedPreferences),來完成自動填入的功能。
Step 1: 畫面布局
在主畫面中,我們只使用了一個EditText來實做。
activity_main.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">
<EditText android:id="@+id/editText" android:layout_width="348dp" android:layout_height="79dp" android:ems="10" android:inputType="textPersonName" android:text="Name" android:autofillHints="user_name" android:importantForAutofill="yes" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
|
值得注意的是android:autofillHints, android:importantForAutofill這兩個屬性。
android:autofillHint: 註明autofill的tag,tag內容可以自己定義,也可以使用Android 官方文件HintConstants所定義的tag, 共有38個種類。
android:importantForAutofill : 自動填入服務
此屬性內容值說明如下
- IMPORTANT_FOR_AUTOFILL_AUTO 預設值, 由系統決定是否使用自動填入
- IMPORTANT_FOR_AUTOFILL_NO 不使用自動填入,但是子view可以使用
- IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS 不使用自動填入,子view也不使用
- IMPORTANT_FOR_AUTOFILL_YES 使用自動填入,包括子view
- IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS 使用自動填入,但子view不使用
Step 2: 創建AutofillService
新增class MyAutofillService繼承AutofillService,並實現兩個函式onFillRequest及onSaveRequest
onFillRequest為執行自動填入時所呼叫的函式,在此文章中會介紹當執行自動填入時,顯示視窗讓使用者選擇填入的值。
onSaveRequest為保存自動填入的內容值所呼叫。
MyAutofillService.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class MyAutofillService extends AutofillService { private String TAG = "Nick AutofillService";
@Override public void onFillRequest(@NonNull FillRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback fillCallback) { Log.e(TAG, "onFillRequest"); }
@Override public void onSaveRequest(@NonNull SaveRequest request, @NonNull SaveCallback callback) { } }
|
在AndroidManifest.xml中宣告此Service
AndroidManifest.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.nickthomas55.autofill.test">
<application ... > <activity android:name=".MainActivity"> ... </activity> <service android:name=".MyAutofillService" android:label="Autofill Service Test" android:permission="android.permission.BIND_AUTOFILL_SERVICE">
<meta-data android:name="android.autofill" android:resource="@xml/autofill_filler"/> <intent-filter> <action android:name="android.service.autofill.AutofillService"/> </intent-filter> </service> </application> </manifest>
|
在使用自動填入服務之前,我們必須先開啟自動填入服務的功能。
從[設定->系統->語言與輸入設定->進階->自動填入服務]
在此頁面中可看見我們在AndroidManifest.xml中所定義的android:label名稱,選取此服務為自動填入服務。
在Project res資料夾中新增xml 資料夾,並在此資料夾中新增autofill_filler.xml
內容如下
autofill_filler.xml1 2 3 4
| <?xml version="1.0" encoding="utf-8"?> <autofill-service xmlns:android="http://schemas.android.com/apk/res/android" android:settingsActivity="[your package name].MainActivity"/>
|
Step 3: 初始化
當程式一開始時, 我們先在SharedPreferences中加入自動填入的內容。
宣告AutofillManager和AutofillCallback,在onResume與onPause註冊與解註冊callback。
MainActivity.java1 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
| public class MainActivity extends AppCompatActivity {
private AutofillManager mAutofillManager; private MyAutofillCallback mAutofillCallback;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
init();
mAutofillManager = this.getSystemService(AutofillManager.class); mAutofillCallback = new MyAutofillCallback(); }
@Override protected void onResume() { super.onResume(); mAutofillManager.registerCallback(mAutofillCallback); }
@Override protected void onPause() { super.onPause(); mAutofillManager.unregisterCallback(mAutofillCallback); }
private void init() { SharedPreferences.Editor editor = getSharedPreferences("People_Info", MODE_PRIVATE).edit(); editor.putString("user_name", "nick"); editor.commit(); }
private class MyAutofillCallback extends AutofillManager.AutofillCallback { @Override public void onAutofillEvent(@NonNull View view, int event) { super.onAutofillEvent(view, event);
}
@Override public void onAutofillEvent(@NonNull View view, int virtualId, int event) { super.onAutofillEvent(view, virtualId, event); } } }
|
Step 4: 取得自動填入的元件
在執行自動填入和保存自動填入資料時,我們必須先取得自動填入的元件。
MyAutofillService.java1 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
| public class MyAutofillService extends AutofillService { private String TAG = "Nick AutofillService";
@Override public void onFillRequest(@NonNull FillRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback fillCallback) { Log.e(TAG, "onFillRequest"); List<FillContext> context = request.getFillContexts(); AssistStructure structure = context.get(context.size() - 1).getStructure();
List<AssistStructure.ViewNode> fields = new ArrayList<>(); traverseStructure(structure, fields); }
@Override public void onSaveRequest(@NonNull SaveRequest request, @NonNull SaveCallback callback) { Log.e(TAG, "onSaveRequest"); List<FillContext> context = request.getFillContexts(); AssistStructure structure = context.get(context.size() - 1).getStructure(); List<AssistStructure.ViewNode> fields = new ArrayList<>(); traverseStructure(structure, fields); }
private void traverseStructure(AssistStructure structure, List<AssistStructure.ViewNode> fields) { int nodes = structure.getWindowNodeCount();
for (int i = 0; i < nodes; i++) { AssistStructure.WindowNode windowNode = structure.getWindowNodeAt(i); AssistStructure.ViewNode viewNode = windowNode.getRootViewNode(); traverseNode(viewNode, fields); } }
private void traverseNode(AssistStructure.ViewNode viewNode, List<AssistStructure.ViewNode> fields) { if (viewNode == null || viewNode.getClassName() == null) return;
if (viewNode.getAutofillHints() != null && viewNode.getAutofillHints().length > 0) { fields.add(viewNode); }
for (int i = 0; i < viewNode.getChildCount(); i++) { AssistStructure.ViewNode childNode = viewNode.getChildAt(i); traverseNode(childNode, fields); } } }
|
從SharedPreferences, DB, 或文件中取得自動填入的資料內容
MyAutofillService.java1 2 3
| SharedPreferences sharedPreferences = getSharedPreferences("People_Info", MODE_PRIVATE); String userName = sharedPreferences.getString("user_name", "");
|
設定一個浮動視窗來顯示資料內容
MyAutofillService.java1 2 3 4
| RemoteViews afRemoteView = new RemoteViews(getPackageName(), R.layout.user_suggestion); afRemoteView.setTextViewText(R.id.user_suggestion_item, userName); afRemoteView.setImageViewResource(R.id.icon, R.mipmap.ic_launcher_round);
|
user_suggestion_item.xml 畫面設定如下
user_suggestion_item.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?xml version="1.0" encoding="utf-8"?> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:id="@+id/user_suggestion_item" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="5dp" android:textSize="18sp" android:textStyle="bold"/>
<ImageView android:id="@+id/icon" android:layout_gravity="center" android:layout_height="wrap_content" android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd" android:layout_width="wrap_content" android:src="@drawable/people" /> </LinearLayout>
|
將浮動視窗和自動填入元件做Mapping
MyAutofillService.java1 2 3 4 5 6
| AssistStructure.ViewNode userField = fields.get(0); Dataset primaryEmailDataSet = new Dataset.Builder(afRemoteView) .setValue(userField.getAutofillId(), AutofillValue.forText(userName)) .build();
|
封裝返回數據並通知自動填入框架
MyAutofillService.java1 2 3 4 5 6 7
| FillResponse response = new FillResponse.Builder() .addDataset(primaryEmailDataSet) .build();
fillCallback.onSuccess(response);
|
Source Code
MainActivity.java請參考上面內容,以下為MyAutofillService.java完整內容。
MyAutofillService.java1 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
| public class MyAutofillService extends AutofillService { private String TAG = "Nick AutofillService";
@Override public void onFillRequest(@NonNull FillRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback fillCallback) { Log.e(TAG, "onFillRequest"); List<FillContext> context = request.getFillContexts(); AssistStructure structure = context.get(context.size() - 1).getStructure();
List<AssistStructure.ViewNode> fields = new ArrayList<>(); traverseStructure(structure, fields);
SharedPreferences sharedPreferences = getSharedPreferences("People_Info", MODE_PRIVATE); String userName = sharedPreferences.getString("user_name", "");
RemoteViews afRemoteView = new RemoteViews(getPackageName(), R.layout.user_suggestion); afRemoteView.setTextViewText(R.id.user_suggestion_item, userName); afRemoteView.setImageViewResource(R.id.icon, R.mipmap.ic_launcher_round);
AssistStructure.ViewNode userField = fields.get(0); Dataset primaryEmailDataSet = new Dataset.Builder(afRemoteView) .setValue(userField.getAutofillId(), AutofillValue.forText(userName) ).build();
FillResponse response = new FillResponse.Builder() .addDataset(primaryEmailDataSet) .build(); fillCallback.onSuccess(response); }
@Override public void onSaveRequest(@NonNull SaveRequest request, @NonNull SaveCallback callback) { Log.e(TAG, "onSaveRequest"); List<FillContext> context = request.getFillContexts(); AssistStructure structure = context.get(context.size() - 1).getStructure(); List<AssistStructure.ViewNode> fields = new ArrayList<>(); traverseStructure(structure, fields); callback.onSuccess(); }
private void traverseStructure(AssistStructure structure, List<AssistStructure.ViewNode> fields) { int nodes = structure.getWindowNodeCount();
for (int i = 0; i < nodes; i++) { AssistStructure.WindowNode windowNode = structure.getWindowNodeAt(i); AssistStructure.ViewNode viewNode = windowNode.getRootViewNode(); traverseNode(viewNode, fields); } }
private void traverseNode(AssistStructure.ViewNode viewNode, List<AssistStructure.ViewNode> fields) { if (viewNode == null || viewNode.getClassName() == null) return;
if (viewNode.getAutofillHints() != null && viewNode.getAutofillHints().length > 0) { fields.add(viewNode); }
for (int i = 0; i < viewNode.getChildCount(); i++) { AssistStructure.ViewNode childNode = viewNode.getChildAt(i); traverseNode(childNode, fields); } } }
|
執行結果