
简介这是一份安卓智能锁屏界面的完整工程源码包面向安卓应用开发者及界面交互设计人员解决自定义锁屏界面、解锁交互、通知预览与安全管理等实战落地问题。资源共57个文件压缩包约722千字节其中包含源码与编译后的类文件共24个、界面布局与配置11个、图片素材9张另有依赖库、安卓安装包和工程配置文件可对照代码与界面布局快速理解整体实现。从内容预览来看工程采用标准安卓项目结构设有源码、资源、素材、库和输出等目录并已生成可直接安装的安装包方便运行验证。对于想学习动态壁纸、自定义解锁动画、快捷功能或系统接口集成的开发者这份代码提供了完整参考也有助于掌握锁屏性能优化与第三方工具库的用法。目前已有249人学习下载适合有一定安卓基础并希望深入锁屏定制开发的学习者也可作为移动应用课程设计或毕业设计的参考模板。1. Android 智能UI锁屏这个资源能做什么以及为什么值得自己动手拆手机锁屏是每台 Android 设备上被打开次数最多的界面但系统自带锁屏往往只有时钟、通知和快捷开关既改不了交互逻辑也接不进自己的业务场景。这里要拆的项目「Android 智能UI锁屏」本质上是一个完整的自定义锁屏 Demo它通过监听系统屏幕状态、接管锁屏展示层在屏幕上渲染一套可自绘的 UI能显示时间、通知、电量并支持滑动手势解锁。对做定制 ROM、企业设备管理、或者单纯想给自己的 App 加一层安全入口的开发者来说这套资源的价值在于把「锁屏」这个系统级交互从黑匣子变成了可改写的工程样本。适合已有 Android 基础、想搞懂锁屏原理或者正在为某跨平台系统做定制 UI 的从业者不建议零基础的人拿它当第一个练手项目。我建议你把它当成一份工程模板来读而不是一个开箱即用的成品。2. 锁屏的活法前台服务、通知监听与应用白名单2.1 锁屏 App 的存活策略为什么普通 Service 活不过锁屏自定义锁屏要稳定运行第一步不是画界面而是保证进程在屏幕关闭后不被系统回收。Android 的 Doze 模式和厂商后台清理策略会不断杀死后台 Service这是一个长期痛点。常见做法是使用前台服务Foreground Service 通知栏常驻通过启动服务时调用startForeground(id, notification)让系统知道这个服务是用户可感知的同时引导用户将应用加入电池优化白名单。需要说明的是Android 12API 31之后从后台启动前台服务有限制通常需要在用户可见的 Activity 中启动服务或者依赖SYSTEM_ALERT_WINDOW权限来规避部分限制。我一般会把服务启动放在 MainActivity 的首帧回调里而不是 Application 的onCreate()避免冷启动时因后台限制被系统拒绝。若要在开机后自动拉起锁屏可以注册一个BOOT_COMPLETED广播接收器但部分厂商 ROM 默认禁止应用自启动需要引导用户在系统设置里手动允许这一步无法通过代码绕过。2.2 布局FrameLayout 叠加层与 WindowManager 的取舍绘制锁屏 UI 有两种常见路径一是使用WindowManager.addView()添加TYPE_APPLICATION_OVERLAY悬浮窗二是直接在当前 Activity 的布局里显示一个全屏 Fragment。前者能覆盖在系统锁屏之上但需要SYSTEM_ALERT_WINDOW权限且悬浮窗在部分机型上会出现层级问题后者实现简单但无法覆盖系统锁屏只能作为应用内锁使用。这个 Demo 采用的是第二种思路——通过两个 Activity 的组合模拟锁屏一个透明主题的 LockActivity 作为锁屏展示层一个 MainActivity 作为桌面层。在onResume()中判断锁屏状态并决定是否跳转。这样做的好处是规避了悬浮窗带来的兼容性问题适合做成应用内锁缺点是无法替代系统锁屏。以下代码展示了 LockActivity 的核心配置public class LockActivity extends Activity { Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 保持屏幕常亮锁屏期间不自动熄屏 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // 隐藏系统状态栏保证 UI 全屏 getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); setContentView(R.layout.activity_lock); } }这里的FLAG_KEEP_SCREEN_ON对功耗有较大影响长时间亮屏会加快电池消耗。实际项目中我会增加一个亮度衰减策略当屏幕无操作超过 30 秒时使用WindowManager.LayoutParams.screenBrightness将亮度从 1.0 降到 0.2模拟系统锁屏的自动调暗效果。SYSTEM_UI_FLAG_HIDE_NAVIGATION在不同 Android 版本上有兼容差异建议在 Android 11 及以上使用WindowInsetsController配合WindowInsets.Type.navigationBars()实现同样的效果这部分后面会在避坑章里细说。2.3 通知监听读取通知内容与图标智能锁屏的核心功能之一是展示通知。这里需要引入NotificationListenerService通过它监听系统通知的发布与移除再把通知内容推送给 LockActivity 进行渲染。需要在AndroidManifest.xml中声明服务并配置android:permissionandroid.permission.BIND_NOTIFICATION_LISTENER_SERVICE同时在onCreate()里检测用户是否在系统设置中给应用授予了「通知使用权」。这个权限默认是关闭的必须引导用户手动开启代码里无法静默授权。service android:name.service.NotificationCaptureService android:permissionandroid.permission.BIND_NOTIFICATION_LISTENER_SERVICE android:exportedtrue intent-filter action android:nameandroid.service.notification.NotificationListenerService / /intent-filter /servicepublic class NotificationCaptureService extends NotificationListenerService { private static final String TAG NotifCapture; // 消息到达时回调texts 里包含通知内容 Override public void onNotificationPosted(StatusBarNotification sbn) { if (sbn null) return; Bundle extras sbn.getNotification().extras; String title extras.getString(Notification.EXTRA_TITLE, ); String text extras.getString(Notification.EXTRA_TEXT, ); // 通过 LocalBroadcast 或 LiveData 把数据推给 UI 层 Intent intent new Intent(com.lock.NOTIF_ARRIVE); intent.putExtra(title, title); intent.putExtra(text, text); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } Override public void onNotificationRemoved(StatusBarNotification sbn) { // 移除通知时同步刷新锁屏列表 Intent intent new Intent(com.lock.NOTIF_REMOVE); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } }这段代码的LocalBroadcastManager在新版本 AndroidX 中被标记为废弃官方推荐使用LiveData或Flow替代。这里保留它是为了降低 Demo 的理解成本。sbn.getNotification().extras取通知标题和正文是最常用的方式但要注意部分应用比如微信的通知正文可能被折叠EXTRA_TEXT可能拿不到完整消息需要配合EXTRA_SUB_TEXT或自定义视图的RemoteViews来补充。onNotificationRemoved在用户手动清除通知或应用主动取消通知时触发锁屏界面的通知列表需要在这个回调中做增量更新如果实现不严谨会出现通知已消失但锁屏上还挂着旧消息的问题。3. 画一个能滑动的锁屏从自定义 View 到手势阈值3.1 时间与状态的绘制Canvas 自绘还是 XML 布局锁屏 UI 可以通过 XML 布局搭建也可以完全用自定义 View 绘制。两种方式差别很大XML 布局适合快速修改排版结构清晰但要在锁屏动画或指纹解锁反馈时做复杂形变就会捉襟见肘自定义 View 绘制则更灵活所有元素时间、日期、电量、通知圆角卡片都走 Canvas 代码可以统一做缩放、位移动画。Demo 采用的是「XML 布局 少量自定义 View」的折中方案时间的数字部分用系统字体直接渲染通知列表用 RecyclerView 承载而滑动手势区域用一个自定义的SlideUpView控制解锁反馈。时间显示部分我一般会在TimeTickReceiver里刷新而不是每秒开一个Handler循环。注册一个每分钟响一次的广播接收器配合SimpleDateFormat格式化时间能大幅降低锁屏期间的电量消耗。以下是一个时间刷新的代码片段public class TimeReceiver extends BroadcastReceiver { // 每分钟刷新一次避免过度刷新造成功耗 Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) { TextView tv ((Activity) context).findViewById(R.id.tv_time); SimpleDateFormat sdf new SimpleDateFormat(HH:mm, Locale.CHINA); tv.setText(sdf.format(new Date())); } } }注册ACTION_TIME_TICK需要在代码里动态注册不能在 Manifest 中静态声明。SimpleDateFormat每次setText都 new 一个实例也是不必要的开销业务量大时可以把它提为成员变量。锁屏场景下的时间格式推荐HH:mm不带秒避免无意义地频繁刷新。电量信息可以通过ACTION_BATTERY_CHANGED广播获取这个广播同样需要动态注册并且可以携带电量百分比和充电状态。3.2 手势解锁从滑动距离到触发阈值解锁手势是锁屏的交互核心。系统锁屏通常采用「底部上滑」或「侧滑」两种方式。Demo 里用的是垂直滑动解锁手指从底部向上拖动一个解锁按钮当滑动距离超过设定的阈值时触发解锁动画然后跳转到桌面未超过阈值则回弹。这里有一个关键设计——回弹动画要跟手否则用户体验会很生硬。我一般在ACTION_MOVE里记录Y轴偏移并在ACTION_UP时判断偏移量是否大于阈值。SlideUpView的核心实现如下public class SlideUpView extends View { private float downY; private float offsetY; private static final float TRIGGER_DISTANCE 300f; // 解锁触发阈值单位 px private boolean isUnlocked false; public SlideUpView(Context context, AttributeSet attrs) { super(context, attrs); } Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downY event.getY(); return true; case MotionEvent.ACTION_MOVE: // 只响应向上的滑动向下不改变位置 offsetY Math.max(0, downY - event.getY()); float progress offsetY / TRIGGER_DISTANCE; // 把偏移量同步给外部更新 UI 位置 setTranslationY(-offsetY); setAlpha(1 - progress * 0.5f); return true; case MotionEvent.ACTION_UP: if (offsetY TRIGGER_DISTANCE !isUnlocked) { isUnlocked true; performUnlock(); } else { // 回弹复位 animate().translationY(0).alpha(1f).setDuration(200).start(); } return true; } return false; } private void performUnlock() { // 通知 Activity 执行跳转和动画 if (onUnlockListener ! null) onUnlockListener.onUnlock(); } }TRIGGER_DISTANCE是一个关键参数。单位是 px不同屏幕密度下同一物理距离对应的 px 不同建议用dp转换后赋值比如dp2px(150f)这样在不同设备上解锁手感保持一致。setAlpha在滑动过程中同步调整透明度模拟系统锁屏被拖起时的渐隐效果这个细节很影响手感。animate().translationY(0).alpha(1f)使用的是属性动画走的是 View 的translationY属性不会触发布局重绘性能上没问题。isUnlocked这个防重复触发标志在performUnlock()执行动画期间必须置位否则多个快速ACTION_UP会导致跳转两次。3.3 通知卡片RecyclerView 的横向滑动与删除联动通知列表部分我用 RecyclerView 配合 LinearLayoutManager 竖向排列每条通知是一个圆角卡片包含 App 图标、标题、正文和时间。有两点需要特别注意一是 RecyclerView 的 item 复用可能导致通知残留在onBindViewHolder里必须重置所有显示状态二是只展示一条通知的场景下锁屏一般只显示最近两条复用问题不突出但一旦做列表展开就会踩坑。删除联动方面我监听了onNotificationRemoved并维护一个本地数据源列表ArrayListString在收到移除广播后从数据源中剔除对应项再用notifyItemRemoved()局部刷新避免notifyDataSetChanged()导致整页闪烁。private ListNotificationItem dataList new ArrayList(); private RecyclerView.Adapter adapter new RecyclerView.AdapterVH() { // 省略 onCreateViewHolder Override public void onBindViewHolder(VH holder, int position) { NotificationItem item dataList.get(position); // 每次绑定都必须重置 item 的透明度、平移状态 holder.itemView.setAlpha(1f); holder.itemView.setTranslationY(0f); holder.tvTitle.setText(item.title); holder.tvText.setText(item.text); } };这里之所以要重置setAlpha和setTranslationY是因为锁屏通知卡片进入时可能做了位移动画列表滚动时 RecyclerView 复用旧 View不复位就会出现残留的半透明或错位。这类问题在真机上调试时最容易看到也是新手最容易忽略的。4. 安全边界防绕过、防误触与应用锁联动4.1 防 Home 键退出锁屏 Activity 的避让策略自定义锁屏最尴尬的事是用户一按 Home 键就回到了桌面锁屏形同虚设。这个需要在锁屏 Activity 中屏蔽 Home 键和最近任务键。常见做法是覆写onAttachedToWindow()但这种方法对 Android 5.0 以上的系统基本无效。真正可用的方案是使用startLockTask()即设备所有者或屏锁模式屏幕固定模式将锁屏 Activity 固定在前台用户无法通过 Home 键回到桌面只有超过锁屏验证要求时才能退出锁屏固定模式。然而 Demo 的局限性在于startLockTask()只对设备所有者Device Owner生效普通应用调用会直接报错。所以更务实的做法是把自己定义成「应用内锁」锁的是自己的 App而不是整个系统。如果要锁住整个系统需要在锁屏 Activity 的onWindowFocusChanged中检测到焦点丢失时立即重新拉回这需要配合FLAG_SHOW_WHEN_LOCKED使用代码复杂且部分厂商会拦截。以下是一种防御性写法Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (!hasFocus !isUnlocking) { // 丢失焦点说明用户点了 Home 或切到了其他应用重新推到前台 Intent intent new Intent(this, LockActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } }isUnlocking是解锁流程中的标志位用来区分「正常解锁跳转」和「用户强行退出」。如果少了这个判断解锁时会因为焦点变化把自己拉回锁屏导致死循环跳转。FLAG_ACTIVITY_NEW_TASK用于在非 Activity 上下文中启动FLAG_ACTIVITY_CLEAR_TOP可以让已有的锁屏实例复用而不是创建新实例。这段代码在部分 ROM 上会因为「后台弹出界面」权限限制而失效需要在授权引导中把该权限说明清楚。4.2 误触保护口袋模式和防误滑判定锁屏容易在口袋里被误触解锁或者用户并不想解锁时手势太敏感。Demo 里用两个方式缓解一是使用ProximitySensor距离传感器检测屏幕是否被遮挡遮挡时禁用解锁手势二是在解锁按钮上增加长按确认而不是单纯滑动即解锁。距离传感器的代码如下public class ProximityHelper { private SensorManager sensorManager; private Sensor proximitySensor; private boolean isNear false; public void register(Context context) { sensorManager (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); proximitySensor sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); // 注册距离传感器监听只在锁屏可见时有效 sensorManager.registerListener(sensorListener, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL); } private SensorEventListener sensorListener new SensorEventListener() { Override public void onSensorChanged(SensorEvent event) { float distance event.values[0]; float maxRange proximitySensor.getMaximumRange(); // 距离小于最大值视为被遮挡 isNear distance (maxRange * 0.8f); } Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } }; }大多数距离传感器返回 0 表示遮挡返回maxRange表示无障碍。用distance maxRange * 0.8f做阈值判断是为了兼容传感器在中间距离的抖动避免频繁在近/远之间切换导致事件风暴。isNear为 true 时SlideUpView的onTouchEvent直接返回false不消费手势事件这样既防止误触也不影响后续界面元素的手势处理。需要注意在锁屏销毁时调用unregisterListener否则会导致传感器一直工作、电池掉电快。4.3 指纹与密码结合和系统锁屏的联动姿势自定义锁屏和系统锁屏的关系是必须想清楚的。Demo 场景下有两种做法一是完全取代系统锁屏直接在LockActivity中调用FingerprintManager或BiometricPrompt做生物识别二是自绘锁屏仅作为系统锁屏之上的遮罩层系统锁屏的密码由系统自己管理。第二种更稳妥但有一个硬性约束系统锁屏的KeyguardManager会优先于应用显示应用层无法完全遮挡系统锁屏除非使用setShowWhenLocked(true)并设置FLAG_SHOW_WHEN_LOCKED窗口属性。Demo 的处理方式是不在自绘锁屏里做指纹而是滑动手势触发后直接跳转到系统解锁流程让用户走系统安全验证。这种做法生产环境中最常见也最不容易翻车。我推荐在SlideUpView.performUnlock()里这样接系统锁屏private void goThroughSystemUnlock() { KeyguardManager km (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); if (km ! null km.isKeyguardSecure()) { // 拉起系统的安全锁屏界面 Intent intent km.createConfirmDeviceCredentialIntent(解锁, 验证身份后才能进入); if (intent ! null) { startActivity(intent); return; } } // 系统未设置密码时直接进入桌面 goToHome(); }createConfirmDeviceCredentialIntent从 Android 8.0 开始可用会弹出系统原生的 PIN/密码/指纹验证界面它是唯一能在自绘锁屏与系统安全策略之间可靠衔接的 API。注意用户未设置系统锁屏时intent为 null必须做空指针判断直接跳桌面。这里的「解锁」标题和「验证身份后才能进入」的副标题可以自定义不影响系统验证逻辑。5. 避坑排查6 个在真机上翻过车的细节5.1 坑 1FLAG_SHOW_WHEN_LOCKED在 Android 12 上失效现象Android 12 手机上锁屏 Activity 无法覆盖系统锁屏黑屏或闪现系统锁屏后才显示自定义界面。原因从 Android 12API 31起FLAG_SHOW_WHEN_LOCKED的行为有变化必须配合setShowWhenLocked(true)方法一起使用才有效。只用addFlags触达不到正确的窗口层级。解决在onCreate()中改为调用setShowWhenLocked(true)同时用WindowInsetsController隐藏系统栏。Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setShowWhenLocked(true); // 设置全屏 getWindow().setDecorFitsSystemWindows(false); getWindow().getInsetsController().hide( WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars()); }5.2 坑 2通知监听服务收不到回调现象onNotificationPosted偶尔不触发锁屏界面通知更新不及时。原因未引导用户开启「通知使用权」。很多开发者以为声明了权限就能用实际上这个权限必须到系统设置里手动开启。部分手机上应用被系统清理后服务被杀导致监听失效。解决启动时主动检测NotificationManagerCompat.getEnabledListenerPackages()中是否包含自己的包名不包含则弹窗引导去设置页。同时把NotificationCaptureService设为「启动后自动恢复」状态配合前台服务提高进程存活率。5.3 坑 3RecyclerView锁屏下点击无响应现象锁屏上通知列表能滑动但点击没有反应按钮 onClik 不触发。原因锁屏的根布局FrameLayout上有全屏的SlideUpView它的onTouchEvent返回 true 时事件被消费掉RecyclerView 根本收不到。这是自定义锁屏常见的手势拦截问题。解决让SlideUpView只在底部滑动区域内启用而不是全屏接收。在SlideUpView的onTouchEvent里判断event.getY() getHeight() - disabledArea时不消费事件把事件让给下面的控件。这个区域参数 I 一般设置为底部 200dp正好是滑动解锁按钮的宽度范围。5.4 坑 4抬手后解锁动画抖动现象滑动距离超过阈值后解锁动画执行到一半界面突然跳变或闪一下。原因ACTION_UP里执行animate()回弹和解锁跳转同时进行回弹动画还没结束 Activity 就开始销毁两个动画同时修改同一个属性导致视觉闪烁。另一种情况是触发阈值判断后没有销毁SlideUpView的动画监听器旧动画回调更新了已经销毁的 View。解决解锁时先取消回弹动画if (offsetY TRIGGER_DISTANCE) { animate().cancel(); // 先取消可能存在的回弹动画 performUnlock(); }同时保证performUnlock()里通过onUnlockListener通知外部后外部延迟 100ms 跳转让解锁位移动画有一个短暂的播放时间。延迟可以用Handler.postDelayed或view.postDelayed。5.5 坑 5开机自启动失效现象手机重启后锁屏不会自动启动需要手动打开 App 才恢复。原因Android 3.1 系统要求应用至少被手动启动过一次才会接收BOOT_COMPLETED广播部分厂商比如主流国产 ROM默认禁止自启动权限仅声明权限不够。解决引导用户手动打开一次应用同时引导用户到系统自启动管理页面把应用设为允许。这两步在代码里只能检测、提示无法静默修改。检测方式private boolean isBootReceiverEnabled() { ComponentName component new ComponentName(this, BootReceiver.class); int status getPackageManager().getComponentEnabledSetting(component); return (status PackageManager.COMPONENT_ENABLED_STATE_ENABLED); }不完全可靠因为厂商自启动白名单在系统的组件状态之外但至少能做一个兜底提示。5.6 坑 6电量消耗异常现象锁屏待机一夜掉电 10% 以上。原因锁屏 UI 中的时间每秒刷新、传感器未注销、或者是Handler循环没有停止。最常见的其实是FLAG_KEEP_SCREEN_ON一直开着屏幕背光在锁屏期间持续高亮。解决缩短亮屏时间在锁屏启动后 5 秒内如果没有触摸事件主动将亮度调低WindowManager.LayoutParams params getWindow().getAttributes(); params.screenBrightness 0.05f; // 极暗保留可见度但不耗电 getWindow().setAttributes(params);注意screenBrightness设置为 0.05f 在弱光环境下几乎不可见我一般会再叠加一个半透明的黑色遮罩层来降低表现亮度的同时不直接把屏幕亮度参数调到最低因为部分 OLED 屏在极低亮度下会闪。6. 从能用变好用内存抖动排查、动画性能与功耗核算清单6.1 用onTrimMemory和onLowMemory做锁屏的后退策略锁屏是常驻界面内存吃紧时系统会优先杀掉它。一个被开发者普遍忽略的技巧是监听onTrimMemory回调在内存紧张时主动释放通知列表的图片缓存和非必须的 View 引用延长自己存活的时间。在锁屏 Activity 里我一般这样做Override public void onTrimMemory(int level) { super.onTrimMemory(level); if (level ComponentCallbacks2.TRIM_MEMORY_MODERATE) { // 清理通知卡片中可能存在的图标缓存 if (notifAdapter ! null) notifAdapter.clearCache(); System.gc(); } }TRIM_MEMORY_MODERATE是系统内存吃紧但还没到杀进程的阶段此时清理图标缓存能明显降低锁屏被系统回收的概率。clearCache()的实现是把LruCache清空这样通知卡片重新绑定时会从磁盘重新加载图标。锁屏场景下不应使用Glide这种重型图片加载库加载通知图标直接用ApplicationInfo.loadIcon()类型为Drawable就好否则内存占用会失控。图标缓存LruCache的大小设置在 4MB 左右超过这个值就应该先走缓存淘汰而不是扩大容量。private static class IconCache { private final LruCacheString, Drawable cache new LruCache(4 * 1024 * 1024); public void put(String key, Drawable drawable) { cache.put(key, drawable); } public Drawable get(String key) { return cache.get(key); } public void clear() { cache.evictAll(); } }6.2 动画性能避免在onDraw里做耗时计算锁屏界面的动画帧率直接影响用户体验。最常见的性能问题是把所有逻辑都放在自定义 View 的onDraw里每帧都做String.format、SimpleDateFormat或Bitmap.createBitmap这会导致掉帧。正确做法是使用ValueAnimator驱动动画进度值在动画监听到进度时更新数据对象然后统一invalidate()让onDraw只做绘制。ValueAnimator animator ValueAnimator.ofFloat(0f, 1f); animator.setDuration(300); animator.addUpdateListener(animation - { float progress (float) animation.getAnimatedValue(); // 根据 progress 更新关键帧状态 slideView.setAlpha(1 - progress * 0.5f); slideView.setTranslationY(-progress * TRIGGER_DISTANCE); }); animator.start();为什么要把动画进度和实际属性绑定在监听里而不是onDraw内计算因为在addUpdateListener中改 View 属性会触发系统自动重绘onDraw里只做把当前属性状态绘制出来的事情职责分离后排查掉帧问题时不用去阅读坐标计算逻辑。setRotationX、setLoop这类属性不要写成布局动画Android 的LayoutAnimationController性能在列表场景下表现一般并且较难精确控制每个通知卡片的错峰进入时刻。以下做一个简单的手势动画参数对照表方便你直接拿去调手感参数推荐值说明滑动触发阈值150dp物理距离固定屏幕尺寸变化不影响手感回弹动画时长200ms系统锁屏的标准回弹速度解锁动画时长300ms大于 300ms 会显得拖沓通知卡片错峰间隔80ms多条通知进入时的错峰间隔透明度渐隐起始点进度 30% 处前 30% 保持不透明避免误触快速滑动时看不清这些参数要给足调整空间不要在代码里写死一个常量放在res/values/integers.xml里走资源系统方便后续做不同屏幕密度或不同机型的适配。熟手可以直接改 Integer新手可以从这组值开始试然后根据真机手感微调。6.3 功耗核算锁屏到底费不费电把锁屏做成常驻服务后功耗是躲不掉的问题。我建议用系统自带的dumpsys batterystats做量化检查锁屏亮屏 1 小时、灭屏待机 8 小时后各拉一份数据重点看Sensor的使用时长、WakeLock的持有时长、以及Process的 CPU 使用率。有一个我一直在用的简易排查方式adb shell dumpsys battery | grep -i level adb shell dumpsys batterystats --reset sleep 5 adb shell dumpsys batterystats --include-protected | grep -A 10 你的包名dumpsys batterystats --reset会把统计清零然后等 5 秒再输出你包名的耗电情况如果 CPU 使用时间超过 30 秒基本可以断定有后台循环或传感器未注销。看到SensorListener 2这样的输出就检查代码里是否在onPause()或onDestroy()里遗漏了unregisterListener。热点感知很重要很多问题在模拟器上根本测不出来只有真机连着让系统奔一会儿抓到的数据才有意义。参数方面锁屏亮屏状态下 CPU 占比 5% 以内、灭屏待机一晚掉电小于 3%这两个指标是我验收这个项目的及格线。那段从做某公司定制项目开始养成却一直留到现在的习惯每次写完锁屏相关模块不直接交付而是我先调一个 30 分钟的真实待机数据再拿着耗电记录逐行对代码确认传感器注销、闹钟清理、WakeLock释放这三点没有遗漏才敢交出去。从那以后我每次做锁屏稳定性验收都强制走一遍dumpsys加手动滑动 100 次的手感体验。锁屏这个东西看起来功能简单真正碰过的才知道到处是细节。这套「Android 智能UI锁屏」的资源和经验希望帮到你。本文还有配套的精品资源点击获取