[转] Android: Expand/collapse animation

本文提供了一种在Android中实现视图展开与折叠动画的方法。通过自定义Animation类,可以根据视图的高度变化来平滑地调整布局,实现视图的渐进显示与隐藏效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文地址:[url]https://stackoverflow.com/a/13381228/6091500[/url]

亲测可用。

import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Transformation;

public class ViewAnimation {
public static void expand(final View v) {
v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
final int targetHeight = v.getMeasuredHeight();

// Older versions of android (pre API 21) cancel animations for views with a height of 0.
v.getLayoutParams().height = 1;
v.setVisibility(View.VISIBLE);
Animation a = new Animation()
{
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
v.getLayoutParams().height = interpolatedTime == 1
? ViewGroup.LayoutParams.WRAP_CONTENT
: (int)(targetHeight * interpolatedTime);
v.requestLayout();
}

@Override
public boolean willChangeBounds() {
return true;
}
};

// 1dp/ms
a.setDuration((int)(targetHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}

public static void collapse(final View v) {
final int initialHeight = v.getMeasuredHeight();

Animation a = new Animation()
{
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if(interpolatedTime == 1){
v.setVisibility(View.GONE);
}else{
v.getLayoutParams().height = initialHeight - (int)(initialHeight * interpolatedTime);
v.requestLayout();
}
}

@Override
public boolean willChangeBounds() {
return true;
}
};

// 1dp/ms
a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
v.startAnimation(a);
}
}
<think>我们正在分析两个独立的问题:updateLegacyStorageApps方法中的遗留存储实现,以及针对target_sdk_version=35的GooglePermissionController应用的原生加载器配置。###1.updateLegacyStorageApps方法中的遗留存储实现####方法功能该方法用于更新具有遗留存储访问权限的应用列表。在Android10(API29)引入分区存储(ScopedStorage)后,此方法管理一个UID列表,这些UID对应的应用仍使用旧版存储模型。####代码分析```javaprivatevoidupdateLegacyStorageApps(StringpackageName,intuid,booleanhasLegacy){synchronized(mLock){if(hasLegacy){Slog.v(TAG,"Package"+packageName+"haslegacystorage");mUidsWithLegacyExternalStorage.add(uid);}else{//TODO(b/149391976):处理共享用户ID。在移除前检查是否有其他已安装应用使用遗留存储Slog.v(TAG,"Package"+packageName+"doesnothavelegacystorage");mUidsWithLegacyExternalStorage.remove(uid);}}}```####关键点1.**同步机制**使用`synchronized(mLock)`确保多线程环境下对`mUidsWithLegacyExternalStorage`集合的修改是线程安全的。2.**条件分支**-当`hasLegacy`为`true`时,将应用UID加入`mUidsWithLegacyExternalStorage`集合(允许遗留存储访问)。-当`hasLegacy`为`false`时,从集合中移除UID(禁用遗留存储访问)。3.**待解决问题**`//TODO(b/149391976)`指出当前实现未处理共享用户ID(shareduserID)的情况:-多个应用共享同一UID时,需确保所有应用都不再需要遗留存储后才能移除UID。-当前逻辑可能过早移除共享UID的访问权限,导致其他应用无法访问存储。####遗留存储判定条件应用是否启用遗留存储取决于:-**targetSDK版本**:低于Android10(API29)的应用默认启用。-**清单属性**:Android10+的应用可通过`android:requestLegacyExternalStorage="true"`显式启用[^1]。>示例:若应用`com.example.app`的`targetSdkVersion=28`,调用此方法时`hasLegacy=true`。---###2.GooglePermissionController的原生加载器配置(targetSdkVersion=35)####背景`GooglePermissionController`是Android系统中处理权限请求的系统组件。当其`targetSdkVersion`升级至35时,需确保原生库(NativeLibrary)加载配置符合新规范。####关键配置在`Android.bp`或`Android.mk`构建文件中需声明:```makefile//原生库配置示例cc_library_shared{name:"libgooglepermissioncontroller",srcs:["native/*.cpp"],shared_libs:["liblog","libandroid",],stl:"libc++_static",cflags:["-Wall","-Werror"],}```####AndroidManifest.xml关键设置```xml<applicationandroid:name=".GooglePermissionControllerApplication"android:persistent="true"android:usesNativeLibrary="true"android:hasFragileUserData="true"...><!--显式声明原生库--><uses-native-libraryandroid:name="libgooglepermissioncontroller"android:required="true"/></application>```####兼容性要求1.**NDK版本**:推荐使用NDKr23+以支持API35。2.**ABI过滤**:在`build.gradle`中指定支持的ABI:```groovyandroid{defaultConfig{ndk{abiFilters'armeabi-v7a','arm64-v8a','x86','x86_64'}}}```3.**位置无关代码(PIC)**:Android6.0+要求所有原生库编译为位置无关代码(`-fPIC`标志)。####加载原生库的Java代码在`GooglePermissionController`的启动类中加载库:```javapublicclassGooglePermissionControllerApplicationextendsApplication{static{//加载原生库System.loadLibrary("googlepermissioncontroller");}}```---###遗留存储与权限控制的关系1.**权限模型变更**-Android6.0(API23)引入运行时权限,但存储权限(`READ_EXTERNAL_STORAGE`/`WRITE_EXTERNAL_STORAGE`)在Android10前仍可访问整个存储设备。-Android10+中,即使授予存储权限,应用默认只能访问媒体文件(照片、视频等),非媒体文件需使用存储访问框架(SAF)[^1]。2.**GooglePermissionController的作用**当应用请求存储权限时:-若应用在遗留存储白名单中(即`mUidsWithLegacyExternalStorage`包含其UID),授予权限意味着允许访问整个存储设备。-否则,仅授予媒体文件访问权限。---###总结|组件|关键实现||---------------------|--------------------------------------------------------------------------||遗留存储管理|通过`mUidsWithLegacyExternalStorage`集合控制UID的存储访问模式||原生库加载配置|使用`System.loadLibrary`加载,并在清单中声明`<uses-native-library>`||权限控制交互|权限授予范围由遗留存储白名单和targetSDK共同决定|§§相关问题§§1.共享用户ID(shareduserID)如何影响Android的遗留存储访问控制?2.在Android12(API31)及以上版本中,`requestLegacyExternalStorage`属性有何行为变化?3.系统应用(如GooglePermissionController)的原生库加载失败会导致什么后果?4.如何测试应用在不同存储模型下的文件访问行为?</think>### 分析:Legacy Storage 实现与 Native Loader 配置 #### 1. `updateLegacyStorageApps` 方法中的遗留存储逻辑 ```java private void updateLegacyStorageApps(String packageName, int uid, boolean hasLegacy) { synchronized (mLock) { if (hasLegacy) { // 启用遗留存储访问 Slog.v(TAG, "Package " + packageName + " has legacy storage"); mUidsWithLegacyExternalStorage.add(uid); } else { // 禁用遗留存储访问(含共享用户ID处理提示) Slog.v(TAG, "Package " + packageName + " does not have legacy storage"); mUidsWithLegacyExternalStorage.remove(uid); } } } ``` ##### 核心机制: 1. **UID 白名单管理** - `mUidsWithLegacyExternalStorage`:存储允许使用传统存储模型的 UID 集合 - 当 `hasLegacy=true` 时添加 UID,否则移除 UID - 访问控制决策基于: $$ \text{hasLegacy} = \begin{cases} \text{true}, & \text{if } \mathtt{targetSdkVersion} < 29 \\ \text{manifest声明}, & \text{otherwise} \end{cases} $$ 2. **共享用户 ID 问题** - 代码中 `TODO` 标注了关键限制:当多个应用共享同一 UID 时 - 当前实现直接移除 UID 可能导致共享应用失去存储访问权限 - 需改进为:仅当所有共享应用都不需要遗留存储时才移除 UID 3. **同步机制** - `synchronized (mLock)` 确保多线程环境下的原子性操作 - 防止并发修改导致的 UID 列表状态不一致 ##### 分区存储演进: | Android 版本 | 遗留存储行为变化 | |-------------|-----------------| | Android 10 (API 29) | 默认启用分区存储,需显式声明 `requestLegacyExternalStorage` | | Android 11 (API 30) | 强制分区存储,但允许有限临时豁免[^1] | | Android 13+ | 完全移除遗留存储支持 | #### 2. GooglePermissionController 的 Native Loader 配置 (targetSdk=35) ##### 配置要点: 1. **原生库加载声明** 在 `AndroidManifest.xml` 中必须声明: ```xml <application android:extractNativeLibs="true" ... > <uses-native-library android:name="libpermissioncontroller" android:required="true"/> </application> ``` 2. **ABI 兼容性处理** - 需在 `build.gradle` 中指定: ```gradle android { ndkVersion "25.1.8937393" defaultConfig { ndk { abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' } } } ``` - 确保提供 64 位版本(Android 12+ 强制要求) 3. **权限模型适配** - 对于 targetSdk=35 需实现: ```java // 检查权限状态 if (PermissionChecker.checkSelfPermission(context, PERMISSION) != PERMISSION_GRANTED) { // 触发动态请求流程 } ``` - 使用 `ActivityResultContracts.RequestPermission()` 处理权限回调 ##### 遗留存储的特殊处理: 当 PermissionController 需要访问遗留存储时: 1. 在 `AndroidManifest.xml` 声明: ```xml <application android:requestLegacyExternalStorage="true" ... > ``` 2. 在代码中验证存储访问模式: ```java if (Environment.isExternalStorageLegacy()) { // 使用传统文件路径访问 } else { // 使用 MediaStore 或 Storage Access Framework } ``` ### 关键问题解决方案 #### 共享用户 ID 处理建议 ```java // 改进后的移除逻辑 else { boolean shouldRemove = true; for (String sharedPkg : getSharedPackages(uid)) { if (hasLegacyStorageDeclared(sharedPkg)) { shouldRemove = false; break; } } if (shouldRemove) { mUidsWithLegacyExternalStorage.remove(uid); } } ``` #### Native Loader 故障排查 若加载失败,检查: 1. `adb logcat` 查看 `dlopen` 错误 2. 验证 `APK/lib/` 目录是否包含正确的 .so 文件 3. 使用 `nm -D libpermissioncontroller.so` 验证符号导出 --- ### 相关问题 1. Android 13+ 完全移除遗留存储支持后,`mUidsWithLegacyExternalStorage` 机制将如何演化? 2. 当多个共享 UID 的应用对 `requestLegacyExternalStorage` 声明不一致时,系统应如何处理存储访问权限? 3. 对于系统级应用(如 PermissionController),在 targetSdkVersion>=30 时如何安全访问非媒体文件? 4. Native 库加载失败是否会导致 Android 权限控制机制完全崩溃?系统有何容错机制? [^1]: 基于 Android 兼容性文档:https://source.android.com/docs/core/storage/legacy [^2]: Android 动态权限请求指南:https://developer.android.com/training/permissions/requesting
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值