Android使用UI适配框架AutoSize

前言

屏幕适配框架AndroidAutoSize是根据今日头条屏幕适配方案优化的。

这种方案无法在Jetpack Compose中使用。

Jetpack Compose可参考文章:

https://www.psvmc.cn/article/2024-04-07-jetpack-compose-ui-adaptation.html

Github地址

https://github.com/JessYanCoding/AndroidAutoSize

基本使用

添加依赖

1
implementation 'me.jessyan:autosize:1.2.1'

kts中

1
implementation("me.jessyan:autosize:1.2.1")

AndroidAutoSize 在使用上非常简单,只需要填写设计图尺寸这一步即可接入项目;

假如我的设计图是横屏的,宽度是1920,高度是1128的2倍图。

我们可以按如下设置

1
2
3
4
5
6
7
8
9
10
<manifest>
<application>
<meta-data
android:name="design_width_in_dp"
android:value="960"/>
<meta-data
android:name="design_height_in_dp"
android:value="564"/>
</application>
</manifest>

注意

默认是按照宽度适配的。

这里的宽度是效果图的宽度,不是是效果图窄边的DP值。

如果按短边适配

1
2
3
4
5
6
7
<manifest>
<application>
<meta-data
android:name="design_width_in_dp"
android:value="960"/>
</application>
</manifest>

有两种类型的布局单位可以选择,一个是 主单位 (dp、sp),一个是 副单位 (pt、in、mm)

  • 主单位: 使用 dp、sp 为单位进行布局,侵入性最低,会影响其他三方库页面、三方库控件以及系统控件的布局效果,但 AndroidAutoSize 也通过这个特性,使用 ExternalAdaptManager 实现了在不修改三方库源码的情况下适配三方库的功能
  • 副单位: 使用 pt、in、mm 为单位进行布局,侵入性高,对老项目的支持比较好,不会影响其他三方库页面、三方库控件以及系统控件的布局效果,可以彻底的屏蔽修改 density 所造成的所有未知和已知问题,但这样 AndroidAutoSize 也就无法对三方库进行适配

在使用主单位时,design_width_in_dpdesign_height_in_dp 的单位必须是 dp,计算公式 dp = px / (DPI / 160) 将 px 尺寸转换为 dp 尺寸,如果实在找不到设备的 DPI 那就直接将 px 尺寸除以 3 或者 2 。

忽略适配

如果某个 Activity 想放弃适配,让这个 Activity 实现 CancelAdapt 接口即可,比如修改 density 影响到了老项目中的某些 Activity 页面的布局效果,这时就可以让这个 Activity 实现 CancelAdapt 接口

1
2
3
public class CancelAdaptActivity extends AppCompatActivity implements CancelAdapt {

}

Fragment也是一样

实现 CancelAdapt

1
2
3
public class CancelAdaptFragment extends Fragment implements CancelAdapt {

}

进阶使用

在 AndroidManifest.xml 中填写的设计尺寸,是整个项目的全局设计图尺寸。

但是如果某些 Activity 页面由于某些原因,这个页面的设计图尺寸和在 AndroidManifest.xml 中填写的设计图尺寸不一样该怎么办呢?

则可以让这个页面的 Activity 实现 CustomAdapt ,CustomAdapt 接口的第一个方法可以修改当前页面的设计尺寸。

自定义Activity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CustomAdaptActivity extends AppCompatActivity implements CustomAdapt {

/**
* 是否按照宽度进行等比例适配 (为了保证在高宽比不同的屏幕上也能正常适配, 所以只能在宽度和高度之中选择一个作为基准进行适配)
*
* @return {@code true} 为按照宽度进行适配, {@code false} 为按照高度进行适配
*/
@Override
public boolean isBaseOnWidth() {
return false;
}

@Override
public float getSizeInDp() {
return 667;
}
}

自定义Fragment

Fragment 的自定义方式和 Activity 是一样的,只不过在使用前需要先在 App 初始化时开启对 Fragment 的支持

1
AutoSizeConfig.getInstance().setCustomFragment(true);

实现 CustomAdapt

1
2
3
4
5
6
7
8
9
10
11
12
public class CustomAdaptFragment extends Fragment implements CustomAdapt {

@Override
public boolean isBaseOnWidth() {
return false;
}

@Override
public float getSizeInDp() {
return 667;
}
}

偶尔失效解决方案

在任何情况下本来适配正常的布局突然出现适配失效,适配异常等问题,只要重写 Activity 的 getResources() 方法即可,如果是 Dialog、PopupWindow 等控件出现适配失效或适配异常,同样在每次 show() 之前调用 autoConvertDensity() 即可。

方法参数

1
public static void autoConvertDensity(Resources resources, float sizeInDp, boolean isBaseOnWidth){}

Java

1
2
3
4
5
6
@Override
public Resources getResources() {
//需要升级到 v1.1.2 及以上版本才能使用 AutoSizeCompat
AutoSizeCompat.autoConvertDensityOfGlobal((super.getResources());//如果没有自定义需求用这个方法
return super.getResources();
}

如果有自定义需求就用这个方法

1
2
3
4
5
6
@Override
public Resources getResources() {
//需要升级到 v1.1.2 及以上版本才能使用 AutoSizeCompat
AutoSizeCompat.autoConvertDensity((super.getResources(), 960, true);//如果有自定义需求就用这个方法
return super.getResources();
}

Kotlin中

1
2
3
4
5
override fun getResources(): Resources {
//需要升级到 v1.1.2 及以上版本才能使用 AutoSizeCompat
AutoSizeCompat.autoConvertDensityOfGlobal(super.getResources())
return super.getResources()
}

1
2
3
4
5
override fun getResources(): Resources {
//需要升级到 v1.1.2 及以上版本才能使用 AutoSizeCompat
AutoSizeCompat.autoConvertDensity(super.getResources(), 960f, true)
return super.getResources()
}

屏幕宽高

要在Android应用中获取屏幕的宽度和高度,你可以使用DisplayMetrics类。

以下是获取屏幕宽度和高度的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;

public class ZScreenUtils {

public static int getScreenWidth(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
if (windowManager != null) {
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.widthPixels;
}
return 0;
}

public static int getScreenHeight(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
if (windowManager != null) {
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.heightPixels;
}
return 0;
}

public static int getScreenWidthInDP(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
if (windowManager != null) {
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
float density = context.getResources().getDisplayMetrics().density;
return (int) (displayMetrics.widthPixels / density);
}
return 0;
}

public static int getScreenHeightInDP(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
if (windowManager != null) {
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
float density = context.getResources().getDisplayMetrics().density;
return (int) (displayMetrics.heightPixels / density);
}
return 0;
}
}

你可以在任何Context对象可用的地方调用这两个方法,比如在Activity中:

1
2
3
4
5
6
7
8
val screenWidth: Int = ZScreenUtils.getScreenWidth(baseContext)
val screenHeight: Int = ZScreenUtils.getScreenHeight(baseContext)
Log.i(TAG, "onCreate: screenWidth:${screenWidth}")
Log.i(TAG, "onCreate: screenHeight:${screenHeight}")
val screenWidthDp: Int = ZScreenUtils.getScreenWidthInDP(baseContext)
val screenHeightDp: Int = ZScreenUtils.getScreenHeightInDP(baseContext)
Log.i(TAG, "onCreate: screenWidthDp:${screenWidthDp}")
Log.i(TAG, "onCreate: screenHeightDp:${screenHeightDp}")

记得在AndroidManifest.xml中添加必要的权限:

1
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

这样你就可以获取到屏幕的宽度和高度了。