Android多解析度適配框架(2)— 原理剖析

探索Android軟鍵盤的疑難雜症
深入探討Android非同步精髓Handler
詳解Android主流框架不可或缺的基石
站在原始碼的肩膀上全解Scroller工作機制


Android多解析度適配框架(1)— 核心基礎
Android多解析度適配框架(2)— 原理剖析
Android多解析度適配框架(3)— 使用指南


自定義View系列教程00–推翻自己和過往,重學自定義View
自定義View系列教程01–常用工具介紹
自定義View系列教程02–onMeasure原始碼詳盡分析
自定義View系列教程03–onLayout原始碼詳盡分析
自定義View系列教程04–Draw原始碼分析及其實踐
自定義View系列教程05–示例分析
自定義View系列教程06–詳解View的Touch事件處理
自定義View系列教程07–詳解ViewGroup分發Touch事件
自定義View系列教程08–滑動衝突的產生及其處理


PS:Android多解析度適配框架視訊教程同步更新啦


前言

在上一篇文章中我們先討論了Andoid中常見的與度量有關的知識和技術;然後對於drawable的載入原理也做了一個完整分析。

在完成這些準備之後,就要直面我們的目標:一套UI圖實現多解析度的適配


思路

國慶的時候出去玩了一圈,回來之後我洗了一張女友的照片放到相框裡面

這裡寫圖片描述

昨天,我又買了一個同款的小號的相框,它的寬和高只有原來的一半。
我現在把原來的照片等比例縮小二分之一是不是就可以將其放入新的相框了呢?

嗯哼,與此類似:為了實現多解析度的適配,我們需要將UI進行等比例的縮放。
比如:可以把一個在高解析度(例如1920*1080)手機上的佈局等比例縮小後展示在其他解析度(例如800*480)的手機上。

在知曉了總體思路後,我們再來拆解Android多解析度適配框架的具體實現。


UI切圖

在多數情況下UI設計師會切幾套對應的圖,我們再將其放入drawable-ldpi、drawable-mdpi、drawable-hdpi、drawable-xhdpi、drawable-xxhdpi、drawable-xxxhdpi資料夾即可。但是,現在,我們只需要一套圖,那麼到底採用哪一套切圖呢?目前,Android手機中主流的解析度是xxhdpi,所以我們可以採用drawable-xxhdpi這套UI切圖,在xxhdpi的手機上調好佈局後,當其他較低dpi手機載入該UI時再將其等比例縮小即可。

切圖是有了,但是該把它放到res下哪個資料夾中呢?
有人說:這不是明擺著的麼,當然放到drawable-xxhdpi中唄。
真的是這樣麼?
嗯哼,結合我們上一篇的講解可知:如果把切圖放到了drawable-xxhdpi中,那麼當這些切圖顯示在hdpi的手機上時圖片會被縮小並且可能導致失真。同理,如果這些切圖顯示在xxxhpi的手機上時圖片會被放大並且可能導致失真。圖片的放大和縮小,這個好理解,但是為什麼會導致圖片失真呢?或者說為什麼不是等比例的放大或者縮小呢?現在通過一個例項來分析原因。
在此,準備兩部測試手機:
華為P7,解析度為1920*1080,dpi為480
HTC T392,解析度為800*480,dpi為240
現在有一套專門為dpi等於480的手機準備的切圖放在drawable-xxhdi中。當P7載入該套切圖時圖片顯示正常,當T392載入該套圖片時圖片被縮小,且縮小比為240/480=0.5。但是請注意兩部手機解析度的比值。其中,寬的比:480/1080=0.44;高的比800/1920=0.42;也就是說兩個螢幕的解析度的比大概是0.4,但是圖片的縮放比是0.5,所以這兩者的不一致導致了圖片縮放後的失真。如果要使圖片在HTC T392上不失真,那麼需要按照0.4縮放圖片,而非0.5.

為了擺脫這個桎梏,避免圖片的縮放導致圖片失真:

  1. 將切圖放入drawable-nodpi中。
    該資料夾中的圖片不會被縮放,在不同解析度的手機上都只顯示原圖的大小。如此以來,摒棄了系統對於圖片的縮放,為我們以後自己處理圖片的縮放做好了鋪墊。
  2. 計算出縮放比。
    依據不同的解析度計算出縮放比。

總之,我們不再採用系統提供的對於圖片的適配和縮放,而是自己確定一個準確的縮放比例將高解析度的UI按照該比例縮放從而實現多解析度的適配


佈局的細節

嗯哼,在知道了怎麼做之後,我們就首先要在一個高解析度(如:1920*1080)手機上完成佈局。
在佈局的過程中,請注意一個問題:不再使用dp、sp作為大小單位,而是統一使用px。

為什麼要這麼做呢?

  1. 縮放比例的確定是基於螢幕的解析度而確定的。
    螢幕的解析度均是採用px作為單位的,所以在佈局時亦採用px從而保證縮放比例的一致和準確
  2. dp和sp均與裝置的dpi有關。
    不同裝置的dpi值不一樣,所以在不同的裝置上同一個dp和sp所對應的px值是不盡相同的。如果採用dp和sp作為尺寸的單位,那麼在縮放時會產生較大的偏差

程式碼實現

好了,現在圖片有了,佈局也寫好了,現在就要開始對UI縮放了。

第一步:

計算縮放比

int widthPixels = displayMetrics.widthPixels;
scale = (float)widthPixels / BASE_SCREEN_WIDTH_FLOAT;

通過裝置的寬與BASE_SCREEN_WIDTH_FLOAT的比值計算出縮放比。
此處的BASE_SCREEN_WIDTH_FLOAT就是一個縮放的基準,比如高解析度手機(如:1920*1080)中的1080。

第二步:

等比例縮放UI

 /**
* 原創作者:
* 谷哥的小弟
*
* 部落格地址:
* http://blog.csdn.net/lfdfhl
*/
public static void scaleViewSize(View view) {
if (null != view) {
int paddingLeft = getScaleValue(view.getPaddingLeft());
int paddingTop = getScaleValue(view.getPaddingTop());
int paddingRight = getScaleValue(view.getPaddingRight());
int paddingBottom = getScaleValue(view.getPaddingBottom());
view.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
LayoutParams layoutParams = view.getLayoutParams();
if (null != layoutParams) {
if (layoutParams.width > 0) {
layoutParams.width = getScaleValue(layoutParams.width);
}
if (layoutParams.height > 0) {
layoutParams.height = getScaleValue(layoutParams.height);
}
if (layoutParams instanceof MarginLayoutParams) {
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) layoutParams;
int topMargin = getScaleValue(marginLayoutParams.topMargin);
int leftMargin = getScaleValue(marginLayoutParams.leftMargin);
int bottomMargin = getScaleValue(marginLayoutParams.bottomMargin);
int rightMargin = getScaleValue(marginLayoutParams.rightMargin);
marginLayoutParams.topMargin = topMargin;
marginLayoutParams.leftMargin = leftMargin;
marginLayoutParams.bottomMargin = bottomMargin;
marginLayoutParams.rightMargin = rightMargin;
}
}
view.setLayoutParams(layoutParams);
}
}

利用該方法對佈局中的每個View進行縮放操作。
在該方法中對每個View的寬高,padding,margin值都按比例縮放,並且在縮放後重新設定其佈局引數。

第三步:

關於TextView的特殊處理。

對於TextView,不但要縮放其尺寸,還需要對其字型進行縮放:

 /**
* 原創作者:
* 谷哥的小弟
*
* 部落格地址:
* http://blog.csdn.net/lfdfhl
*/
Object isScale = textView.getTag(R.id.is_scale_font_tag);
if (!(isScale instanceof Boolean) || !((Boolean) isScale).booleanValue()) {
float size = textView.getTextSize();
size *= scale;
textView.setTextSize(0, size);
}

除此以外,還要考慮到對TextView的CompoundDrawable進行縮放

 /**
* 原創作者:
* 谷哥的小弟
*
* 部落格地址:
* http://blog.csdn.net/lfdfhl
*/
public static Drawable scaleDrawableBounds(Drawable drawable) {
int right=getScaleValue(drawable.getIntrinsicWidth());
int bottom=getScaleValue(drawable.getIntrinsicHeight());
drawable.setBounds(0, 0, right, bottom);
return drawable;
}

總結

嗯哼,關於Android多解析度適配框架的原理就分析完了。再回過頭看,可以發現:其實它沒有我們想象的那麼難。
對於該框架的理解和使用,有以下幾個要點:

  • 切圖存放於drawable-nodpi
  • 拋開系統的dpi並且摒棄dp和sp,統一使用px作為尺寸單位
  • 按照高解析度(如1920*1080)切圖和佈局
  • 在程式碼中等比例縮放每個View

目前,xxhdpi解析度的手機佔了主流,所以在該框架中採用了drawable-xxhdpi的切圖。倘若以後xxxhdpi解析度的手機佔了主導地位,那麼就請UI設計師按照該解析度切圖,我們將其放在drawable-nohdpi中,再修改BASE_SCREEN_WIDTH_FLOAT即可。

who is the next one? ——> Demo



PS:Android多解析度適配框架視訊教程同步更新啦