Android屏幕适配

本文深入探讨了Android屏幕适配的原因,解释了像素、屏幕尺寸、分辨率、像素密度和密度无关像素等核心概念。介绍了如何通过使用dp和sp单位、布局组件适配、备用布局、最小宽度限定符以及今日头条的适配方案来实现屏幕适配。此外,还提到了ConstraintLayout在适配中的作用。
摘要由CSDN通过智能技术生成

为什么Android就得做屏幕适配

由于Android系统是开源的,任何用户、开发者、OEM厂商、运营商都可以对Android进行定制,导致运行Android的设备多种多样,它们屏幕尺寸和像素密度都不甚相同。尽管我们通过基本的缩放和调整大小能使界面适配不同屏幕,但还是需要进一步优化,来确保所有界面能在不同设备上美观地展现出来。

怎么做屏幕适配

几个重要的概念

像素

像素是指由图像的小方格组成的,这些小方块都有一个明确的位置和被分配的色彩数值,小方格颜色和位置就决定该图像所呈现出来的样子。通俗来讲,小时候家里面大屁股电视没有信号的时候,肉眼可以看到一个一个点状的东西就是像素点。

屏幕尺寸

屏幕尺寸指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米

比如常见的屏幕尺寸有2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0等

屏幕分辨率

屏幕分辨率是指在横纵向上的像素点数,单位是px,1px就是1个像素点。一般我们描述屏幕分辨率是以纵向像素*横向像素,比如1080x1920。表示宽度方向上有1080个像素点,长度方向上有1920个像素点。

单位: px pixel ), 1px=1 像素点
Android 手机常见的分辨率: 320x480 480x800 720x1280 1080x1920
UI设计师的设计图会以 px 作为统一的计量单位。

屏幕像素密度

屏幕像素密度是指每英寸上的像素点数,单位是dpi,即“dot per inch”的缩写。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小,分辨率越高,像素密度越大。

假设设备内每英寸有 160 个像素,那么该设备的屏幕像素密度 就是160dpi。

安卓手机对于每类手机屏幕大小都有一个相应的屏幕像素密度:

标准屏幕像素密度(mdpi): 每英寸长度上有160个像素点(160dpi),即称为标准屏幕像素密度(mdpi)。

我们可以通过两个adb命令获取到屏幕分辨率:
<1>adb shell wm size

<2>adb shell dumpsys window displays 

屏幕尺寸、分辨率、像素密度三者关系

一部手机的分辨率是宽x高,屏幕大小是以寸为单位,那么三者的关系是

 

密度无关像素

density-independent pixel ,叫 dip dp ,与终端上的实际物理像素点无关。可以保证在不同屏幕像素密度的设备上显示相同的效果。

 Android开发时用dp而不是px单位设置图片大小,是Android特有的单位。

场景:假如同样都是画一条长度是屏幕一半的线,如果使用 px 作为计量单位,那么 480x800 分辨率手机上设置应为240px ;在 320x480 的手机上应设置为 160px ,二者设置就不同了;如果使用 dp为单位,在这两种分辨率下, 160dp 都显示为屏幕一半的长度。

Android中,规定以160dpi(即屏幕分辨率为320x480)为基准:1dp=1px  (谷歌给的标准)

dp px 的转换:px = dp * (dpi / 160)
逻辑密度:density = (dpi / 160)
其实我们在布局文件中不管用的是dp还是sp还是px,最终都是以px为单位渲染到屏幕上的,具体可以看如下源码:

独立比例像素

sp,即scale-independent pixels,与dp类似,用来设置Android中文本显示大小,且可以根据文字大小首选项进行缩放。当文字尺寸是“正常”时1sp=1dp,当文字尺寸是“大”或“超大”时,1sp>1dp。类似我们在windows里调整字体尺寸 以后的效果——窗口大小不变,只有文字大小改变。

适配方案

几个重要的基本概念搞明白之后,我们再来看看屏幕适配方案。

布局组件适配

布局文件中使用wrap_content,match_parent,weight, dp, 0dp来控制视图组件的宽度和高度,避免使用px这种绝对尺寸。

按理来说,谷歌给我们开发者提供的dp和sp就已经可以做到屏幕适配了,为什么还会在某些手机上不能完全满足显示效果。其实我们通过px = dp * (dpi / 160) 这个公式就可以发现,如果手机厂商不按照谷歌的标准来,比如1080*1920的设备本来dpi应该是480,但是厂商设备的尺寸却是5英 寸,那么最终算出来的dpi大概就是440,那dpi都不一样了,dp当然就不一样呀。

布局适配(备用布局)

布局适配是指我们根据不同屏幕尺寸的设备设计不同的备用布局,以便应用在运行时根据屏幕的配置加载相应的UI布局。谷歌官方为我们提供了各种限定符。

size(尺寸)限定符(注:这种方式只适合Android 3.2之前的版本

res目录新建一个layout-large文件夹,布局名字和res/layout里面的同名。在平板电脑和电视的屏幕(>7英寸)上:实施 双面板 模式以同时显示更多内容,它会加载res/layout-large里面的布局,在手机较小的屏幕上:使用 单面板 模式分别显示内容,加载的是res/layout里面的同名布局。

 Smallest-Width(最小宽度)限定符

使用“最小宽度”屏幕尺寸限定符,我们可以为具有最小宽度(以密度无关像素 dp 或 dip 为度量单位)的屏幕提供备用布局。

通过将屏幕尺寸描述为密度无关像素的度量值,Android 允许我们创建专为非常具体的屏幕尺寸而设计的布局,同时让您不必对不同的像素密度有任何担心。

例如,您可以创建一个名为 main_activity 且针对手机和平板电脑进行了优化的布局,方法是在目录中创建该文件的不同版本,如下所示:

res/layout/main_activity.xml           # For handsets (smaller than 600dp available width)
res/layout-sw600dp/main_activity.xml   # For 7” tablets (600dp wide and bigger)

最小宽度限定符指定屏幕两侧的最小尺寸,而不考虑设备当前的屏幕方向,因此这是一种指定布局可用的整体屏幕尺寸的简单方法。

下面是其他最小宽度值与典型屏幕尺寸的对应关系:

  • 320dp:典型手机屏幕(240x320 ldpi、320x480 mdpi、480x800 hdpi 等)。
  • 480dp:约为 5 英寸的大手机屏幕 (480x800 mdpi)。
  • 600dp:7 英寸平板电脑 (600x1024 mdpi)。
  • 720dp:10 英寸平板电脑(720x1280 mdpi、800x1280 mdpi 等)。

今日头条的适配方案

import android.app.Activity;
import android.app.Application;
import android.content.ComponentCallbacks;
import android.content.res.Configuration;
import android.util.DisplayMetrics;

import androidx.annotation.NonNull;

public class WindowUtils {
	/**
	 * 系统的Density
	 */
	private static float sNonCompatDensity;
	
	/**
	 * 系统的ScaledDensity
	 */
	private static float sNonCompatScaleDensity;
	
	public static void setCustomDensity(Activity activity) {
		Application application = activity.getApplication();
		final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
		if (sNonCompatDensity == 0) {
			// 系统的Density:长度相关参数(dp)
			sNonCompatDensity = appDisplayMetrics.density;
			// 系统的ScaledDensity:字体相关参数(sp)
			sNonCompatScaleDensity = appDisplayMetrics.scaledDensity;
			// 监听在系统设置中切换字体
			application.registerComponentCallbacks(new ComponentCallbacks() {
				@Override
				public void onConfigurationChanged(@NonNull Configuration newConfig) {
					if (newConfig != null && newConfig.fontScale > 0) {
						sNonCompatScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
					}
				}
				
				@Override
				public void onLowMemory() {
				
				}
			});
		}
		/*
		 * 如果UX提供的是480dp的设计稿,也就是分辨率为1080*1920的设计稿
		 *
		 * 把系统的逻辑密度(density)和像素密度(dpi)根据UX提供的设计稿进行处理
		 */
		final float targetDensity = appDisplayMetrics.widthPixels / 480;
		final float targetScaledDensity = targetDensity * (sNonCompatScaleDensity / sNonCompatDensity);
		final int targetDensityDpi = (int) (160 * targetDensity);
		
		/**
		 * 设置Application对应的DisplayMetrics参数
		 */
		appDisplayMetrics.density = targetDensity;
		appDisplayMetrics.scaledDensity = targetScaledDensity;
		appDisplayMetrics.densityDpi = targetDensityDpi;
		
		/**
		 * 同时也要设置Activity对应的DisplayMetrics参数
		 */
		final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
		activityDisplayMetrics.density = targetDensity;
		activityDisplayMetrics.scaledDensity = targetScaledDensity;
		activityDisplayMetrics.densityDpi = targetDensityDpi;
	}
}

约束布局

可以在Api9以上的Android系统使用ConstraintLayout(约束布局),它的出现主要是为了解决布局嵌套过多导致解析布局和绘制UI消耗时间过长的问题,从AS 2.3 起,官方的模板默认使 ConstraintLayout,也会自动帮我们添加约束布局的依赖。同时约束布局也可以解决90%以上的安卓屏幕适配问题。

implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

约束布局基本属性

控制 View 最大尺寸和最小尺寸的属性,可以设置到 ConstraintLayout 上来控制其尺寸。

相对定位 

与相对布局一样,约束布局中如果对控件没有添加约束时,默认控件会出现根布局的左上角。

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/color_7"
        android:gravity="center"
        android:text="没有约束"
        android:textSize="@dimen/text_size_32"
        app:layout_constraintWidth_min="@dimen/min_width_dip_120"
        tools:ignore="MissingConstraints" />    

    <TextView
        android:id="@+id/target"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintWidth_min="@dimen/min_width_dip_120"
        android:text="目标控件"
        android:gravity="center"
        android:textSize="@dimen/text_size_32"
        android:background="@color/color_10"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />    

     <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/color_3"
        android:gravity="center"
        android:text="A"
        android:textSize="@dimen/text_size_32"
        app:layout_constraintEnd_toStartOf="@+id/target"
        app:layout_constraintTop_toTopOf="@+id/target"
        app:layout_constraintWidth_min="@dimen/min_width_dip_120" />

上面的代码效果如下:

首先目标控件是以父布局也就是根布局为基准,上下左右方向都与根布局建立约束,最终效果就是居中显示;而A控件又把目标控件作为基准,通过app:layout_constraintEnd_toStartOf="@+id/target"将其末端与目标控件始端建立约束;再通过app:layout_constraintTop_toTopOf="@+id/target"将其顶部与目标控件顶部建立约束。

关于相对定位的常用属性:
layout_constraintLeft_toLeftOf
layout_constraintLeft_toRightOf
layout_constraintRight_toLeftOf
layout_constraintRight_toRightOf
layout_constraintStart_toEndOf
layout_constraintStart_toStartOf
layout_constraintEnd_toStartOf
layout_constraintEnd_toEndOf
layout_constraintTop_toTopOf
layout_constraintTop_toBottomOf
layout_constraintBottom_toTopOf
layout_constraintBottom_toBottomOf
layout_constraintBaseline_toBaselineOf

PS:start/end是为了适配比如阿拉伯国家阅读习惯的,阿拉伯国家是从右往左阅读的。

Baseline指的是文本基线,两个TextView的高度不一致,但是我们却希望它们文本对齐,这个时候就可以使用layout_constraintBaseline_toBaselineOf,代码如下:

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/color_10"
        android:gravity="center"
        android:text="文本对齐"
        android:textSize="@dimen/text_size_32"
        app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintBaseline_toBaselineOf="@+id/target"
        app:layout_constraintWidth_min="@dimen/min_width_dip_120" />

    <TextView
        android:id="@+id/target"
        android:layout_width="@dimen/horizontal_min_dip_160"
        android:layout_height="@dimen/vertical_min_dip_160"
        android:background="@color/color_10"
        android:gravity="center"
        android:text="中间位置"
        android:textSize="@dimen/text_size_32"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_min="@dimen/min_width_dip_120" />

效果图:

未完待续... 

参考文档:
《设备兼容性概览》https://developer.android.com/guide/practices/compatibility?hl=zh-cn#java

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值