14、自定义认证UI:使用Fragment实现自定义界面、自定义动画、品牌化设计
说实话,系统自带的生物识别对话框虽然能用,但放在咱们自己的App里,总感觉有点格格不入。尤其是做品牌化设计的时候,那个默认弹窗就像穿了别人的衣服——怎么都不对劲。
我个人习惯是,只要涉及用户敏感操作,UI一定要跟App整体风格统一。这不仅是好看的问题,用户信任感也会更强。今天我们就聊聊怎么用Fragment把虹膜认证的UI完全掌控在自己手里。
为什么非要自己写UI?
系统提供的BiometricPrompt确实方便,但它的UI定制能力非常有限。你想想看:
- 不能改背景颜色
- 不能加自定义动画
- 按钮文字改不了
- Logo位置固定死
我在项目中遇到过客户要求「认证界面必须跟品牌色一致,还要有呼吸灯效果」。系统API?做不到。只能自己撸一个Fragment。
核心思路:用Fragment接管认证流程
说白了,就是把系统弹窗替换成我们自己的Fragment。认证逻辑还是走BiometricPrompt,但UI展示完全由Fragment控制。
关键点:Fragment只负责展示和交互,真正的认证结果还是要通过BiometricPrompt.AuthenticationCallback回调回来。
第一步:创建自定义认证Fragment
先写一个基础的Fragment布局。嗯,这里要注意,布局里至少要有:状态提示区、取消按钮、品牌Logo位。
<!-- fragment_iris_auth.xml -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F0FF">
<!-- 品牌Logo区域 -->
<ImageView
android:id="@+id/brand_logo"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center_horizontal|top"
android:layout_marginTop="60dp"
android:src="@drawable/ic_brand_logo" />
<!-- 虹膜扫描动画占位 -->
<View
android:id="@+id/iris_scan_anim"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="center"
android:background="@drawable/iris_scan_ring" />
<!-- 状态文字 -->
<TextView
android:id="@+id/tv_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="120dp"
android:text="请注视屏幕"
android:textColor="#333333"
android:textSize="16sp" />
<!-- 取消按钮 -->
<Button
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="40dp"
android:backgroundTint="#FF6B6B"
android:text="取消认证"
android:textColor="#FFFFFF" />
</FrameLayout>
第二步:Fragment中绑定认证回调
Fragment不能直接调用BiometricPrompt.authenticate(),需要把回调传进来。我习惯用接口的方式解耦。
public class IrisAuthFragment extends Fragment {
private AuthCallback authCallback;
private TextView tvStatus;
private View scanAnim;
// 定义回调接口
public interface AuthCallback {
void onCancel();
void onRetry();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_iris_auth, container, false);
tvStatus = view.findViewById(R.id.tv_status);
scanAnim = view.findViewById(R.id.iris_scan_anim);
view.findViewById(R.id.btn_cancel).setOnClickListener(v -> {
if (authCallback != null) authCallback.onCancel();
});
return view;
}
// 外部调用来更新状态
public void updateStatus(String text) {
if (tvStatus != null) {
tvStatus.setText(text);
}
}
public void setAuthCallback(AuthCallback callback) {
this.authCallback = callback;
}
}
第三步:自定义动画——让扫描动起来
系统弹窗没有动画,但我们可以自己加。我个人比较喜欢用属性动画做呼吸效果,看起来更自然。
// 在Fragment的onViewCreated中启动动画
private void startScanAnimation() {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(scanAnim, "scaleX", 1f, 1.15f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(scanAnim, "scaleY", 1f, 1.15f, 1f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(scanAnim, "alpha", 0.6f, 1f, 0.6f);
AnimatorSet set = new AnimatorSet();
set.playTogether(scaleX, scaleY, alpha);
set.setDuration(1500);
set.setInterpolator(new AccelerateDecelerateInterpolator());
set.setRepeatCount(ValueAnimator.INFINITE);
set.start();
}
我曾经踩过一个坑:动画在Fragment销毁时没有停止,导致内存泄漏。所以记得在onDestroyView()里调用set.cancel()。
第四步:品牌化设计——颜色、字体、Logo全掌控
品牌化说白了就是让用户一眼认出这是你的App。我建议至少统一三样东西:
| 元素 | 系统默认 | 自定义方案 |
|---|---|---|
| 背景色 | 白色/灰色 | 品牌主色渐变 |
| 按钮样式 | 圆角矩形 | 品牌圆角+阴影 |
| 状态文字 | 系统字体 | 自定义字体+品牌色 |
小技巧:把品牌Logo放在扫描圈上方,用户注视屏幕时自然能看到Logo,无形中强化品牌印象。
第五步:在Activity中组装
最后一步,把Fragment跟BiometricPrompt串起来。注意Fragment的显示时机——等系统准备好再展示。
public class MainActivity extends AppCompatActivity {
private IrisAuthFragment authFragment;
private BiometricPrompt biometricPrompt;
private void showCustomAuth() {
authFragment = new IrisAuthFragment();
authFragment.setAuthCallback(new IrisAuthFragment.AuthCallback() {
@Override
public void onCancel() {
biometricPrompt.cancelAuthentication();
dismissFragment();
}
@Override
public void onRetry() {
// 重新发起认证
startAuth();
}
});
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_in_up, R.anim.slide_out_down)
.add(R.id.fragment_container, authFragment)
.commit();
// 启动系统认证
startAuth();
}
private void startAuth() {
biometricPrompt.authenticate(
new BiometricPrompt.PromptInfo.Builder()
.setTitle("") // 标题留空,我们用Fragment展示
.setSubtitle("")
.setDescription("")
.setNegativeButtonText("") // 按钮也留空
.build()
);
}
}
注意:系统BiometricPrompt的setNegativeButtonText不能传空字符串,否则会崩溃。我一般传一个空格或者用setDeviceCredentialAllowed(true)绕过。
知识结构总览
下面这张图帮你理清自定义UI的核心流程:
避坑指南
做自定义UI的时候,有几个坑我替你们踩过了:
- Fragment重叠问题:如果用户快速点击取消再重试,可能会叠加多个Fragment。我习惯用
isAdded()判断一下再commit。 - 动画卡顿:属性动画别在
onCreate里启动,等View完全渲染好再动。加个postDelayed延迟100ms就行。 - 系统弹窗残留:自定义Fragment显示时,系统
BiometricPrompt的默认弹窗虽然看不见,但可能还占着屏幕。记得把系统Prompt的title和button都设成空。
总结一下:自定义UI的核心就三件事——Fragment布局、动画控制、回调桥接。把这三块理清楚,品牌化设计就是换皮的事。我自己的项目里,这套方案已经跑了三个大版本,用户反馈明显比系统弹窗更友好。