前言
Android工具类是一些封装好的工具方法的集合,用于简化Android开发中的常见操作和实现特定功能。这些工具类可以帮助开发者更高效地编写代码、提高开发效率和减少重复劳动。
一、Intent
1. 获取应用程序信息
(1)从 Intent 或者 URI 中读取应用信息
从 Intent 或者 URI 中读取应用信息APK 包的名称、大小、图标等信息是比较困难的,因为 Intent 或者 URI 并没有直接提供这些信息。不过,你可以通过以下步骤来获取这些信息:
- 使用包名获取应用程序的 ApplicationInfo 对象。
- 使用 PackageManager 对象获取应用程序的名称、大小、图标等信息。
以下是一种可能的实现方式:
// 从 Intent 中获取 URI
Uri uri = intent.getData();
if (uri == null) {
return;
}
// 从 URI 中获取包名
String packageName = uri.getAuthority();
if (packageName == null) {
return;
}
PackageManager pm = getPackageManager();
ApplicationInfo appInfo;
try {
// 使用包名获取 ApplicationInfo 对象
appInfo = pm.getApplicationInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return;
}
// 获取应用名称
String appName = pm.getApplicationLabel(appInfo).toString();
// 获取应用图标
Drawable icon = pm.getApplicationIcon(appInfo);
// 获取应用 APK 文件的路径
String apkPath = appInfo.sourceDir;
// 获取应用 APK 文件的大小
long apkSize = new File(apkPath).length();
需要注意的是,上述代码仅适用于从 URI 中获取包名的情况。如果你从 Intent 中获取了包名,可以将其替换到代码中的 packageName
变量中。此外,如果 URI 中不包含包名,你需要使用其他方式获取包名,例如解析 URI 中的路径信息。
(2)常规使用 PackageManager
类来获取应用信息
以下是一种示例代码:
PackageManager pm = getPackageManager();
try {
ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
// 获取应用名称
String appName = pm.getApplicationLabel(appInfo).toString();
// 获取应用图标
Drawable icon = pm.getApplicationIcon(appInfo);
// 获取应用 APK 文件的路径
String apkPath = appInfo.sourceDir;
// 获取应用 APK 文件的大小
long apkSize = new File(apkPath).length();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
在上述代码中,我们首先创建一个 PackageManager
对象,然后使用 getApplicationInfo(packageName, 0)
方法获取指定包名对应的应用信息。接着,我们使用 getApplicationLabel(appInfo)
方法获取应用名称,使用 getApplicationIcon(appInfo)
方法获取应用图标。最后,我们使用 appInfo.sourceDir
获取应用 APK 文件的路径,使用 new File(apkPath).length()
获取应用 APK 文件的大小。 需要注意的是,如果指定的包名对应的应用不存在,getPackageManager().getApplicationInfo(packageName, 0)
方法会抛出 PackageManager.NameNotFoundException
异常。在代码中,我们使用 try-catch 块处理该异常。
2、获取Intent携带的uri
(1)获取Uri内容
如果需要兼容其他类型的 Intent,可以在代码中添加相应的处理逻辑。一般来说,可以使用 getData()
方法获取 URI,或者根据 Intent 中的数据类型进行处理。
以下是一种通用的方式,用于兼容多种类型的 Intent:
Uri uri = null;
if (intent != null) {
String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action)) {
// 获取 VIEW Intent 中的 URI
uri = intent.getData();
} else if (Intent.ACTION_SEND.equals(action)) {
// 获取 SEND Intent 中的 URI
uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
} else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
// 获取 SEND_MULTIPLE Intent 中的 URI
ArrayList<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
if (uris != null && !uris.isEmpty()) {
uri = uris.get(0);
}
} else if (Intent.ACTION_CHOOSER.equals(action)) {
// 获取 CHOOSER Intent 中的 URI
Intent chooserIntent = Intent.createChooser(intent, "Select");
if (chooserIntent != null) {
uri = chooserIntent.getParcelableExtra(Intent.EXTRA_STREAM);
}
} else {
// 其他情况,根据数据类型获取 URI
uri = intent.getData();
if (uri == null) {
String type = intent.getType();
if (type != null) {
// 根据数据类型获取 URI
String[] types = type.split("/");
if (types.length == 2) {
String dataType = types[0];
String dataString = intent.getStringExtra(Intent.EXTRA_TEXT);
if (dataString != null) {
uri = Uri.parse(dataString);
}
}
}
}
}
}
在上述代码中,我们使用 getData()
方法获取 URI,并在其为空的情况下,根据数据类型进行处理。具体地,我们使用 getType()
方法获取数据类型,然后根据类型进行处理。 在代码中,我们假设数据类型为 text/plain
,并使用 getStringExtra()
方法获取 EXTRA_TEXT
键对应的字符串。如果字符串不为空,则将其转换为 URI。如果需要兼容其他类型的数据,可以根据实际情况进行扩展。
(3)获取uri数量
在 Intent 中包含多个文件的 URI 的情况下,可以使用 getClipData()
方法获取 ClipData
对象,然后使用 getItemCount()
方法获取 URI 的数量。 以下是一种获取多个文件 URI 数量的方式:
int uriCount = 0;
if (intent != null) {
ClipData clipData = intent.getClipData();
if (clipData != null) {
uriCount = clipData.getItemCount();
} else {
Uri uri = intent.getData();
if (uri != null) {
uriCount = 1;
}
}
}
在上述代码中,我们首先使用 getClipData()
方法获取 ClipData
对象。如果 ClipData
不为空,则使用 getItemCount()
方法获取 URI 的数量;否则,使用 getData()
方法获取单个 URI,如果 URI 不为空,则数量为 1。 需要注意的是,这种方式只适用于 ACTION_SEND
和 ACTION_SEND_MULTIPLE
类型的 Intent,如果 Intent 的类型不是这两种类型,则无法获取多个文件 URI。
三、ImageView
1、描边圆角效果叠加
可以通过创建一个带有描边的 Shape Drawable,然后将其设置为 ImageView 的背景来实现在 ImageView 中设置黑色描边和圆角的效果。具体步骤如下:
(1) 创建一个圆角矩形的 Shape Drawable,并设置其圆角半径。
示例代码如下:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp" />
</shape>
(2)创建一个带有描边的 Shape Drawable,并将圆角矩形的 Shape Drawable 设置为其内部的 Shape Drawable
示例代码如下:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<stroke android:width="1dp" android:color="#000000" />
<corners android:radius="10dp" />
<padding android:left="1dp" android:top="1dp" android:right="1dp" android:bottom="1dp" />
</shape>
上述代码中,使用 stroke
元素来设置描边的宽度和颜色,使用 padding
元素来设置描边的偏移量,这里设置为 1dp。同时,使用 solid
元素将背景颜色设置为透明。将圆角矩形的 Shape Drawable 设置为其内部的 Shape Drawable。
(3)在代码中获取 ImageView 对象,并将带有描边的 Shape Drawable 设置为其背景
示例代码如下:
ImageView imageView = findViewById(R.id.imageView);
Drawable drawable = getResources().getDrawable(R.drawable.rounded_corners_with_stroke);
imageView.setBackground(drawable);
上述代码中,通过 getResources().getDrawable()
方法获取带有描边的 Shape Drawable,并将其设置为 ImageView 的背景。 需要注意的是,如果图片的宽度或高度小于圆角半径的两倍,则圆角的效果可能不明显,因此应该根据具体情况适当调整圆角半径。
2、仅在代码中设置图片圆角
可以通过代码实现在 ImageView 中设置圆角。
具体步骤如下:
- 创建一个 Bitmap 对象,并将图片资源转换为 Bitmap 对象。示例代码如下:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
- 创建一个圆角矩形的 ShapeDrawable 对象,并设置其圆角半径。示例代码如下:
float radius = 10f; // 圆角半径
ShapeDrawable shapeDrawable = new ShapeDrawable(new RoundRectShape(new float[] {radius, radius, radius, radius, radius, radius, radius, radius}, null, null));
上述代码中,通过 RoundRectShape
的构造函数设置圆角矩形的圆角半径,其中第一个参数是一个 float 数组,指定了圆角的半径,按照左上、右上、右下、左下的顺序排列,如果某个角不需要圆角则将其半径设置为 0;第二个和第三个参数分别指定了外边框和内边框的矩形,这里不需要设置,因此传入 null。
- 创建一个 BitmapShader 对象,并将其设置为 ShapeDrawable 的 Shader。示例代码如下:
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
shapeDrawable.getPaint().setShader(bitmapShader);
上述代码中,通过 BitmapShader
的构造函数创建一个 BitmapShader 对象,并将其设置为 ShapeDrawable 的 Shader,以实现将 Bitmap 显示到 ShapeDrawable 中。
- 将 ShapeDrawable 设置为 ImageView 的背景。示例代码如下:
imageView.setBackground(shapeDrawable);
上述代码中,通过 setBackground()
方法将 ShapeDrawable 设置为 ImageView 的背景,从而实现在 ImageView 中设置圆角的效果。 完整的代码示例:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
float radius = 10f; // 圆角半径
ShapeDrawable shapeDrawable = new ShapeDrawable(new RoundRectShape(new float[] {radius, radius, radius, radius, radius, radius, radius, radius}, null, null));
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
shapeDrawable.getPaint().setShader(bitmapShader);
imageView.setBackground(shapeDrawable);
3、AnimationDrawable点击失效解决
AnimationDrawable
是一个逐帧动画,它可以使用一组 Drawable
对象来创建动画的效果。当使用 AnimationDrawable
在 ImageView
上播放逐帧动画时,如果给 ImageView
设置了点击事件,你可能会发现点击事件不生效。这是因为在播放动画的过程中,ImageView
实际上是被替换成了一个 Drawable
,而不是一个普通的视图。因此,点击事件会被 Drawable
拦截,而不会传递给 ImageView
。
为了解决这个问题,我们可以在 ImageView
上添加一个覆盖层,来接收点击事件。具体而言,我们可以在 ImageView
的父容器上添加一个透明的视图,然后给这个视图设置点击事件。例如:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/my_animation" />
<View
android:id="@+id/click_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent" />
</RelativeLayout>
在这个示例中,我们在 ImageView
的父容器上添加了一个透明的 View
,并将其覆盖在 ImageView
上。然后,我们可以给这个 View
设置点击事件,来接收用户的点击操作。 接下来,我们需要在代码中获取 ImageView
和 View
对象,并分别设置点击事件。例如:
ImageView imageView = findViewById(R.id.image_view);
View clickView = findViewById(R.id.click_view);
clickView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 处理点击事件
}
});
在这个示例中,我们使用 findViewById()
方法获取 ImageView
和 View
对象,并给 View
设置了点击事件。当用户点击 ImageView
时,点击事件会被 View
拦截,并传递给 OnClickListener
处理。
需要注意的是,在使用 AnimationDrawable
播放逐帧动画时,也可以通过代码来控制动画的启动、暂停和停止等操作。因此,在实际开发中,可能需要根据具体的业务需求,来动态调整动画的播放状态。
四、Textview
1、文本缩写
(1)在 XML 布局文件中设置
<TextView
android:id="@+id/my_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1" // 设置 TextView 显示的最大行数为 1 行
android:ellipsize="middle" // 设置省略位置为中间
android:text="这是一个很长的文本,需要省略显示" />
(2)在 Java 代码中设置
TextView textView = findViewById(R.id.my_text_view);
textView.setSingleLine(true);
textView.setMaxLines(1);
textView.setEllipsize(TextUtils.TruncateAt.MIDDLE);
textView.setText("这是一个很长的文本,需要省略显示");
五、PopupWindow
1、限制屏幕内显示
PopupWindow 在显示时可能会超出屏幕范围,为了保证 PopupWindow 能够完全显示在屏幕内,可以在 PopupWindow 显示前先计算 PopupWindow 的位置和大小,然后根据屏幕大小和 PopupWindow 的位置和大小,调整 PopupWindow 的位置。 下面是一个判断 PopupWindow 位置是否超出屏幕并调整位置的示例代码:
public void showPopupWindow(View anchorView, View contentView) {
PopupWindow popupWindow = new PopupWindow(contentView,
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
// 计算 PopupWindow 在屏幕中的位置
int[] location = new int[2];
anchorView.getLocationOnScreen(location);
int anchorX = location[0];
int anchorY = location[1];
int anchorWidth = anchorView.getWidth();
int anchorHeight = anchorView.getHeight();
int screenWidth = getResources().getDisplayMetrics().widthPixels;
int screenHeight = getResources().getDisplayMetrics().heightPixels;
// 判断 PopupWindow 是否超出屏幕范围
boolean isOverX = anchorX + popupWindow.getWidth() > screenWidth;
boolean isOverY = anchorY + anchorHeight + popupWindow.getHeight() > screenHeight;
// 如果超出了屏幕范围,调整 PopupWindow 的位置
if (isOverX) {
popupWindow.setWidth(screenWidth - anchorX);
}
if (isOverY) {
popupWindow.setHeight(screenHeight - anchorY - anchorHeight);
}
// 显示 PopupWindow
popupWindow.showAtLocation(anchorView, Gravity.NO_GRAVITY, anchorX, anchorY + anchorHeight);
}
在这个示例代码中,我们首先创建了一个 PopupWindow,并计算了 PopupWindow 在屏幕中的位置。然后,我们获取了屏幕的大小,判断 PopupWindow 是否超出了屏幕范围,并根据情况调整 PopupWindow 的大小和位置。最后,我们调用了 PopupWindow 的 showAtLocation 方法,将 PopupWindow 显示在指定位置。
注意,在计算 PopupWindow 的位置和大小时,可能需要考虑状态栏和导航栏的高度。如果需要考虑状态栏和导航栏的高度,可以使用 ViewCompat.getWindowInsetsController 方法获取 WindowInsetsController,并从中获取状态栏和导航栏的高度。
六、ConstraintLayout
1、布局水平方向均分
要让子 View 在 ConstraintLayout 中水平方向上宽度均分为 50%,可以使用以下方法:
- 在 ConstraintLayout 中添加两个子 View,并且把它们分别放在布局的左侧和右侧,例如:
<androidx.constraintlayout.widget.ConstraintLayout
...>
<View
android:id="@+id/view_left"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/view_right"
... />
<View
android:id="@+id/view_right"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@+id/view_left"
app:layout_constraintEnd_toEndOf="parent"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
2、 然后在两个子 View 上添加水平方向上的权重属性,使它们在水平方向上均分布局的宽度。例如:
<androidx.constraintlayout.widget.ConstraintLayout
...>
<View
android:id="@+id/view_left"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/view_right"
app:layout_constraintHorizontal_weight="1"
... />
<View
android:id="@+id/view_right"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@+id/view_left"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
这样,两个子 View 在水平方向上就均分了布局的宽度。如果您想要更改子 View 的宽度比例,只需要调整它们的权重即可。例如,如果您想让左侧的子 View 宽度占 30%,右侧的子 View 宽度占 70%,可以将左侧的子 View 的权重设置为 3,右侧的子 View 的权重设置为 7。
七、动画
1、共享元素
(1)修改起始位置和大小
如果您需要使用RectF
对象来获取共享元素的位置和大小信息,可以使用以下方法
RectF sharedElementRectF = new RectF();
int[] location = new int[2];
sharedElement.getLocationOnScreen(location);
sharedElementRectF.set(location[0], location[1], location[0] + sharedElement.getWidth(), location[1] + sharedElement.getHeight());
在上面的代码中,我们先通过View.getLocationOnScreen(int[])
方法获取共享元素在屏幕上的坐标,然后使用这些坐标创建一个RectF
对象表示共享元素在屏幕上的位置和大小信息。 然后,您可以将此RectF
对象传递给ChangeBounds.setEpicenter()
方法作为共享元素过渡的起始位置和大小。
以下是一个示例代码,展示了如何在setEnterSharedElementCallback()
方法中使用RectF
对象执行共享元素过渡动画:
setEnterSharedElementCallback(new SharedElementCallback() {
@Override
public void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
super.onSharedElementStart(sharedElementNames, sharedElements, sharedElementSnapshots);
// 获取共享元素的位置和大小信息
RectF sharedElementRectF = new RectF();
int[] location = new int[2];
sharedElements.get(0).getLocationOnScreen(location);
sharedElementRectF.set(location[0], location[1], location[0] + sharedElements.get(0).getWidth(), location[1] + sharedElements.get(0).getHeight());
// 创建一个共享元素过渡对象,并设置起始位置和大小
TransitionSet transitionSet = new TransitionSet();
ChangeBounds changeBounds = new ChangeBounds();
changeBounds.setEpicenter(sharedElementRectF);
transitionSet.addTransition(changeBounds);
// 启动共享元素过渡动画
TransitionManager.beginDelayedTransition(container, transitionSet);
}
});
在上面的示例中,我们使用RectF
对象来创建共享元素过渡的起始位置和大小,将其传递给ChangeBounds.setEpicenter()
方法,然后启动共享元素过渡动画。