IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android 屏幕适配问题 -> 正文阅读

[移动开发]Android 屏幕适配问题

Android 屏幕适配问题

基本概念

ppi

ppi(Pixels Per Inch)硬件像素密度:在物理设备上,每英寸包含的物理像素点数量,ppi是不能修改的。

dpi

dpi(Dots Per Inch)屏幕像素密度:是软件概念,每英寸包含多少个点,dpi一般情况下是能修改的。

density

density 密度:1个dp对应多少个px。

dp

dp(density-independent pixel)密度无关像素:与设备的像素密度无关的像素,Google推荐使用。

sp

sp(scale-independent pixel)比例无关像素:与dp类似,但会随着系统的字体大小改变而调整。

密度限定符

不同屏幕像素密度大设备对应了不同的密度限定符。

密度类型说明像素密度denstiy
ldpi低密度屏幕120dpi0.75
mdpi中密度屏幕(基准密度)160dpi1
hdpi高密度屏幕240dpi1.5
xhdpi超高密度屏幕320dpi2
xxhdpi超超高密度屏幕480dpi3

转换公式

px = density * dp

density = dpi /160

px = dp * (dpi / 160)

获取屏幕信息

不同的手机屏幕上,1pd对应的px值可能忽悠很大差异。如,在小屏幕上1dp可能对应1px,在大屏幕圣桑可能对应1px

可以通过displayMetrics获取详细信息:

val displayMetrics = applicationContext.resources.displayMetrics

//输出: DisplayMetrics{density=3.0, width=1080, height=1920, scaledDensity=3.0, xdpi=480.0, ydpi=480.0}

说明:

  • 屏幕像素密度为480dpi
  • density为3,表示在这个设备上1dp=3px
  • 屏幕宽高为1080*1920px,也就是360*640dp

Android系统定义的屏幕像素密度基准值是160dp,也就是1dp=1px,因此480dp下1dp=3px

适配问题

在布局文件中使用的单位值,最终都会被系统转换为px。Google官方推荐使用dp作为单位值,系统会根据屏幕的实际情况自动完成dp与px之间的转换。

如:将一个View的宽度设置为180dp,在标准屏幕下如540*960px/240dpi 即360*640dp720*1280px/320dpi 即360*640dp1080*1920px/480dpi 即360*640dp中都是显示一半空间。由于屏幕像素密度的存在,使得同一套dp在不同的屏幕下显示相同的效果。但是dp值只适用于大部分正常情况。

在这里插入图片描述

屏幕适配问题的根源是设备碎片化:系统碎片化、屏幕尺寸碎片化、屏幕像素密度碎片化。

屏幕尺寸碎片化问题,如:dpi都为320,但屏幕尺寸不相同时,同样的180dp在720*1280px/320dpi下占据50%的空间,但在900*1600px/320dpi下占据40%的空间,两边的显示效果就不一样了。这是用dp值无法解决的。

在这里插入图片描述

屏幕像素密度碎片化问题,如:尺寸是720*1280px/320dpi 即360*640dp,尺寸为720*1280px/360dpi 即320*568dp,这时相同的屏幕尺寸但是dpi不同,导致同样的180dp所占据的空间是不一样的。

适配方案

在布局文件中声明的dp值,最终都通过TypedValue#applyDimension()方法转换为px值,即:density * dp

public static float applyDimension(int unit, float value, DisplayMetrics metrics) {
    switch (unit) {
        case COMPLEX_UNIT_PX:
            return value;
        case COMPLEX_UNIT_DIP:
            return value * metrics.density;
        case COMPLEX_UNIT_SP:
            return value * metrics.scaledDensity;
        case COMPLEX_UNIT_PT:
            return value * metrics.xdpi * (1.0f/72);
        case COMPLEX_UNIT_IN:
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM:
            return value * metrics.xdpi * (1.0f/25.4f);
    }
    return 0;
}

今日头条方案

详细文档

本质是通过获取屏幕宽度,再除以基准dp值,获取新的density,通过修改旧的density,最终改变View的尺寸。

fun setCustomDensity(activity: Activity, application: Application, designWidthDp: Int) {
    val appDisplayMetrics = application.resources.displayMetrics
    val targetDensity = 1.0f * appDisplayMetrics.widthPixels / designWidthDp
    val targetDensityDpi = (targetDensity * 160).toInt()
    appDisplayMetrics.density = targetDensity
    appDisplayMetrics.densityDpi = targetDensityDpi
    val activityDisplayMetrics = activity.resources.displayMetrics
    activityDisplayMetrics.density = targetDensity
    activityDisplayMetrics.densityDpi = targetDensityDpi
}
override fun onCreate(savedInstanceState: Bundle?) {
    val displayMetrics = applicationContext.resources.displayMetrics
    log("修改前:${displayMetrics.toString()}")
    setCustomDensity(this, application, 360)
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    log("修改后:${displayMetrics.toString()}")
}

//修改前:DisplayMetrics{density=2.0, width=900, height=1600, scaledDensity=2.0, xdpi=320.0, ydpi=320.0}
//修改后:DisplayMetrics{density=2.5, width=900, height=1600, scaledDensity=2.0, xdpi=320.0, ydpi=320.0}

smallestWidth 最小宽度限定

smallestWidth即最小宽度适配,是系统原生支持的一种适配方案。

它是不考虑屏幕方向,指的是最短的那个边,按比例分配,如:设计稿是720*1280px/360dpi,那么基准分辨率就是设计稿的宽度宽度为360dp,表示把宽度分为360份,得到以下尺寸:

<dimen name="dp_2">1dp</dimen>
<dimen name="dp_1">2dp</dimen>
...
<dimen name="dp_360 ">360dp</dimen>

360dp为基准,在不同最小宽度的文件夹下按比例缩放尺寸,如sw480dp

<dimen name="dp_2">1.333dp</dimen>
<dimen name="dp_1">2.666dp</dimen>
...
<dimen name="dp_360 ">480dp</dimen>

在这里插入图片描述

在这里插入图片描述

使用最小宽度限定符适配解决大部分情况下的适配问题。

布局适配

  • 可以使用LinearLayout(线性布局)和百分比布局控制权重比例。

  • 使用RelativeLayout(相对布局)和ConstraintLayout(约束布局)控制关联关系。

其他

dimens 文件生成工具

import java.io.File
import java.io.FileOutputStream
import kotlin.math.min

private const val XML_FILE_NAME = """dimens.xml"""
private const val XML_HEADER = """<?xml version="1.0" encoding="utf-8"?>"""
private const val XML_RESOURCE_START = """<resources>"""
private const val XML_SW_DP_TAG = """<string name="sw_dp">%ddp</string>"""
private const val XML_DIMEN_TEMPLATE_TO_DP = """<dimen name="DIMEN_%ddp">%.2fdp</dimen>"""
private const val XML_DIMEN_TEMPLATE_TO_PX = """<dimen name="DIMEN_%dpx">%.2fdp</dimen>"""
private const val XML_RESOURCE_END = """</resources>"""

private const val DESIGN_WIDTH_DP = 360
private const val DESIGN_HEIGHT_DP = 640

private const val DESIGN_WIDTH_PX = 720
private const val DESIGN_HEIGHT_PX = 1280

fun main() {
    val designWidthDp = min(DESIGN_WIDTH_DP, DESIGN_HEIGHT_DP)
    val srcDirFileDp = File("src-dp")
    makeDimens(designWidthDp, srcDirFileDp, XML_DIMEN_TEMPLATE_TO_DP)

    val designWidthPx = min(DESIGN_WIDTH_PX, DESIGN_HEIGHT_PX)
    val srcDirFilePx = File("src-px")
    makeDimens(designWidthPx, srcDirFilePx, XML_DIMEN_TEMPLATE_TO_PX)
}

private fun makeDimens(designWidth: Int, srcDirFile: File, xmlDimenTemplate: String) {
    if (srcDirFile.exists() && !srcDirFile.deleteRecursively()) {
        return
    }
    srcDirFile.mkdirs()
    val smallestWidthList = mutableListOf<Int>().apply {
        for (i in 320..460 step 10) {
            add(i)
        }
    }.toList()
    for (smallestWidth in smallestWidthList) {
        makeDimensFile(designWidth, smallestWidth, xmlDimenTemplate, srcDirFile)
    }
}

private fun makeDimensFile(
    designWidth: Int,
    smallestWidth: Int,
    xmlDimenTemplate: String,
    srcDirFile: File
) {
    val dimensFolderName = "values-sw" + smallestWidth + "dp"
    val dimensFile = File(srcDirFile, dimensFolderName)
    dimensFile.mkdirs()
    val fos = FileOutputStream(dimensFile.absolutePath + File.separator + XML_FILE_NAME)
    fos.write(generateDimens(designWidth, smallestWidth, xmlDimenTemplate).toByteArray())
    fos.flush()
    fos.close()
}

private fun generateDimens(designWidth: Int, smallestWidth: Int, xmlDimenTemplate: String): String {
    val sb = StringBuilder()
    sb.append(XML_HEADER)
    sb.append("\n")
    sb.append(XML_RESOURCE_START)
    sb.append("\n")
    sb.append("    ")
    sb.append(String.format(XML_SW_DP_TAG, smallestWidth))
    sb.append("\n")
    for (i in 1..designWidth) {
        val dpValue = i.toFloat() * smallestWidth / designWidth
        sb.append("    ")
        sb.append(String.format(xmlDimenTemplate, i, dpValue))
        sb.append("\n")
    }
    sb.append(XML_RESOURCE_END)
    return sb.toString()
}

dimens 文件生成插件

一、需要在AndroidStudio中安装ScreenMatch插件。

二、在默认values文件夹下准备一份dimens.xml文件。

三、鼠标右键ScreenMatch选项,立即会生成多套dimens文件。

四、可以打开工程目录下screenMatch.properties文件,配置一些其他信息。

在这里插入图片描述

在这里插入图片描述

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-03-21 21:02:21  更:2022-03-21 21:06:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 19:31:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码