因爲以前發的適配文章關注的人比較多,而以前的方案存在較多坑,但這已經被我這一週來仔細琢磨找到了最優的解決方案,擔憂你們還停留在以前的適配方式,因此在掘金只能靠分享連接來提醒大家查看最新更新版本,爲以前還未成熟的適配深表歉意,煩請你們一塊兒再次閱覽一遍下文吧。android
一個月前看了今日頭條新的屏幕適配方案,這是傳送門,對此不由拍案叫絕,爲此我想把這種方案融入到我工具類中直接一行代碼便可適配,現在最新 1.19.0 版 AndroidUtilCode 已有其最新的適配方案,其相關函數在 ScreenUtils 中,相關 API 以下所示:git
adaptScreen4VerticalSlide : 適配垂直滑動的屏幕
adaptScreen4HorizontalSlide: 適配水平滑動的屏幕
cancelAdaptScreen : 取消適配屏幕
isAdaptScreen : 是否適配屏幕複製代碼
UtilApk 中的 ScreenAdaptActivity 以設計圖爲 360dp 寬度 來作適配,咱們設置兩個 view 寬度爲 180dp,代碼以下所示:github
public class ScreenAdaptActivity extends BaseActivity {
private TextView tvUp;
private TextView tvDown;
public static void start(Context context) {
Intent starter = new Intent(context, ScreenAdaptActivity.class);
context.startActivity(starter);
}
@Override
public void initData(@Nullable Bundle bundle) {
if (ScreenUtils.isPortrait()) {
ScreenUtils.adaptScreen4VerticalSlide(this, 360);
} else {
ScreenUtils.adaptScreen4HorizontalSlide(this, 360);
}
}
@Override
public int bindLayout() {
return R.layout.activity_screen_adapt;
}
@Override
public void initView(Bundle savedInstanceState, View contentView) {
}
@Override
public void doBusiness() {
}
@Override
public void onWidgetClick(View view) {
}
public void toggleFullScreen(View view) {
ScreenUtils.toggleFullScreen(this);
}
@Override
protected void onDestroy() {
ScreenUtils.cancelAdaptScreen(this);
super.onDestroy();
}
}複製代碼
其在 1080x1920 420dpi(xxhdpi) 下的效果以下所示:markdown
其在 768x1280 320dpi(xhdpi) 下的效果以下所示:app
其在 480x800 240dpi(hdpi) 下的效果以下所示:ide
其在 320x480 160dpi(mdpi) 下的效果以下所示:函數
如上就是豎屏以 360dp 爲寬度和寬屏以 360dp 爲高度的適配效果。工具
若是看了上面今日頭條的那篇適配文章,那麼你可能已經知道其原理了,不明白的話能夠繼續看下個人解釋:
咱們知道 px = dp * density
,咱們要適配的話須要確保 dp 不變去修改 density,而安卓默認 density = dpi / 160
,其意思就是 1dp 有多少 px,也就是像素密度,咱們開發是按照一份設計稿來作的,那麼有沒有什麼辦法來讓 density 和設計稿尺寸作聯繫呢?假設咱們設計稿是寬度是 1080px,資源放在 xxhdpi,那麼咱們寬度轉換爲 dp 就是 1080 / 3 = 360dp,要在不一樣設備上寬度都表現爲 360dp,那麼就須要修改其 density = screenWidthPx / 360
,這樣就知足了上述條件,而和 density 相關的還有 densityDpi、scaledDensity,咱們根據 density 等比修改 densityDpi、scaledDensity 便可。oop
因爲 API 26 及以上的 Activity#getResources()#getDisplayMetrics()
和 Application#getResources()#getDisplayMetrics()
是不一樣的引用,因此在 API 26 及以上適配是沒有影響的,但在 API 26 如下 Activity#getResources()#getDisplayMetrics()
和 Application#getResources()#getDisplayMetrics()
是相同的引用,致使適配有問題,這裏要感謝 @MirkoWu 提出的問題,後面會有解決之法。佈局
可是因爲以前傳入的是設計圖 dp 尺寸,好比 xxhdpi 的 360dp,那樣若是你曾經寫的一個按鈕寬度是 36dp,那麼爲了適配還須要改成 36 / 3 = 12dp,並且字體大小也須要都修改成除以 3 的尺寸,這樣就並不能達到一步接入,因此我改爲了直接傳入以 mdpi 爲特例來適配,好比前面說到的 xxhdpi 的 360dp,那麼在 mdpi 下就是 360 * 3 = 1080dp,這樣咱們新建一個寬爲 1080px 的 mdpi 設備,而後切換爲該設備來預覽佈局就完美解決了以上問題,咱們在寫佈局的時候設計圖是 36px,那麼咱們直接就寫 36dp 便可,設計圖字體是 24px, 咱們直接就寫 24sp 便可,這樣即可達到和設計圖一致的效果。
不只如此,上面說到的問題若是在接入第三方 SDK 帶有界面或者 View 的話會致使它的尺寸全然不對,由於咱們那樣適配後界面寬度只有 360dp,而第三方 SDK 中頗有可能寫的佈局會超出 360dp,這便會引起新的問題,而我上面的改動也順便解決了這個問題。另外,圖片資源放在須要適配的最高 dpi 下面便可,好比 drawable-xxhdpi
或者 drawable-xxxhdpi
,這樣在高清屏上也不會致使失真。
可是這樣會致使獲取狀態欄和導航欄高度有問題,其獲取狀態欄高度代碼爲以下所示:
public static int getStatusBarHeight() {
Resources resources = Utils.getApp().getResources();
int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
return resources.getDimensionPixelSize(resourceId);
}複製代碼
因爲使用的是 Application#getResources,這會致使最後計算狀態欄高度使用的是修改事後的 density,在這裏也要感謝 @magic0908 無心間提到的 Resources.getSystem()
來獲取系統的 Resources
,果不其然能夠獲取到正確高度的狀態欄高度,代碼以下所示:
public static int getStatusBarHeight() {
Resources resources = Resources.getSystem();
int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
return resources.getDimensionPixelSize(resourceId);
}複製代碼
同理獲取導航欄高度也能夠這樣。
考慮到了 Resources.getSystem()
,那麼咱們在適配上豈不是能夠更方便,不用區分版本什麼的 Activity#getResources()#getDisplayMetrics()
和 Application#getResources()#getDisplayMetrics()
,也不須要什麼中間變量來記錄適配前的值,那些值咱們直接在 Resources#getSystem()#getDisplayMetrics()
中獲取 density
、densityDpi
、scaledDensity
便可,並且在修改系統字體的時候,Resources#getSystem()#getDisplayMetrics()
也會相應地改變,這樣也就不須要註冊 registerComponentCallbacks
來監聽系統字體的改變,因此最終的源碼非常簡潔,但其中間遇到的問題非常複雜,光工具類我這些天就更新了不少版原本解決其問題,從1.18.0
到 1.18.7
,有六個版本都是和這個適配有關係,但最終仍是完美地找到了解決方案,也要感謝你們的幫助,其最終源碼以下所示:
/**
* Adapt the screen for vertical slide.
*
* @param activity The activity.
* @param designWidthInPx The size of design diagram's width, in pixel.
*/
public static void adaptScreen4VerticalSlide(final Activity activity,
final int designWidthInPx) {
adaptScreen(activity, designWidthInPx, true);
}
/**
* Adapt the screen for horizontal slide.
*
* @param activity The activity.
* @param designHeightInPx The size of design diagram's height, in pixel.
*/
public static void adaptScreen4HorizontalSlide(final Activity activity,
final int designHeightInPx) {
adaptScreen(activity, designHeightInPx, false);
}
/**
* Reference from: https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA
*/
private static void adaptScreen(final Activity activity,
final int sizeInPx,
final boolean isVerticalSlide) {
final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();
final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();
final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();
if (isVerticalSlide) {
activityDm.density = activityDm.widthPixels / (float) sizeInPx;
} else {
activityDm.density = activityDm.heightPixels / (float) sizeInPx;
}
activityDm.scaledDensity = activityDm.density * (systemDm.scaledDensity / systemDm.dens
activityDm.densityDpi = (int) (160 * activityDm.density);
appDm.density = activityDm.density;
appDm.scaledDensity = activityDm.scaledDensity;
appDm.densityDpi = activityDm.densityDpi;
}
/**
* Cancel adapt the screen.
*
* @param activity The activity.
*/
public static void cancelAdaptScreen(final Activity activity) {
final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();
final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();
final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();
activityDm.density = systemDm.density;
activityDm.scaledDensity = systemDm.scaledDensity;
activityDm.densityDpi = systemDm.densityDpi;
appDm.density = systemDm.density;
appDm.scaledDensity = systemDm.scaledDensity;
appDm.densityDpi = systemDm.densityDpi;
}
/**
* Return whether adapt screen.
*
* @return {@code true}: yes<br>{@code false}: no
*/
public static boolean isAdaptScreen() {
final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();
final DisplayMetrics appDm = Utils.getApp().getResources().getDisplayMetrics();
return systemDm.density != appDm.density;
}複製代碼
在原理裏都已經說完了哈。
新老項目均可以用這套方案,老項目中若是有新的 Activity 加進來,那麼能夠對其使用該方案來適配,而後在啓動其餘老的 Activity 時候 cancelAdaptScreen
便可。新項目我建議採用我工具類中的使用,可讓你爽到極致,在 BaseActivity
中 setContentView(xx)
以前調用適配代碼便可,記得第二個參數必定要傳入設計圖的實際像素尺寸,再也不是曾經的 dp 尺寸了。
有了固定的尺寸,那麼咱們百分比是否是就很好實現了,計算後直接寫 xxdp 便可,這樣在全部設備上也都是必定的比例,哪裏還須要什麼百分比佈局什麼的來作?是否是 so easy,更多風騷的操做可待你解鎖。
若是個人工具類對你的適配形成了影響,歡迎到 AndroidUtilCode 提 issue,感謝今日頭條的方案,讓我能夠站在巨人的肩膀上裝一次 13。
最後
記得屏幕適配必定要用 1.19.0 版本及以上
記得屏幕適配必定要用 1.19.0 版本及以上
記得屏幕適配必定要用 1.19.0 版本及以上
給你們帶來了麻煩,sorry。