螢屏自適應
- 螢屏適配相關知識點
- 1. 適配原理
- 原理核心
- 優缺點
- 2. 框配置
- 3. 自定義初始化
- 4. 常用方法決議
- 5. 常見介面及類的使用
- CustomAdapt
- CancelAdapt
- 6.框架核心
- 1.螢屏適配自定義
- 2.適配策略的實作
- 7. 其實事項
- 總結
首先感謝大神JessYan的創神之作《AndroidAutoSize》,大神以今日頭條螢屏適配的核心代碼為基礎進行了擴展封裝,產生了《AndroidAutoSize》這個能快速接入使用的螢屏適配方案,這個螢屏適配方案是我遇到的截止2020.9.15為止最強大、簡單有效的螢屏適配方案,我已使用該方案有一年,在使用程序未發現有何問題,強烈推薦各位極客們使用學習,
以下是大神JessYan的相關地址:
郵箱:jess.yan.effort@gmail.com
github:https://github.com/JessYanCoding/AndroidAutoSize
簡書:https://www.jianshu.com/p/4aa23d69d481
原始核心代碼:https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA
大神的原始碼都在github中各位可以自行下載,我寫這篇博客的目的就是記錄使用心得,并將該框架的重要類和方法使用進行詳細說明,大神的文章中對細節問題寫的比較少,我對其進行了細微的修改和詳細的注解解釋,
螢屏適配相關知識點
像素
通常所說的像素,就是CCD/CMOS上光電感應元件的數量,一個感光元件經過感光,光電信號轉換,A/D轉換等步驟以后,在輸出的照片上就形成一個點,我們如果把影像放大數倍,會發現這些連續色調其實是由許多色彩相近的小方點所組成,這些小方點就是構成影像的最小單位“像素”(Pixel),簡而言之,像素就是手機螢屏的最小構成單元,
螢屏尺寸
螢屏尺寸指螢屏的對角線的長度,單位是英寸,1英寸=2.54厘米,比如常見的螢屏尺寸有2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0等
螢屏解析度
螢屏解析度是指在橫縱向上的像素點數,單位是px,1px=1個像素點,一般以縱向像素橫向像素,如19201080
螢屏像素密度(dpi)
螢屏像素密度是指每英寸上的像素點數,單位是dpi,即“dot per inch”的縮寫,螢屏像素密度與螢屏尺寸和螢屏解析度有關,在單一變化條件下,螢屏尺寸越小、解析度越高,像素密度越大,反之越小,

計算公式: 像素密度 = 像素 / 尺寸 (dpi = px / in)
標準螢屏像素密度(mdpi): 每英寸長度上還有160個像素點(160dpi),即稱為標準螢屏像素密度(mdpi),
密度無關像素(dp)
含義:density-independent pixel,叫dp或dip,與終端上的實際物理像素點無關
單位:dp,可以保證在不同螢屏像素密度的設備上顯示相同的效果,是安卓特有的長度單位,
場景例子:假如同樣都是畫一條長度是螢屏一半的線,如果使用px作為計量單位,那么在480x800解析度手機上設定應為240px;在320x480的手機上應設定為160px,二者設定就不同了;如果使用dp為單位,在這兩種解析度下,160dp都顯示為螢屏一半的長度,
dp與px的轉換:1dp = (dpi / 160 ) * 1px;
| 密度型別 | 代表的解析度(px) | 螢屏像素密度(dpi) | 換算 |
|---|---|---|---|
| 低密度(ldpi) | 240 x 320 | 120 | 1dp = 0.75px |
| 中密度(mdpi) | 320 x 480 | 160 | 1dp = 1px |
| 高密度(hdpi) | 480 x 800 | 240 | 1dp = 1.5px |
| 超高密度(xhdpi) | 720 x 1280 | 320 | 1dp = 2px |
| 超超高密度(xxhdpi) | 1080 x 1920 | 480 | 1dp = 3px |
獨立比例像素(sp)
scale-independent pixel,叫sp或sip,字體大小專用單位 ,Android開發時用此單位設定文字大小,可根據字體大小首選項進行縮放,
推薦使用12sp、14sp、18sp、22sp作為字體大小,不推薦使用奇數和小數,容易造成精度丟失,12sp以下字體太小,
sp與dp的區別
dp只跟螢屏的像素密度有關, sp和dp很類似但唯一的區別是,Android系統允許用戶自定義文字尺寸大小(小、正常、大、超大等等),當文字尺寸是“正常”時1sp=1dp=0.00625英寸,而當文字尺寸是“大”""或“超大”時,1sp>1dp=0.00625英寸,類似我們在windows里調整字體尺寸以后的效果——視窗大小不變,只有文字大小改變,
1. 適配原理
Android AutoSize的核心代碼來源于位元組跳動的微信文章https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA,網上也有多各個大神進行了代碼的封裝設計,都是萬變不離其中,
原理核心
DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
if (sNoncompatDensity == 0) {
sNoncompatDensity = appDisplayMetrics.density;
sNoncompatDensity = appDisplayMetrics.scaledDensity;
application.registerComponentCallbacks(new ComponentCallbacks() {
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig != null && newConfig.fontScale > 0) {
sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
public void onLowMemory() {
}
});
}
float targetDensity = appDisplayMetrics.widthPixels / 360;
float targetScaleDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
int targetDensityDpi = (int) (160 * targetDensity);
appDisplayMetrics.density = targetDensity;
appDisplayMetrics.scaledDensity = targetScaleDensity;
appDisplayMetrics.densityDpi = targetDensityDpi;
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaleDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
原理很簡單,例如一個4.59的10801920的手機它的dpi=480,它的density=480/160,則說明1dp=3px,當我們在布局中給如TextView設定
layout_width=30dp時,在程式運行時會自動計算其對應px單位長度90px,px=dpdensity,
今日頭條適配方案的核心就是動態計算程式中的density=appDisplayMetrics.widthPixels / 360,360是原始設計圖紙的dp,假設原先的設計圖紙10801920,現在適配5.99寸560dpi的14402880手機,則30dp=30560/160=105px,實際上螢屏適配要求的30dp=1440/36030=120px才可以達到適配效果,因為120/1440=90/1080,控制元件在布局中的占寬比是一樣的才能達到寬度適配效果,這就是為什么要動態修改全域或activity的DisplayMetrics#density的目的了,
優缺點
優點:
- 侵入性非常低,該方案和專案完全解耦,使用的還是Android官方單位
- 接入無性能損耗,使用的全是Android官方的API,
缺點:
- 專案中的系統控制元件、三方庫控制元件、等非我們專案自身設計的控制元件,它們的設計圖尺寸并不會和我們專案自身的設計圖尺寸一樣,此時會產生適配誤差,解決方案就是取消當前 Activity 的適配效果,改用其他的適配方案
- 系統修改字體大小后,回傳應用系統字體大小還是未改變,需要設定registerComponentCallbacks監聽, Android AutoSize框架已經解決了該問題,
- 在使用程序中需要進行registerComponentCallbacks監聽內容文字的大小改變情況,解決退出應用修改文字大小后,文字大小不改變的情況,
2. 框配置
依賴配置
- 遠程依賴,截止到2020.9.15,版本為1.2.1
implementation 'me.jessyan:autosize:1.2.1'
- 從github上下載原始碼進行library庫依賴
引數配置
在AndroidManifest.xml中配置引數
<manifest>
<application>
...
<meta-data
android:name="design_width_in_dp"
android:value="360"/>
<meta-data
android:name="design_height_in_dp"
android:value="640"/>
...
</application>
</manifest>
3. 自定義初始化
本文中使用的框架是經過大神JessYan的封裝后成為你所看到的框架,它能根據一套給定的設計圖尺寸進行布局展示,當安裝當不同解析度尺寸的設備上時,它能自動適配螢屏,
框架的初始化時機是配置在ContentProvider中,在Application#onCreate()方法之前啟動,框架一旦初始化完成,其適配效果會在Activity和Fragment、各種View中自動全域適配,程式將默認是以螢屏寬度為基準進行適配的,并且使用的是在AndroidManifest中填寫的全域設計圖尺寸進行全域適配,
框架支持dp、sp兩個主單位,pt、in、mm三個冷門副單位,如果使用副單位,可以規避系統控制元件或三方庫控制元件使用的不良影響,
ContentProvider初始化第三方庫
ContentProvider是一種共享型組件,它通過Binder向其他組件或者其他應用程式提供資料,當ContentProvider所在行程啟動時候,ContentProvider會被同時啟動并被發布到AMS中,
ContentProvider的onCreate要優先于Application的onCreate,但在attachBaseContext()之后而執行,它的具體詳細啟動原始碼在ActivityThread中,很多人會在ContentProvider#onCreate()初始化第三方庫,
一般進行了依賴配置和引數配置兩操作,Android AutoSize就配置完成可以直接使用了,它的框架原始碼初始化在InitProvider代碼中,
在InitProvider 中已進行了初始化設定
public class InitProvider extends ContentProvider {
@Override
public boolean onCreate() {
if (getContext() != null) {
Context application = getContext().getApplicationContext();
if (application == null) {
application = AutoSizeUtils.getApplicationByReflect();
}
AutoSizeConfig.getInstance()
.setLog(true)
.init((Application) application)
.setUseDeviceSize(false);
return true;
}
return false;
}
但是為了個性化的配置,我們可以在Application中進行一些自定義設定,設定的方法都應寫在Application#onCreate()方法中,
public class Application {
@Override
public void onCreate() {
super.onCreate();
...
AutoSize.initCompatMultiProcess(this);
AutoSize.checkAndInit(this);
AutoSizeConfig.getInstance()
.setCustomFragment(true)
.setExcludeFontScale(true)
.setPrivateFontScale(0.8f)
.setLog(false)
.setBaseOnWidth(true)
.setUseDeviceSize(true)
//螢屏適配監聽器
.setOnAdaptListener(new OnAdaptListener() {
@Override
public void onAdaptBefore(Object target, Activity activity) {
// AutoSizeConfig.getInstance().setScreenWidth(ScreenUtils.getScreenSize(activity)[0]);
// AutoSizeConfig.getInstance().setScreenHeight(ScreenUtils.getScreenSize(activity)[1]);
AutoSizeLog.d(String.format(Locale.ENGLISH, "%s onAdaptBefore!", target.getClass().getName()));
}
@Override
public void onAdaptAfter(Object target, Activity activity) {
AutoSizeLog.d(String.format(Locale.ENGLISH, "%s onAdaptAfter!", target.getClass().getName()));
}
});
configUnits();
}
private void configUnits() {
AutoSizeConfig.getInstance()
.getUnitsManager()
.setSupportDP(true)
.setDesignSize(2160, 3840)
.setSupportSP(true)
.setSupportSubunits(Subunits.MM);
}
}
4. 常用方法決議
對于初始化中方法,我們進行一一分析
1. AutoSize.initCompatMultiProcess(Context context)
當 App 中出現多行程,并且您需要適配所有的行程,就需要在 App 初始化時呼叫,一般的單行程App程式不用設定,
2. AutoSize.checkAndInit(Application application)
if (!checkInit()) {
AutoSizeConfig.getInstance()
.setLog(true)
.init(application)
.setUseDeviceSize(false);
}
一般來說Android AutoSize會通過InitProvider實體化自動完成初始化,是不需要呼叫checkAndInit()方法的,
但由于某些 issues 反應, 可能會在某些特殊情況下出現InitProvider未能正常實體化的情況, 導致 AndroidAutoSize 未能完成初始化,所以需要使用該方法確保Android AutoSize 初始化成功,
3. AutoSizeConfig.getInstance().setCustomFragment(boolean customFragment)
設定是否讓框架支持自定義Fragment 的適配引數,一般這個需求比較少,默認不支持的
4. AutoSizeConfig.getInstance().setExcludeFontScale(true)
是否屏蔽系統字體大小對AndroidAutoSize 的影響, 如果為 true, App 內的字體的大小將不會跟隨系統設定中字體大小的改變, 如果為 false, 則會跟隨系統設定中字體大小的改變, 默認為 false
5. AutoSizeConfig.getInstance().setPrivateFontScale(float fontScale)
區別于系統字體大小的放大比例, AndroidAutoSize 允許 APP 內部可以獨立于系統字體大小之外,獨自擁有全域調節 APP 字體大小的能力, fontScale取值0~1,設為 0 則取消此功能,同時字體的單位必須是sp做單位,
6. AutoSizeConfig.getInstance().setLog(boolean log)
設定是否列印AutoSize的日志,true為列印
7. AutoSizeConfig.getInstance().setBaseOnWidth(true)
是否全域按照寬度進行等比例適配,true以寬來適配,false以高來適配
8. AutoSizeConfig.getInstance().stop(this)
自動適配方案可以手動呼叫方法停止,需要注意的是Android AutoSize暫停只是停止了對后續還沒有啟動的{@link Activity}進行適配的作業,但對已經啟動且已經適配的{@link Activity}不會有任何影響
9. AutoSizeConfig.getInstance().restart()
AutoSize可以暫停適配也可以重啟適配,但是重啟適配只能對后續還沒有啟動的 {@link Activity} 進行適配的作業,但對已經啟動且在stop期間未適配的{@link Activity}不會有任何影響
10. AutoSizeConfig.getInstance().setUseDeviceSize(true)
是否以螢屏的實際尺寸為高度,默認為false,螢屏的適配高度是螢屏總高度減去狀態欄高度,
11. UnitsManager.setSupportSP(boolean supportSP)
是否讓框架支持sp單位,默認是為true支持,如果為false,則字體大小最好設定為其他單位才能自動適配
12. UnitsManager.setSupportSubunits(Subunits supportSubunits)
自主設定心儀的副單位,可以從pt、in、mm中進行選擇,如果使用了Subunits#NONE即代表不支持副單位
13. UnitsManager.setSupportDP(boolean supportDP)
是否支持dp單位,默認是true支持,如果關閉將不對dp單位進行支持
14. UnitsManager.setDesignSize(float designWidth, float designHeight)
設定設計圖尺寸,一般專為副單位尺寸設計,它與AndroidManifest.xml中配置的引數不一樣,不會被覆寫,
5. 常見介面及類的使用
CustomAdapt
實作CustomAdapt介面即可對activity和fragment進行新的自定義尺寸適配,適配方向可以自主選擇是寬度還是高度,實作該介面會取消默認的適配方案和效果
對于fragment的自定義尺寸需要進行AutoSizeConfig.getInstance().setCustomFragment(true)設定,默認是不支持對fragment的自定義尺寸適配的,
在CustomAdapt介面中需要實作者重寫兩個方法boolean isBaseOnWidth()和float getSizeInDp(),根據使用者需求自定義,
1. boolean isBaseOnWidth()
為了保證在高寬比不同的螢屏上也能正常適配,所以只能在寬度和高度之中選一個作為基準進行適配, true為按照寬度適配, false 為按照高度適配
2. float getSizeInDp()
getSizeInDp 須配合isBaseOnWidth()使用, 有如下使用規則:
如果 {@link #isBaseOnWidth()} 回傳 {@code true}, {@link CustomAdapt #getSizeInDp} 則應該回傳設計圖的總寬度,
如果 {@link #isBaseOnWidth()} 回傳 {@code false}, {@link CustomAdapt #getSizeInDp} 則應該回傳設計圖的總高度,
如果您不需要自定義設計圖上的設計尺寸, 想繼續使用在 AndroidManifest 中填寫的設計圖尺寸,getSizeInDp 則回傳 0即可,
CancelAdapt
介面CancelAdapt沒有任何成員變數,支持AndroidAutoSize的專案所有模塊默認使用適配功能,第三方庫的也不例外,
如果某個頁面不想使用適配功能, 請讓該頁面實作CancelAdapt介面放棄適配,所有的適配效果都將失效,
6.框架核心
1.螢屏適配自定義
通過位元組跳動的核心原始碼,只能進行全域適配,但是該框架中進行了Activity和Fragmen的自定義適配和隨時取消恢復適配功能,它的原理是注冊了ActivityLifecycleCallbacks,進行了Activity的適配時間精準化自我掌控,
通過注冊ActivityLifecycleCallbacks,進行Activity的生命周期進行管理, 當onActivityCreated時,也就是OnCreate()的setContentView之前進行了AutoAdaptStrategy#applyAdapt的呼叫,這種方案類似于 AOP, 面向介面, 侵入性低, 方便統一管理, 擴展性強,
@Override
public void onActivityCreated(@androidx.annotation.NonNull Activity activity, Bundle savedInstanceState) {
if (AutoSizeConfig.getInstance().isCustomFragment()) {
if (mFragmentLifecycleCallbacksToAndroidx != null && activity instanceof androidx.fragment.app.FragmentActivity) {
((androidx.fragment.app.FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(mFragmentLifecycleCallbacksToAndroidx, true);
}
}
//Activity 中的 setContentView(View) 一定要在 super.onCreate(Bundle); 之后執行
if (mAutoAdaptStrategy != null) {
mAutoAdaptStrategy.applyAdapt(activity, activity);
}
}
通過注冊registerFragmentLifecycleCallbacks,進行Fragment的生命周期管理,當onFragmentCreated時,也就是OnCreate()中進行了AutoAdaptStrategy#applyAdapt的呼叫
@Override
public void onFragmentCreated(@androidx.annotation.NonNull FragmentManager fm, @androidx.annotation.NonNull Fragment f, Bundle savedInstanceState) {
if (mAutoAdaptStrategy != null) {
mAutoAdaptStrategy.applyAdapt(f, f.getActivity());
}
}
通過全域的進行Activity和Fragment的生命周期監控,在其布局創建之前呼叫 AutoAdaptStrategy#applyAdapt進行具體的適配操作,它的關鍵點是動態修改density、scaledDensity、densityDpi三個引數,造成每個Activity或Fragment加載布局時的density、scaledDensity、densityDpi等引數不一樣,達到的適配效果則不一樣,
2.適配策略的實作
ActivityLifecycleCallbacks的使用能實時監測Activity和Fragment進行適配呼叫,但是實際操作的代碼在策略方案AutoAdaptStrategy的實作子類中,框架中已有默認策略方案,當然自己也可以自定義修改創建,
- 當target實作CancelAdapt后,將density、scaledDensity、densityDpi恢復到原始狀態,不進行匹配
- 當target實作CustomAdapt后,將density、scaledDensity、densityDpi根據target的配置進行計算后設定
- 當target未進行任何處理時,將density、scaledDensity、densityDpi根據AndroidManifest.xml中的配置進行計算設定
@Override
public void applyAdapt(Object target, Activity activity) {
....
//如果 target 實作 CancelAdapt 介面表示放棄適配, 所有的適配效果都將失效
if (target instanceof CancelAdapt) {
AutoSizeLog.w(String.format(Locale.ENGLISH, "%s canceled the adaptation!", target.getClass().getName()));
AutoSize.cancelAdapt(activity);
return;
}
//如果 target 實作 CustomAdapt 介面表示該 target 想自定義一些用于適配的引數, 從而改變最終的適配效果
if (target instanceof CustomAdapt) {
AutoSizeLog.d(String.format(Locale.ENGLISH, "%s implemented by %s!", target.getClass().getName(), CustomAdapt.class.getName()));
AutoSize.autoConvertDensityOfCustomAdapt(activity, (CustomAdapt) target);
} else {
AutoSizeLog.d(String.format(Locale.ENGLISH, "%s used the global configuration.", target.getClass().getName()));
AutoSize.autoConvertDensityOfGlobal(activity);
}
...
}
7. 其實事項
1. Fragment橫豎屏切換布局問題
由于某些原因, 螢屏旋轉后 Fragment 的重建, 會導致框架對 Fragment 的自定義適配引數失去效果,所以如果您的 Fragment 允許螢屏旋轉, 則請在 onCreateView 手動呼叫一次 AutoSize.autoConvertDensity(),如AutoSize.autoConvertDensity(getActivity(), 1080, true),
如果您的 Fragment 不允許螢屏旋轉, 則可以將下面呼叫 AutoSize.autoConvertDensity() 的代碼洗掉掉
public class CustomFragment1 extends Fragment implements CustomAdapt {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
//由于某些原因, 螢屏旋轉后 Fragment 的重建, 會導致框架對 Fragment 的自定義適配引數失去效果
//所以如果您的 Fragment 允許螢屏旋轉, 則請在 onCreateView 手動呼叫一次 AutoSize.autoConvertDensity()
//如果您的 Fragment 不允許螢屏旋轉, 則可以將下面呼叫 AutoSize.autoConvertDensity() 的代碼洗掉掉
AutoSize.autoConvertDensity(getActivity(), 1080, true);
return createTextView(inflater, "Fragment-1\nView width = 360dp\nTotal width = 1080dp", 0xffff0000);
}
2. 主副單位的逐步替換
框架中同時支持主單位和副單位,對于對于舊專案中已使用dp或px的專案,可以通過逐步在新頁面中使用主單位副單位,通過不斷的迭代替換,最終將專案中的主單位如dp全替換為副單位px,或者將副單位px全替換為主單位dp,
當單位都替換完成后,設定UnitsManager.setSupportDP(false)關閉對dp的支持,徹底隔離修改 density 所造成的不良影響,
或者都使用dp,不在支持副單位時設定UnitsManager.setSupportSubunits(Subunits.NONE)關閉對副單位的支持,
3. 主副單位的同時支持
當使用者想將舊專案從主單位過渡到副單位, 或從副單位過渡到主單位時,因為在使用主單位時, 建議在 AndroidManifest 中填寫設計圖的 dp 尺寸, 比如 360 * 640,
但在 AndroidManifest 中卻只能填寫一套設計圖尺寸, 并且已經填寫了主單位的設計圖尺寸,所以當專案中同時存在副單位和主單位, 并且副單位的設計圖尺寸與主單位的設計圖尺寸不同時, 可以通過UnitsManager#setDesignSize() 方法配置,
如果副單位的設計圖尺寸與主單位的設計圖尺寸相同, 則不需要呼叫 UnitsManager#setDesignSize(), 框架會自動使用 AndroidManifest 中填寫的設計圖尺寸,
4. 自定義單位模擬器創建
布局時的實時預覽在開發階段是一個很重要的環節, 很多情況下 Android Studio 提供的默認預覽設備并不能完全展示我們的設計圖,所以我們就需要自己創建模擬設備, 大神@JessYan已經為我們準備好了dp、pt、in、mm 這四種單位的模擬設備創建方法,請點擊查看鏈接https://github.com/JessYanCoding/AndroidAutoSize/blob/master/README-zh.md#preview
總結
經過我自己修改注釋的原始碼在https://github.com/l424533553/MyAutoSize.git中,大家也可以自行封裝框架,適合自己的才是最好的,
自適應的核心就是根據需要在使用之前不斷修改density、scaledDensity、densityDpi達到適配效果,
博客書寫不易,您的點贊收藏是我前進的動力,覺得不錯請點贊、 收藏 ^ _ ^ !
相關鏈接
- Https通信之數字證書 https://blog.csdn.net/luo_boke/article/details/106018128
- Kotlin到底比Java好在哪,我們不得不說的事 https://blog.csdn.net/luo_boke/article/details/107172965
- Android 史上最新最全的ADB及命令百科,沒有之一https://blog.csdn.net/luo_boke/article/details/106303208
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/124653.html
標籤:其他
上一篇:flutter使用local_auth插件出現local_auth plugin requires activity to be a FragmentActivity.
