android 全局dp单位,Android听说你还在用dp单位做屏幕适配?

前言

屏幕适配一直是Android开发人员躲避不开的话题,更多的同学使用dp单位结合权重去做屏幕适配,但是当设备的物理尺寸存在差异的时候,dp就显得无能为力了。为4.3寸屏幕准备的UI,运行在5.0寸的屏幕上,很可能在右侧和下侧存在大量的空白。而5.0寸的UI运行到4.3寸的设备上,很可能显示不下。也有同学使用GooGle的百分比布局,但是实践过程中需要增加代码量,也没有那么简单高效,有没有一种无脑按照UI设计图设置宽高就能完美适配不同机型的方案?

思路

我们可以按照百分比布局的思想创造出一个全新的单位变量,来衡量屏幕到底是多少份,无论是480 x 800 还是720 x 1280 甚至1080 x 1920分辨率的手机,我们都可以把他的宽分成100份,高分成100份。

math?formula=%5Ccolor%7Bred%7D%7B%E4%BB%A5480*800%E7%9A%84%E6%89%8B%E6%9C%BA%E6%9D%A5%E8%AF%B4%7D

一份宽就是480/100 =

math?formula=%5Ccolor%7Bred%7D%7B4.8%E5%83%8F%E7%B4%A0%7D

一份高就是800/100 =

math?formula=%5Ccolor%7Bred%7D%7B8%E5%83%8F%E7%B4%A0%7D

math?formula=%5Ccolor%7Bred%7D%7B%E4%BB%A5720*1280%E7%9A%84%E6%89%8B%E6%9C%BA%E6%9D%A5%E8%AF%B4%EF%BC%9A%7D

一份宽就是720/100 =

math?formula=%5Ccolor%7Bred%7D%7B%207.2%E5%83%8F%E7%B4%A0%7D

一份高就是1280/100 =

math?formula=%5Ccolor%7Bred%7D%7B12.8%E5%83%8F%E7%B4%A0%7D

在布局的时候,比如设计稿上需要一个View宽占屏幕宽度一半,高也占屏幕一半 如下图

9a837dc375f0

image.png

那我们就已经知道宽设置成50份,高也设置成50份,就可以完美适配所有分辨率的屏幕。

android:layout_width="50份"

android:layout_height="50份"

math?formula=%5Ccolor%7Bred%7D%7B%E5%AF%B9%E4%BA%8E480%20*%20800%E7%9A%84%E6%9C%BA%E5%9E%8B%7D

宽实际上就是50份x

math?formula=%5Ccolor%7Bred%7D%7B4.8%E5%83%8F%E7%B4%A0%7D = 240像素

高实际上就是50份x

math?formula=%5Ccolor%7Bred%7D%7B8%E5%83%8F%E7%B4%A0%7D = 400像素

math?formula=%5Ccolor%7Bred%7D%7B%E5%AF%B9%E4%BA%8E720*1280%E7%9A%84%E6%9C%BA%E5%9E%8B%7D

宽实际上就是50份x

math?formula=%5Ccolor%7Bred%7D%7B7.2%E5%83%8F%E7%B4%A0%7D = 360像素

高实际上就是50份x

math?formula=%5Ccolor%7Bred%7D%7B12.8%E5%83%8F%E7%B4%A0%7D = 640像素

伪实践

按照UI设计师给我们的蓝湖设计稿为例

9a837dc375f0

蓝湖设计稿.png

UI设计师给我们了360*640设计原稿 那么我们可以把宽分成360份,高分成640份。

math?formula=%5Ccolor%7Bred%7D%7B%E4%BB%A5480*800%E7%9A%84%E6%89%8B%E6%9C%BA%E6%9D%A5%E8%AF%B4%7D

一份宽就是480/360 =

math?formula=%5Ccolor%7Bred%7D%7B1.33%E5%83%8F%E7%B4%A0%7D

一份高就是800/640 =

math?formula=%5Ccolor%7Bred%7D%7B1.25%E5%83%8F%E7%B4%A0%7D

math?formula=%5Ccolor%7Bred%7D%7B%E4%BB%A5720*1280%E7%9A%84%E6%89%8B%E6%9C%BA%E6%9D%A5%E8%AF%B4%EF%BC%9A%7D

一份宽就是720/360 =

math?formula=%5Ccolor%7Bred%7D%7B%202%E5%83%8F%E7%B4%A0%7D

一份高就是1280/640 =

math?formula=%5Ccolor%7Bred%7D%7B2%E5%83%8F%E7%B4%A0%7D

9a837dc375f0

蓝湖设计稿2.png

那么我们在写这个View宽高布局的时候如下

android:layout_width="48份"

android:layout_height="24份"

工程实践

工程方案1(已经过多个项目实践)

针对你所需要适配的手机屏幕的分辨率各自建立一个value文件

9a837dc375f0

image.png

比如以蓝湖设计稿320*480的分辨率为基准

宽度为320,将任何分辨率的宽度分为320份,取值为x1-x320

高度为480,将任何分辨率的高度分为480份,取值为y1-y480

x1相当于1份宽

y1相当于1份高

例如对于800*480的宽度480:

9a837dc375f0

image.png

可以看到x1 = 480 / 基准 = 480 / 320 = 1.5 以此类推

假设我现在需要在屏幕中心有个按钮,宽度和高度为我们屏幕宽度的1/2,我可以怎么编写布局文件呢?

android:layout_gravity="center"

android:gravity="center"

android:text="@string/hello_world"

android:layout_width="@dimen/x160"

android:layout_height="@dimen/x160"/>

可以看到我们的宽度和高度定义为x160,其实就是宽度的50%

不同机型的效果图

9a837dc375f0

image.png

好了,有个最主要的问题,就是分辨率这么多,难道我们要自己计算,然后手写?

math?formula=%5Ccolor%7Bred%7D%7B%E4%B8%8B%E8%BD%BD%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E5%B7%A5%E5%85%B7%7D

jar包内置了常用的分辨率,默认基准为480*320,当然对于特殊需求,通过命令行指定即可

例如基准 1280 * 800 ,额外支持尺寸:1152 * 735;4500 * 3200

java -jar xx.jar width height width,height_width,height

9a837dc375f0

20150503173911632.gif

这样拷贝到自己的项目中就可以使用了

缺点

新建了很多value文件,给apk新增了3-4M的体积

默认基准分辨率只能适配99%的机型,如果遇到value里面不存在的分辨率则不能适配,需要动态增加该分辨率下的value文件

工程方案2(头条的适配方案)

大致思路:通过重写Activity的getResources(),重写冷门单位pt作为基准单位,1pt代表前文提到的一份

AdaptScreenUtils

public final class AdaptScreenUtils {

private static List sMetricsFields;

private AdaptScreenUtils() {

throw new UnsupportedOperationException("u can't instantiate me...");

}

/**

* Adapt for the horizontal screen, and call it in {@link android.app.Activity#getResources()}.

*/

public static Resources adaptWidth(final Resources resources, final int designWidth) {

float newXdpi = (resources.getDisplayMetrics().widthPixels * 72f) / designWidth;

applyDisplayMetrics(resources, newXdpi);

return resources;

}

/**

* Adapt for the vertical screen, and call it in {@link android.app.Activity#getResources()}.

*/

public static Resources adaptHeight(final Resources resources, final int designHeight) {

return adaptHeight(resources, designHeight, false);

}

/**

* Adapt for the vertical screen, and call it in {@link android.app.Activity#getResources()}.

*/

public static Resources adaptHeight(final Resources resources, final int designHeight, final boolean includeNavBar) {

float screenHeight = (resources.getDisplayMetrics().heightPixels

+ (includeNavBar ? getNavBarHeight(resources) : 0)) * 72f;

float newXdpi = screenHeight / designHeight;

applyDisplayMetrics(resources, newXdpi);

return resources;

}

private static int getNavBarHeight(final Resources resources) {

int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");

if (resourceId != 0) {

return resources.getDimensionPixelSize(resourceId);

} else {

return 0;

}

}

/**

* @param resources The resources.

* @return the resource

*/

public static Resources closeAdapt(final Resources resources) {

float newXdpi = Resources.getSystem().getDisplayMetrics().density * 72f;

applyDisplayMetrics(resources, newXdpi);

return resources;

}

/**

* Value of pt to value of px.

*

* @param ptValue The value of pt.

* @return value of px

*/

public static int pt2Px(final float ptValue) {

DisplayMetrics metrics = FWAdSDK.sContext.getResources().getDisplayMetrics();

return (int) (ptValue * metrics.xdpi / 72f + 0.5);

}

/**

* Value of px to value of pt.

*

* @param pxValue The value of px.

* @return value of pt

*/

public static int px2Pt(final float pxValue) {

DisplayMetrics metrics = FWAdSDK.sContext.getResources().getDisplayMetrics();

return (int) (pxValue * 72 / metrics.xdpi + 0.5);

}

private static void applyDisplayMetrics(final Resources resources, final float newXdpi) {

resources.getDisplayMetrics().xdpi = newXdpi;

FWAdSDK.sContext.getResources().getDisplayMetrics().xdpi = newXdpi;

applyOtherDisplayMetrics(resources, newXdpi);

}

static void preLoad() {

applyDisplayMetrics(Resources.getSystem(), Resources.getSystem().getDisplayMetrics().xdpi);

}

private static void applyOtherDisplayMetrics(final Resources resources, final float newXdpi) {

if (sMetricsFields == null) {

sMetricsFields = new ArrayList<>();

Class resCls = resources.getClass();

Field[] declaredFields = resCls.getDeclaredFields();

while (declaredFields != null && declaredFields.length > 0) {

for (Field field : declaredFields) {

if (field.getType().isAssignableFrom(DisplayMetrics.class)) {

field.setAccessible(true);

DisplayMetrics tmpDm = getMetricsFromField(resources, field);

if (tmpDm != null) {

sMetricsFields.add(field);

tmpDm.xdpi = newXdpi;

}

}

}

resCls = resCls.getSuperclass();

if (resCls != null) {

declaredFields = resCls.getDeclaredFields();

} else {

break;

}

}

} else {

applyMetricsFields(resources, newXdpi);

}

}

private static void applyMetricsFields(final Resources resources, final float newXdpi) {

for (Field metricsField : sMetricsFields) {

try {

DisplayMetrics dm = (DisplayMetrics) metricsField.get(resources);

if (dm != null) dm.xdpi = newXdpi;

} catch (Exception e) {

Log.e("AdaptScreenUtils", "applyMetricsFields: " + e);

}

}

}

private static DisplayMetrics getMetricsFromField(final Resources resources, final Field field) {

try {

return (DisplayMetrics) field.get(resources);

} catch (Exception e) {

Log.e("AdaptScreenUtils", "getMetricsFromField: " + e);

return null;

}

}

}

使用方法

以宽度320为基准

@Override

public Resources getResources() {

return AdaptScreenUtils.adaptWidth(super.getResources(),320);

}

假设我现在需要在屏幕中心有个按钮,宽度和高度为我们屏幕宽度的1/2,我可以怎么编写布局文件呢?

android:layout_gravity="center"

android:gravity="center"

android:text="@string/hello_world"

android:layout_width="160pt"

android:layout_height="160pt"/>

优点

1. 无侵入性

用了这个之后你依然可以使用dp包括其他任何单位,对你从前使用的布局不会造成任何影响,在老项目中开发新功能你可以胆大地加入该适配方案,新项目的话更可以毫不犹豫地采用该适配,并且在关闭该关闭后,pt 效果等同于 dp 哦。

2. 灵活性高

如果你想要对某个 View 做到不同分辨率的设备下,使其尺寸在适配维度上所占比例一致的话,那么对它使用 pt 单位即可,如果你不想要这样的效果,而是想要更大尺寸的设备显示更多的内容,那么你可以像从前那样写 dp、sp 什么的即可,结合这两点,在界面布局上你就可以游刃有余地做到你想要的效果。

3. 不会影响系统 View 和三方 View 的大小

这点其实在无侵入性中已经表现出来了,由于头条的方案是直接修改 DisplayMetrics#density 的 dp 适配,这样会导致系统 View 尺寸和原先不一致,比如 Dialog、Toast、 尺寸,同样,三方 View 的大小也会和原先效果不一致,这也就是我选择 pt 适配的原因之一。

4. 不会失效

因为不论头条的适配还是 AndroidAutoSize,都会存在 DisplayMetrics#density 被还原的情况,需要自己重新设置回去,最显著的就是界面中存在 WebView 的话,由于其初始化的时候会还原 DisplayMetrics#density 的值导致适配失效,当然这点已经有解决方案了,但还会有很多其他情况会还原 DisplayMetrics#density 的值导致适配失效。而我这方案就是为了解决这个痛点,不让 DisplayMetrics 中的值被还原导致适配失效。

效果

480 x 800 - mdpi(160dpi)

9a837dc375f0

image

720 x 1280 - xhdpi(320dpi)

9a837dc375f0

image

1080 x 1920 - xxhdpi(480dpi)

9a837dc375f0

image

1440x2560 - 560dpi

9a837dc375f0

image

可以看到效果图中 WebView 对之后的 View 并没有产生适配失效的问题,这是之前适配所不能解决的问题。

如何创建预览?

在 AS 中 Tools -> AVD Manager -> Create Virtual Device...,我们以适配 1080 x 1920px 为例,具体操作如下:

9a837dc375f0

image

创建完设备我们在预览界面选中这个设备即可看到 pt 单位效果。

注:有的设备需要重启电脑才可以选中设备效果

设计师给你的设计图尺寸是多少,那你就建多少尺寸的设备即可,比如是 720 x 1280px 的,那你把上图的尺寸换成 720 和 1280,再计算下屏幕尺寸即可,如果是 360 x 640dp 的话,那就把上图的尺寸换成 360 和 640,再计算下屏幕尺寸即可,不用去 care 单位到底是什么,设计图标注多少那你就写多少即可,无需换算。适配的时候传入这个维度的尺寸值即可,比如 720 x 1280 的宽度适配,那就传入 720 即可。

参考链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 Android 应用中实现全局监听屏幕点击,可以使用以下步骤: 1. 创建一个继承自 Service 的服务类 ScreenClickService。 2. 在 ScreenClickService 中创建一个继承自 GestureDetector.SimpleOnGestureListener 的手势监听器类 ScreenClickGestureListener。 3. 在 ScreenClickGestureListener 中重写 onSingleTapConfirmed 方法,该方法会在用户单击屏幕时被调用。 4. 在 ScreenClickService 中创建一个 WindowManager,并使用其 addView 方法添加一个全屏幕的透明 View。 5. 在添加的 View 上设置一个 OnTouchListener,将其与 ScreenClickGestureListener 关联。 6. 在 AndroidManifest.xml 文件中注册 ScreenClickService。 以下是示例代码: ```java public class ScreenClickService extends Service { private WindowManager windowManager; private View screenClickView; private GestureDetector gestureDetector; @Override public void onCreate() { super.onCreate(); // 创建一个全屏幕的透明 View screenClickView = new View(this); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); windowManager.addView(screenClickView, layoutParams); // 创建手势监听器 gestureDetector = new GestureDetector(this, new ScreenClickGestureListener()); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); // 移除透明 View if (screenClickView != null) { windowManager.removeView(screenClickView); } } @Nullable @Override public IBinder onBind(Intent intent) { return null; } // 手势监听器类 private class ScreenClickGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onSingleTapConfirmed(MotionEvent e) { // 用户单击屏幕时触发的逻辑 Log.d("ScreenClickService", "Screen clicked"); return super.onSingleTapConfirmed(e); } } } ``` 记得在 AndroidManifest.xml 文件中注册 ScreenClickService: ```xml <service android:name=".ScreenClickService" /> ``` 以上代码实现了在 Android 应用中全局监听屏幕点击的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值