- px適配名斟;
- 百分比適配池凄;
- 修改dp適配;
屏幕適配
- 布局適配
- 使用wrap_content阳藻,match_parent晰奖;
- LinearLayout xxx:layout_weight="1"
- RelativeLayout xxx:layout_centerParent="true"
- ContraintLayout xxx:layout_constraintLeft_toLeftOf="parent"
- Percent-support-lib xxx:layout_widthParcent="30%"
- 圖片資源適配
- .9圖或者SVG圖實(shí)現(xiàn)縮放
- 備用位圖匹配不同的分辨率
- 用戶流程適配
- 根據(jù)業(yè)務(wù)邏輯執(zhí)行不同的邏輯跳轉(zhuǎn)
- 根據(jù)別名展示不同的頁面
- 限定符適配
- 分辨率限定符 drawable-hdpi,drawable-xhdpi,...
- 尺寸限定符 layout-small,layout-large,...
- 最小寬度限定符 values-sw360dp,values-sw84dp,...
- 屏幕方向限定符 layout-land,layout-port
- 劉海屏適配
- Android 9.0 官方適配
- 華為,Oppo腥泥,Vivo
自定義view中像素適配
- 以一個特定寬度尺寸的設(shè)備為參考匾南,在view的加載過程,根據(jù)當(dāng)前設(shè)備的實(shí)際像素?fù)Q算出目標(biāo)像素蛔外,再作用在控件上蛆楞。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
if(!flag){
float scaleX = Utils.getInstance(getContext()).getHorizontalScale();//獲取橫向的縮放比例
float scaleY = Utils.getInstance(getContext()).getVerticalScale();//獲取豎向的縮放比例
for(int i = 0; i < getChildCount(); i++){
View child = getChildAt(i);
LayoutParams lp = (LayoutParams)child.getLayoutParams();
lp.width = (int) (lp.width * scaleX);//換算寬度目標(biāo)值
lp.height = (int)(lp.height * scaleY);//換算高度的目標(biāo)值
lp.topMargin = (int)(lp.topMargin * scaleY);//換算四周間距的目標(biāo)值
......
}
flag = true;
}
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
}
縮放比例 是當(dāng)前設(shè)備的像素值和參考值比值的結(jié)果;布局中是px來寫布局夹厌;
百分比布局適配
- 以父容器尺寸作為參考臊岸,在view的加載過程,根據(jù)當(dāng)前父容器實(shí)際尺寸換算出目標(biāo)尺寸尊流,再作用在view上帅戒。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
//獲取父容器的寬高
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
...
for(int i = 0; i < getChildCount(); i++){
View child = getChildAt(i);//重寫設(shè)置子view的布局屬性,再進(jìn)行view的測量
LayoutParams lp = (LayoutParams)child.getLayoutParams();
float widthPercent = ((LayoutParams).lp).widthPercent;//自定義百分比屬性
if(widthPercent > 0){
lp.width = (int) width * widthPercent;//設(shè)置當(dāng)前view在父容器中的尺寸占比
}
...
}
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
}
注意:需要參考RelativeLayout中的靜態(tài)類LayoutParams實(shí)現(xiàn)自定義容器布局崖技;
view的加載過程逻住,view是通過容器view來進(jìn)行加載;
- 創(chuàng)建自定義屬性
- 繼承 static LayoutParams迎献,解析自定義屬性瞎访;
- 必須重寫generateLayoutParams(AttributeSet attrs)方法;
- 重寫onMeasure吁恍,計(jì)算扒秸;
像素密度適配
- 修改density,scaleDensity冀瓦,densityDpi值,直接更改系統(tǒng)內(nèi)部對于目標(biāo)尺寸而言的像素密度伴奥。
px : 其實(shí)就是像素單位,比如我們通常說的手機(jī)分辨列表800*400都是px的單位
sp : 同dp相似翼闽,還會根據(jù)用戶的字體大小偏好來縮放
dp : 虛擬像素拾徙,在不同的像素密度的設(shè)備上會自動適配
dip: 同dp
要理解dp,首先要先引入dpi這個概念感局,dpi全稱是dots per inch尼啡,對角線每英寸的像素點(diǎn)的個數(shù)暂衡,
而dp也叫dip,是device independent pixels崖瞭。設(shè)備不依賴像素的一個單位狂巢。在不同的像素密度的設(shè)備上會自動適配,比如:
在320x480分辨率书聚,像素密度為160,1dp=1px
在480x800分辨率唧领,像素密度為240,1dp=1.5px
計(jì)算公式:px = dp * (dpi/160)
private static final float WIDTH = 360;//參考像素密度
protected void setDensity(final Application application,Activity activity){
DisplayMetics appDisplayMetrics = application.getResources().getDisplayMetrics();
//獲取目標(biāo)density值
...
float targetDensity = appDisplayMetrics.widthPixels / WIDTH;
float targetScaleDensity = targetDensity * (appScaledDensity / appDensity);
int targetDensityDpi = (int)(targetDensity * 160);
//替換Activity的density,scaleDensity等值
DisplayMetrics displayMetrics = activity.getResources().getDisplayMetrics();
displayMetrics.density = targetDensity;
displayMetrics.scaledDensity = targetScaleDensity;
displayMetrics.densityDpi = targetDensityDpi;
}
//可運(yùn)行代碼
private static final float WIDTH = 360;//參考屏幕的寬寺惫,單位是dp 設(shè)計(jì)稿的大小
private static float appDensity;//表示屏幕的密度
private static float appScaledDensity;//表示字體縮放比例 默認(rèn)是density
public static void setDensity(final Application application, Activity activity){
final DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
if (appDensity == 0){
appDensity = displayMetrics.density;
appScaledDensity = displayMetrics.scaledDensity;
//監(jiān)聽系統(tǒng) 字體變化
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig != null && newConfig.fontScale > 0){
appScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
//目標(biāo)的值
float targetDensity = displayMetrics.widthPixels / WIDTH;// 1080 / 360 = 3.0 --- 類比 160 /160 = 1
float targetScaledDensity = targetDensity * (appScaledDensity / appDensity);
int targetDensityDpi = (int) (targetDensity * 160);
//需要修改的 值
DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
metrics.density = targetDensity;
metrics.scaledDensity = targetScaledDensity;
metrics.densityDpi = targetDensityDpi;
}
劉海屏適配
- Android官方9.0劉海屏適配策略
- 如果非全屏模式(有狀態(tài)欄)疹吃,則app不受劉海屏的影響蹦疑,劉海屏的高度就是狀態(tài)欄的高西雀;
- 如果全屏模式,app未適配劉海屏歉摧,則系統(tǒng)會對界面做特殊處理艇肴,豎屏向下移動,橫屏向右移動
- 全屏模式下劉海區(qū)黑邊(內(nèi)容區(qū)域下挫)問題叁温。
//1.全屏設(shè)置
requestWindowFeature(Window.FEATURE_NO_TITLE);
window.setFlags(LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParamsWindowManager.FLAG_FULLSCREEN);
//2.讓內(nèi)容區(qū)域延伸至劉海區(qū)再悼,需要先判斷是否有劉海
WindowManager.LayoutParams params = window.getAttributes();
params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
window.setAttributes();
//3.沉浸式
int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
int visibility = view.getDecorView().getSystemUiVisibility();
visibility |= flags;
window.getDecorView().setSystemUiVisibility(visibility); - 避開劉海區(qū)域
//獲取劉海高度,通常情況劉海的高度不會超過狀態(tài)欄高度
int height = getStatusBarHeight();
//設(shè)置控件的margin
//設(shè)置父容器的padding
- 其他廠商的適配
部分代碼參考
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//1 設(shè)置全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);
Window window = getWindow();
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
if (hasCutout(window)) {
//2 內(nèi)容可以延伸進(jìn)劉海
WindowManager.LayoutParams attributes = window.getAttributes();
/*
* @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 默認(rèn)
* @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 可以擴(kuò)展
* @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 不允許
*/
attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
//3.沉浸式設(shè)置
int flags = View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;//虛擬導(dǎo)航欄
int visibility = window.getDecorView().getSystemUiVisibility();//系統(tǒng)的顯示類型
visibility |= flags;
window.getDecorView().setSystemUiVisibility(visibility);
}
setContentView(R.layout.activity_cutout);
//1.判斷手機(jī)廠商 2.判斷是否有劉海 3.是否內(nèi)容延伸進(jìn)劉海 4.是否內(nèi)容 避開劉海 5膝但、獲取劉海高
}
@TargetApi(Build.VERSION_CODES.P)
boolean hasCutout(Window window) {
View decorView = window.getDecorView();
WindowInsets rootWindowInsets = decorView.getRootWindowInsets();
DisplayCutout displayCutout = rootWindowInsets.getDisplayCutout();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && displayCutout != null) {
if (displayCutout.getBoundingRects() != null && displayCutout.getBoundingRects().size() > 0 && displayCutout.getSafeInsetTop() > 0) {
return true;
}
}
return false;
}
private int getStatusBarHeight(Context context){
int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resId > 0){
return context.getResources().getDimensionPixelSize(resId);
}
return 0;
}