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()
        );
    }
}

注意:系统BiometricPromptsetNegativeButtonText不能传空字符串,否则会崩溃。我一般传一个空格或者用setDeviceCredentialAllowed(true)绕过。

知识结构总览

下面这张图帮你理清自定义UI的核心流程:

自定义认证UI核心流程 Fragment布局 品牌Logo + 扫描圈 + 状态文字 自定义动画 属性动画 + 呼吸效果 品牌化设计 颜色 / 字体 / Logo统一 Fragment生命周期管理 onCreateView → onViewCreated → onDestroyView BiometricPrompt回调桥接 onAuthenticationSucceeded / onError → 更新UI状态

避坑指南

做自定义UI的时候,有几个坑我替你们踩过了:

  • Fragment重叠问题:如果用户快速点击取消再重试,可能会叠加多个Fragment。我习惯用isAdded()判断一下再commit。
  • 动画卡顿:属性动画别在onCreate里启动,等View完全渲染好再动。加个postDelayed延迟100ms就行。
  • 系统弹窗残留:自定义Fragment显示时,系统BiometricPrompt的默认弹窗虽然看不见,但可能还占着屏幕。记得把系统Prompt的title和button都设成空。

总结一下:自定义UI的核心就三件事——Fragment布局、动画控制、回调桥接。把这三块理清楚,品牌化设计就是换皮的事。我自己的项目里,这套方案已经跑了三个大版本,用户反馈明显比系统弹窗更友好。

公众号:蓝海资料掘金营,微信deep3321