目錄
前言
智能手機的用戶分佈在不同國家,且偏好各異,這就要求開發階段兼容適配;由於各廠家生產出的安卓設備分別率不同、屏幕大小和風格也存在各異,如果手機的用戶設備各異,僅用一張圖片可能會出現拉伸變形模糊,影響用戶體驗,因此對應屏幕的兼容適配是重中之重;隨着Google不斷更新Android版本,每個版本的代碼也有區別。Android適配技能日益成爲開發者必不可少的一項專業技能。
本篇文章分別講解了語言適配、屏幕適配、版本適配三個內容。
一、適配國家語言
語言適配有兩種場景:一種是在手機系統裏的“設置”選項中更改了系統的語言,影響的是整個手機內應用,包括系統應用和非系統應用。另一種,是用戶可以手動切換某一款應用的內部語言風格,影響的範圍僅僅是自身應用。
(1)手機系統語言適配
工程的根目錄有個res/的目錄,該目錄下存放的是資源文件:如drawable、anim、layout、values。其中,value目錄下存放/strings.xml,它用來設置項目中需要的字符串對象,可以理解爲應用文本顯示的內容。如下,默認的應用名稱。
<resources>
<string name="app_name"> APP_NAME </string>
</resources>
value目錄會根據手機語言的改變而加載不同String.xml。因此,在res/目錄下創建多個values/strings.xml文件,且values目錄需要改名,例如:
res/
values/ 默認
strings.xml
values-en/ 英文
strings.xml
values-es/ 西班牙
strings.xml
values-fr/ 法語
strings.xml
英語:/values-en/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title">My Application</string>
<string name="hello_world">Hello World!</string>
</resources>
西班牙語:/values-es/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title">Mi Aplicación</string>
<string name="hello_world">Hola Mundo!</string>
</resources>
法語:/values-fr/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title">Mon Application</string>
<string name="hello_world">Bonjour le monde !</string>
</resources>
最後,我們就可以在代碼中使用R.string.<string_name>語法來引用字符串資源就可以了。配置好了就不怕手機系統語言切換了,但是APP內部語言切換就得換一種實現方式了。
(2)應用切換語言
首先,通過Configuration(配置信息)這個類,獲取應用當前的語言,然後修改該配置中的語言,最後更新配置,重啓一下Activity才能生效。其次,還有一個比較重要的類Locale(地點),如下。
public final class Locale implements Cloneable, Serializable {
static private final Cache LOCALECACHE = new Cache();
/** 中文
*/
static public final Locale CHINESE = createConstant("zh", "");
/**英文
*/
static public final Locale ENGLISH = createConstant("en", "");
/** 法文
*/
static public final Locale FRENCH = createConstant("fr", "");
/** 德文
*/
static public final Locale GERMAN = createConstant("de", "");
/**印度文
*/
static public final Locale ITALIAN = createConstant("it", "");
/** 日文
*/
static public final Locale JAPANESE = createConstant("ja", "");
/** 韓文
*/
static public final Locale KOREAN = createConstant("ko", "");
.....
}
點擊切換按鈕,實現中英文語言切換。
@Override
public void onClick(View v) {
//1、獲取當前語言
String current_language;
// 注:Locale.getDefault().getLanguage();該方法獲取系統語言,對於應用內切換不適用。
Configuration config = getResources().getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){//api24 ,android 7.0
current_language = config.getLocales().toLanguageTags();
}else{
current_language = config.locale.getLanguage();
}
//2、切換中英語言
if (current_language.equals(Locale.CHINESE.getLanguage()) || current_language.equals("zh-CN") ){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
config.getLocales();
config.setLocale(Locale.ENGLISH);
}else{
config.locale = Locale.ENGLISH;
}
}else if (current_language.equals(Locale.ENGLISH.getLanguage())){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
config.getLocales();
config.setLocale(Locale.CHINESE);
}else{
config.locale = Locale.CHINESE;
}
}
//3、更新應用配置
getBaseContext().getResources().updateConfiguration(config,getBaseContext().getResources().getDisplayMetrics());
//4、重啓Activity
recreate();
}
二、屏幕適配
爲什麼做屏幕適配?
目前市場上的安卓手機設備的分別率各不相同、屏幕大小和風格(9.0 劉海屏適配方案)也存在各異。例如,項目中僅用一張圖片,在多款不同大小的手機上表現可能會出現拉伸變形模糊,影響用戶體驗。其次,Android設備是支持屏幕旋轉功能的,如果不做適配那問題就大了,輕者佈局錯位重則生命週期重刷發生異常崩潰。這裏歸納總結的屏幕適配有三個方向:圖片適配、佈局適配、旋轉適配。
(1)圖片適配
dp是一種基於屏幕密度的抽象單位,也叫虛擬像素,在不同的像素密度的設備上會自動適配。原理:我們知道px是像素單位,一款設備的屏幕上會散列着物理像素點,叫分別率。密度dpi表示在屏幕上每英寸的所佔的像素點。規定當dpi爲160時,1dp是等於1px的,從而實現dp可以自動適配的功能。
計算公式:px = dp * (dpi/160) ; dp = px/dpi/160;
手機設備的尺寸是☞屏幕對角線的長度,因此像素密度DPI的計算方式比較有趣:例如有個5寸且長寬比爲4:3的手機設備。1寸 = 2.54釐米。知道了對角線長度和長寬比,就可以根據勾股定理很容易的計算出手機屏幕的長和寬了。
分辨率級別 | DPI | 比率 | 分別率(px) |
dp 換算 px關係 |
ldpi(Low:低) | 120 | 0.75 | 240*320 | 1dp = 0.75px |
mdpi(Middle:中) | 160 | 1 | 320*480 | 1dp = 1px |
hdpi(High:高) | 240 | 1.5 | 480*800 | 1dp =1.5px |
xhdpi(超高,2倍圖) | 320 | 2 | 1280*720 | 1dp = 2px |
xxhdpi(3倍圖) | 480 | 3 | 1920*1080 | 1dp =3px |
xxxhdpi(4倍圖) | 640 | 4 | 3840*2160 | 1dp =4px |
因此,我們可以根據dp與px的換算關係,讓UI設計師給出計算後的多套圖。例如,xhdpi 級別的設備畫了一張200*200px的圖,那麼hdpi的150*150px、mdpi的100*100px、ldpi的75*75px....然後,將這些文件放入相應的drawable資源目錄中,當引用
@drawable/image
時系統會根據屏幕的分辨率選擇恰當的bitmap。
res/
drawable-xhdpi/
image.png
drawable-hdpi/
image.png
drawable-mdpi/
image.png
drawable-ldpi/
image.png
一般通過dp適配時,是在如上表中比較標準的dip和分別率的情況下,可以完美適配,但出現特殊屏幕時就會出現問題。舉個例子,已知MIX2的屏幕尺寸是
5.5英寸
,分辨率是2160*1080象素,DPI
= resources.displayMetrics.densityDpi= 440。此時,設計人員給的UI設計圖是按照分別率1280*720也就是2倍圖,換算dp後寬高是640dp*375dp。而在MIX2設備上,屏幕的寬度是1080/(440/160)=392.7dp,也就是說手機設備屏幕比設計圖要寬,這種情況下要顯示滿屏的歡迎頁圖片,就無法達到和設計圖相同的效果。
當然,還會存在手機設備寬度不足375dp,那麼就會出現圖片超出屏幕的情況。因此用dp進行適配也是差強人意,下面是字節跳動的屏幕適配方案:獲取屏幕參數信息,進行動態適配
object ScreenUtil {
fun adapterScreen(activity: Activity, targetDP: Int, isVertical: Boolean) {
//系統的屏幕尺寸
val systemDM = Resources.getSystem().displayMetrics
//app整體的屏幕尺寸
val appDM = activity.application.resources.displayMetrics
//activity的屏幕尺寸
val activityDM = activity.resources.displayMetrics
if (isVertical) {
// 適配屏幕的高度
activityDM.density = activityDM.heightPixels / targetDP.toFloat()
} else {
// 適配屏幕的寬度
activityDM.density = activityDM.widthPixels / targetDP.toFloat()
}
// 適配相應比例的字體大小
activityDM.scaledDensity = activityDM.density * (systemDM.scaledDensity / systemDM.density)
// 適配dpi
activityDM.densityDpi = (160 * activityDM.density).toInt()
}
fun resetScreen(activity: Activity) {
//系統的屏幕尺寸
val systemDM = Resources.getSystem().displayMetrics
//app整體的屏幕尺寸
val appDM = activity.application.resources.displayMetrics
//activity的屏幕尺寸
val activityDM = activity.resources.displayMetrics
activityDM.density = systemDM.density
activityDM.scaledDensity = systemDM.scaledDensity
activityDM.densityDpi = systemDM.densityDpi
appDM.density = systemDM.density
appDM.scaledDensity = systemDM.scaledDensity
appDM.densityDpi = systemDM.densityDpi
}
}
使用時需要注意以下幾點:
- 儘量只在當前頁面生效,包括activity,fragment,dialog,view,需要在setCOntentView()或者inflate之前調用這個方法,在結束onDestroy,onDismiss,onDetachWindow()的時候調用resetScreen()方法。
- 在頁面中需要彈出toast和dialog的時候需要調用resetScreen,不然toast和dialog的頁面大小和字體大小會被影響。
- 記住一點使用前調用adapterScreen,時候後調用resetScreen。
(2)、XML佈局適配
Layout適配尺寸有4種:小(small),普通(normal),大(large),超大(xLarge)
我們在資源文件layout目錄創建不同尺寸佈局文件,系統會根據運行的設備屏幕尺寸,選擇在與之對應的layout目錄中加載layout。如下:
res/
layout(-normal)/默認
main.xml
layout-large/大
main.xml
layout-xlarge/超大
main.xml
layout-small/小
main.xml
注意:記得在AndroidManifest.xml文件中設置多分辨率支持!!!
<Supports-screens
android:largeScreens="true"
android:normalScreen="true"
android:anyDensity="true"
android:smalleScreen="true"/>
佈局控件常用適配方法
- 儘量使用線性佈局(LinearLayout)和相對佈局(RelateLayout),儘量不使用絕對佈局(AbsoluteLayout)和幀佈局(FrameLayout)。
- 儘量使用wrap_content、mach_parent讓view自適應或最大化,儘量不要寫寬高的值。
- 使用線下佈局的百分比weight權重時,要把寬度寫成“0dp“,如果寫成wrap_coent會使佈局效果不佳等問題。
- 儘量使用android的Shape自定義view背景,這樣會隨之自適應。
- ImageView的ScaleType有五種方式(center,centerCrop,centerInside,fieCenter,fieXY),儘量使用fieCenter按比例擴大至view寬度,能取得較好適配和顯示效果。
(3)、橫豎屏適配
在AndroidMaifest.xml中activity中的屬性 android:screenOrientation="",可以設置屏幕爲固定橫屏或豎屏。值有三種類型:屬性landscape是橫向,portrait是縱向,"sensor"是由物理的感應器來決定。
如果用戶手動旋轉設備,此時不僅要注意Activity會經過銷燬到重建的問題,還要注意佈局兼容問題。 適配橫向屏幕,首先再創建一份layout-land佈局文件,系統會根據運行的設備屏幕方向情況自動加載對應的layout。如下:
res/
layout-port/ 豎屏
main.xml
layout-land/ 橫屏
main.xml
layout-large-land/ 也是可以與不同屏幕大小一起使用
main.xml
也可以只在layout文件夾下創建不同xml佈局,通過Configuration().orientation來判斷當前是橫屏landscape還是豎屏portrait,然後加載相對應的佈局文件即可。
重建問題:
如果不需要重新走一遍Activity的生命週期,則在AndroidManifest.xml中activity標籤下設置android: configChanges="orientation|keybordHidden|screenSize",這樣的話就不會重複調用activity的生命週期方法,切換時只會調用 onConfigurationChanged(Configuration newconfig)方法。
如果一切讓它銷燬在重建,只不過這過程中把需重要的值保存起來。重建後在取出來就行了。
//onResume之後調用,onPause()之前執行
@Override
protected void onSaveInstanceState(Bundle outBundle) {
super.onSaveInstanceState(outBundle);
outBundle.putBoolean("RoadChange", mChange);
}
//onResume之前調用 ,onStart之後執行
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mChange = savedInstanceState.getBoolean("RoadChange");
}
三、適配不同系統版本
新的Android版本會爲我們的app提供更棒的API,但我們的app仍應支持舊版本的Android,直到更多的設備升級到新版本爲止。Android 5.0、6.0、7.0、8.0、9.0 、10.0新特性,DownloadManager踩坑記
首先,在項目清單文件中指定最小和目標API級別。具體來說,<uses-sdk>元素中的minSdkVersion和targetSdkVersion 屬性,標明在設計和測試app時,最低兼容API的級別和最高適用的API級別(這個最高的級別是需要通過我們的測試的)。例如:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ... >
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="15" />
...
</manifest>
其次,是在代碼中判斷檢查版本信息。Android在Build常量類中提供了對每一個版本的唯一代號,在我們的app中使用這些代號可以建立條件,保證依賴於高級別的API的代碼,只會在這些API在當前系統中可用時,纔會執行。
private void setUpActionBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
//
}
}