部分內(nèi)容轉(zhuǎn)載
作者:Alan_蘭哥
鏈接:http://www.reibang.com/p/0d61f9dffb14
屏幕尺寸灶体、屏幕分辨率、屏幕像素密度
屏幕尺寸:屏幕對(duì)角線長(zhǎng)度梆惯,單位是英寸晃跺,我們常說(shuō)的多少多少寸宏娄,比如4.7存手機(jī)、5.7存手機(jī)廉沮,指的就是這個(gè)颓遏。
屏幕分辨率:如 1920×1080,是指在手機(jī)屏幕的像素點(diǎn)的個(gè)數(shù)滞时,單位是px叁幢,1px = 1 像素點(diǎn),一般是縱向像素 × 橫向像素坪稽,意味著高有 1920 個(gè)像素點(diǎn)遥皂,寬有 1080 個(gè)像素點(diǎn)。
屏幕像素密度:是指每英寸上的像素點(diǎn)數(shù)刽漂,單位是 dpi(dotper inch)演训。像素密度和屏幕尺寸和屏幕分辨率有關(guān),它是由對(duì)角線的像素點(diǎn)數(shù)除以屏幕的大小得到的贝咙,關(guān)系如下
dp样悟、dip、dpi庭猩、sp窟她、px
dp:是Android 特有的,意為密度無(wú)關(guān)像素蔼水,Google 發(fā)布的 BASELINE(基準(zhǔn)線)為 160震糖,以此為基準(zhǔn)。
dip:Density Independent Pixels趴腋,同dp一個(gè)意思吊说,目前廢棄了,一般都寫(xiě)dp优炬。
dpi:即為屏幕像素密度的單位
sp:Scale-IndependentPixels的縮寫(xiě)颁井,可以根據(jù)文字大小首選項(xiàng)自動(dòng)進(jìn)行縮放。Google推薦我們使用12sp以上的大小蠢护,通逞疟觯可以使用12sp,14sp葵硕,18sp眉抬,22sp贯吓,為避免精度損失,建議最好不要使用奇數(shù)和小數(shù)蜀变。
px:就是我們常說(shuō)的像素
屏幕分辨率限定符(不推薦)
屏幕分辨率限定符適配需要在 res 文件夾下創(chuàng)建各種屏幕分辨率對(duì)應(yīng)的 values-xxx 文件夾宣决,如下圖:
然后根據(jù)一個(gè)基準(zhǔn)分辨率,例如基準(zhǔn)分辨率為 1280x720昏苏,將寬度分成 720 份尊沸,取值為 1px~720px,將高度分成 1280 份贤惯,取值為 1px~1280px洼专,生成各種分辨率對(duì)應(yīng)的 dimens.xml 文件。如下分別為分辨率 1280x720 與 1920x1080 所對(duì)應(yīng)的橫向dimens.xml 文件:
假設(shè)設(shè)計(jì)圖上的一個(gè)控件的寬度為 720px孵构,那么布局中就寫(xiě) android:layout_width="@dimen/x720" 屁商,當(dāng)運(yùn)行程序的時(shí)候,系統(tǒng)會(huì)根據(jù)設(shè)備的分辨率去尋找對(duì)應(yīng)的 dimens.xml 文件颈墅。例如運(yùn)行在分辨率為 1280x720 的設(shè)備上蜡镶,系統(tǒng)會(huì)自動(dòng)找到對(duì)應(yīng)的 values-1280x720 文件夾下的 lay_x.xml 文件,由上圖可知 x720 對(duì)應(yīng)的值為
720.px恤筛,可鋪滿該屏幕寬度官还。運(yùn)行在分辨率為 1920x1080 的設(shè)備上,系統(tǒng)會(huì)自動(dòng)找到對(duì)應(yīng)的 values-1920x1080 文件夾下的 lay_x.xml 文件毒坛,由上圖可知 x720 對(duì)應(yīng)的值為 1080.0px望伦,可鋪滿該屏幕寬度。這樣就達(dá)到了屏幕適配的要求煎殷!
smallestWidth 限定符
smallestWidth 限定符適配原理與屏幕分辨率限定符適配原理一樣屯伞,系統(tǒng)都是根據(jù)限定符去尋找對(duì)應(yīng)的 dimens.xml 文件。例如程序運(yùn)行在最小寬度為 360dp 的設(shè)備上豪直,系統(tǒng)會(huì)自動(dòng)找到對(duì)應(yīng)的 values-sw360dp 文件夾下的 dimens.xml 文件劣摇。區(qū)別就在于屏幕分辨率限定符適配是拿 px 值等比例縮放,而 smallestWidth 限定符適配是拿 dp 值來(lái)等比縮放而已弓乙。需要注意的是“最小寬度”是不區(qū)分方向的末融,即無(wú)論是寬度還是高度,哪一邊小就認(rèn)為哪一邊是“最小寬度”唆貌。如下分別為最小寬度為 360dp 與最小寬度為 640dp 所對(duì)應(yīng)的 dimens.xml 文件:
可以通過(guò)ScreenMatch生成
需要自己安裝ScreenMatch插件滑潘,第一次生成,會(huì)生成兩個(gè)文件
將screenMatch_example_dimens拷貝到value目錄锨咙,并且重命名成dimens;
screenMatch.properties文件中我們可以設(shè)置匹配哪些寬帶的屏幕追逮、忽略哪些以及匹配 哪個(gè)項(xiàng)目等等酪刀。
然后我再做一次生成操作就可以生成了各種swXXXdp目錄了
最后使用android:layout_width="@dimen/x720"
平板適配
新建layout-sw720dp目錄粹舵,里面添加layout布局文件。 使用平板打開(kāi)APP自動(dòng)切換骂倘。
也可以使用最小寬度符以及下面的適配 眼滤。
常見(jiàn)適配方案
布局適配
- 避免寫(xiě)死控件尺寸,使用wrap_content, match_parent
- LinearLayout xxx:weight="0.5“
- RelativeLayout xxx:layout_centerInParent="true" ...
- ContraintLayout xxx:layout_constraintLeft_toLeftOf="parent"...
相對(duì)定位历涝,可以設(shè)置偏移量0-1百分比
角度定位
邊距margin分顯隱性诅需,就是不顯示的時(shí)候也存在
可以設(shè)置寬高比,橫向縱向線可以設(shè)置百分比荧库,可以設(shè)置占位view(不繪制)堰塌,
組成鏈?zhǔn)娇梢栽O(shè)置權(quán)重和鏈?zhǔn)綐邮剑?br> Barrier設(shè)置屏障,約束關(guān)聯(lián)view一側(cè)效果
Group分衫,一起控制顯隱性
Optimizer對(duì)二次測(cè)量進(jìn)行優(yōu)化 - Percent-support-lib xxx:layout_widthPercent="30%" ...寬占父布局寬度多少场刑,
LayoutParams中屬性的獲取
onMeasure中,改變params.width為百分比計(jì)算結(jié)果蚪战,測(cè)量
如果測(cè)量值過(guò)小且設(shè)置的w/h是wrap_content牵现,重新測(cè)量
圖片資源適配
- .9圖或者SVG圖實(shí)現(xiàn)縮放
- 備用位圖匹配不同分辨率
動(dòng)態(tài)運(yùn)行時(shí)加載
(關(guān)于小分辨率手機(jī)看到比大分辨率手機(jī)大,跟像素點(diǎn)大小有關(guān)邀桑,可以參照權(quán)重處理瞎疼,
其實(shí)權(quán)重比也有問(wèn)題,就是屏幕分辯率帶來(lái)的問(wèn)題壁畸,不同屏幕其分辨率比值是不一樣的丑慎,導(dǎo)致大分辨率上是長(zhǎng)方形 ,小分辨率上是正方形)
自定義 分辨率布局
設(shè)計(jì)稿有標(biāo)準(zhǔn)尺寸1920X1080(設(shè)計(jì)稿針對(duì)的屏幕尺寸)
那View控件寬高(設(shè)計(jì)稿view的寬高)*當(dāng)前手機(jī)屏幕和標(biāo)準(zhǔn)尺寸(設(shè)計(jì)稿標(biāo)準(zhǔn)尺寸)的縮放比瓤摧,得到view控件的真實(shí)值竿裂。
另外 margin也有計(jì)算。
最外層加一個(gè)自己的布局照弥,在測(cè)量時(shí)完成上面的邏輯
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!flag) { //防止兩次測(cè)量
flag = true;
//獲取橫豎方向等比
float scaleX = Utils.getInstance(getContext()).getHorizontalScale();
float scaleY = Utils.getInstance(getContext()).getVerticalScale();
//子View個(gè)數(shù)
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
params.width = (int) (params.width * scaleX);
params.height = (int) (params.height * scaleY);
params.leftMargin = (int) (params.leftMargin * scaleX);
params.rightMargin = (int) (params.rightMargin * scaleX);
params.topMargin = (int) (params.topMargin * scaleY);
params.bottomMargin = (int) (params.bottomMargin * scaleY);
child.setPadding((int) (child.getPaddingLeft() * scaleX), (int) (child.getPaddingTop() * scaleY),
(int) (child.getPaddingRight() * scaleX), (int) (child.getPaddingBottom() * scaleY));
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public class Utils {
private static Utils utils;
private Context mContext;
//這里是設(shè)計(jì)稿參考寬高
private static final float STANDARD_WIDTH = 1080;
private static final float STANDARD_HEIGHT = 1920;
//這里是屏幕顯示寬高
private int mDisplayWidth;
private int mDisplayHeight;
public Utils(Context context) {
mContext = context;
//獲取屏幕寬高
if (mDisplayWidth == 0 || mDisplayHeight == 0) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (windowManager != null) {
//寬高獲取
DisplayMetrics displayMetrics = new DisplayMetrics();
//如果不是NavigationBar沉浸式(不包含NavigationBar腻异,如果屏幕有NavigationBar會(huì)比real小即真實(shí)分辨率小)
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
// windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);//真實(shí)屏幕寬高
//判斷當(dāng)前的橫豎屏
if (displayMetrics.widthPixels > displayMetrics.heightPixels) {
//橫屏
mDisplayWidth = displayMetrics.heightPixels;
mDisplayHeight = displayMetrics.widthPixels - getStatusBarHeight(context);
} else {
//豎屏
mDisplayWidth = displayMetrics.widthPixels;
mDisplayHeight = displayMetrics.heightPixels - getStatusBarHeight(context);
}
}
}
}
public static Utils getInstance(Context context) {
if (utils == null) {
utils = new Utils(context);
}
return utils;
}
//獲取狀態(tài)欄高度
public int getStatusBarHeight(Context context) {
int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resId > 0) {
return context.getResources().getDimensionPixelSize(resId);//獲取具體的像素值
}
return 0;
}
//獲取水平方向的縮放比例
public float getHorizontalScale() {
return mDisplayWidth / STANDARD_WIDTH;
}
//獲取垂直方向的縮放比例
public float getVerticalScale() {
return mDisplayHeight / (STANDARD_HEIGHT - getStatusBarHeight(mContext));
}
}
自定義 百分比布局
和谷歌提供的百分比布局類(lèi)似
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//獲取父容器寬高
if (!flag) {
flag = true;
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//給子控件設(shè)置修改后的屬性值
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
//獲取子控件
View child = getChildAt(i);
//獲取子控件LayoutParams
ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
//判斷子控件是否是百分比布局屬性
if (checkLayoutParams(layoutParams)) {
//是
LayoutParams lp = (LayoutParams) layoutParams;
float widthPercent = lp.widthPercent;
float heightPercent = lp.heightPercent;
float marginLeftPercent = lp.marginLeftPercent;
float marginRightPercent = lp.marginRightPercent;
float marginTopPercent = lp.marginTopPercent;
float marginBottomPercent = lp.marginBottomPercent;
if (widthPercent > 0) {
lp.width = (int) (widthSize * widthPercent);
}
if (heightPercent > 0) {
lp.height = (int) (heightSize * heightPercent);
}
if (marginLeftPercent > 0) {
lp.leftMargin = (int) (widthSize * marginLeftPercent);
}
if (marginRightPercent > 0) {
lp.rightMargin = (int) (widthSize * marginRightPercent);
}
if (marginTopPercent > 0) {
lp.topMargin = (int) (heightSize * marginTopPercent);
}
if (marginBottomPercent > 0) {
lp.bottomMargin = (int) (heightSize * marginBottomPercent);
}
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
/**
* 1这揣、創(chuàng)建自定義屬性
* 2悔常、在容器中去創(chuàng)建一個(gè)靜態(tài)內(nèi)部類(lèi)LayoutParams
* 3、在LayoutParams構(gòu)造方法中獲取自定義屬性
* 4给赞、onMeasure中給子控件設(shè)置修改后的屬性值
*/
public static class LayoutParams extends RelativeLayout.LayoutParams {
private float widthPercent;
private float heightPercent;
private float marginLeftPercent;
private float marginRightPercent;
private float marginTopPercent;
private float marginBottomPercent;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
//3机打、在LayoutParams構(gòu)造方法中獲取自定義屬性 解析自定義屬性
TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.PercentLayout);
widthPercent = typedArray.getFraction(R.styleable.PercentLayout_widthPercent, 1, 2, 0);
heightPercent = typedArray.getFraction(R.styleable.PercentLayout_heightPercent, 1, 2, 0);
marginLeftPercent = typedArray.getFraction(R.styleable.PercentLayout_marginLeftPercent, 1, 2, 0);
marginRightPercent = typedArray.getFraction(R.styleable.PercentLayout_marginRightPercent, 1, 2, 0);
marginTopPercent = typedArray.getFraction(R.styleable.PercentLayout_marginTopPercent, 1, 2, 0);
marginBottomPercent = typedArray.getFraction(R.styleable.PercentLayout_marginBottomPercent, 1, 2, 0);
typedArray.recycle();//回收
}
}
attrs
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="PercentLayout">
<attr name="widthPercent" format="fraction" />
<attr name="heightPercent" format="fraction" />
<attr name="marginLeftPercent" format="fraction" />
<attr name="marginRightPercent" format="fraction" />
<attr name="marginTopPercent" format="fraction" />
<attr name="marginBottomPercent" format="fraction" />
</declare-styleable>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<com.dn_alan.myapplication.percent.PercentLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#f00"
app:heightPercent="5%"
app:widthPercent="90%"
app:marginLeftPercent="30%"
app:marginRightPercent="30%"
app:marginTopPercent="1%"
tools:ignore="MissingPrefix" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/textView"
android:background="#f00"
app:heightPercent="30%"
app:marginBottomPercent="30%"
app:marginLeftPercent="30%"
app:marginRightPercent="30%"
app:marginTopPercent="30%"
app:widthPercent="30%"
tools:ignore="MissingPrefix" />
</com.dn_alan.myapplication.percent.PercentLayout>
像素密度布局(像素密度包含長(zhǎng)寬,所以和之前分辨率布局類(lèi)似但是也有不一樣的地方)
重點(diǎn)就是改變displayMetrics
dm.density = targetDensity; //(dpi/160) 后得到的值
dm.scaledDensity = targetScaleDensity;//字體縮放
dm.densityDpi = targetDensityDpi; //dpi
并注冊(cè)組件監(jiān)聽(tīng)片迅,判斷字體是否更改縮放残邀,生成適配當(dāng)前的縮放;
也可以 在application中注冊(cè)activity聲明周期回調(diào)
public class DensityUtils {
private static final float WIDTH = 360;//參考寬度(dp)
private static float appDensity;//表示屏幕密度
private static float appScaleDensity;//字體縮放比例,默認(rèn)為appDensity
public static void setDensity(final Application application, Activity activity) {
//獲取當(dāng)前屏幕信息
DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
if (appDensity == 0) {
//初始化賦值
appDensity = displayMetrics.density;
appScaleDensity = displayMetrics.scaledDensity;
//監(jiān)聽(tīng)字體變化
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
//字體發(fā)生更改芥挣,重新計(jì)算scaleDensity
if (newConfig != null && newConfig.fontScale > 0) {
appScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
//計(jì)算目標(biāo)density scaledDensity
float targetDensity = displayMetrics.widthPixels / WIDTH;//1080/360=3;
float targetScaleDensity = targetDensity * (appScaleDensity / appDensity);
int targetDensityDpi = (int) (targetDensity * 160);
//替換Activity的值
//px = dp * (dpi / 160)
DisplayMetrics dm = activity.getResources().getDisplayMetrics();
dm.density = targetDensity; //(dpi/160) 后得到的值
dm.scaledDensity = targetScaleDensity;
dm.densityDpi = targetDensityDpi; //dpi
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// //設(shè)置density
DensityUtils.setDensity(getApplication(), this);
setContentView(R.layout.activity_density);
}
今日頭條
也是改的這幾個(gè)值
做了架構(gòu)設(shè)計(jì)同時(shí)提供頁(yè)面單獨(dú)適配包括fragment和取消適配驱闷,
---->AutoSizeConfig.init ()
給了初始值放在獲取MetaData過(guò)慢
mDesignWidthInDp = 1080;
mDesignHeightInDp = 1920;
getMetaData(application);
mActivityLifecycleCallbacks = new ActivityLifecycleCallbacksImpl(new WrapperAutoAdaptStrategy(strategy == null ? new DefaultAutoAdaptStrategy() : strategy));
application.registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
----->ActivityLifecycleCallbacksImpl.onActivityCreated()
1. 適配androidx或者v4注冊(cè)fargment聲明周期
((androidx.fragment.app.FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(mFragmentLifecycleCallbacksToAndroidx, true);
最終也是調(diào)用的applyAdapt方法只不過(guò)第一個(gè)參數(shù)是fagement自己
2. 調(diào)用設(shè)置方法
//Activity 中的 setContentView(View) 一定要在 super.onCreate(Bundle); 之后執(zhí)行
if (mAutoAdaptStrategy != null) {
mAutoAdaptStrategy.applyAdapt(activity, activity);
}
----->DefaultAutoAdaptStrategy.applyAdapt(Object target, Activity activity)兩個(gè)參數(shù)都傳的當(dāng)前acitivity
如果你有自定義就是走自定義沒(méi)有走默認(rèn)類(lèi)DefaultAutoAdaptStrategy()
1. 檢查是否開(kāi)啟了外部三方庫(kù)的適配模式, 只要不主動(dòng)調(diào)用 ExternalAdaptManager 的方法, 下面的代碼就不會(huì)執(zhí)行。里面還會(huì)判斷是否實(shí)現(xiàn)取消接口空免,然后才走第三方空另。
2. target是不是實(shí)現(xiàn)了CancelAdapt,即取消蹋砚,走AutoSize.cancelAdapt(activity)扼菠,把值改回原來(lái)的還會(huì)走下面的方法
3. 如果 target 實(shí)現(xiàn) CustomAdapt 接口表示該 target 想自定義一些用于適配的參數(shù), 從而改變最終的適配效果AutoSize.autoConvertDensityOfCustomAdapt(activity, (CustomAdapt) target);--->autoConvertDensity(activity, sizeInDp, customAdapt.isBaseOnWidth())
4. 最終走AutoSize.autoConvertDensityOfGlobal(activity)
------>AutoSize.autoConvertDensityOfGlobal(activity)
基于高度還是寬度走不同方法
autoConvertDensityBaseOnWidth(activity, AutoSizeConfig.getInstance().getDesignWidthInDp());
autoConvertDensityBaseOnHeight(activity, AutoSizeConfig.getInstance().getDesignHeightInDp());
兩者最終調(diào)用了autoConvertDensity(activity, designWidthInDp, true);第三個(gè)參數(shù)寬是true高是false
------>AutoSize.autoConvertDensity(Activity activity, float sizeInDp, boolean isBaseOnWidth)
根據(jù)傳入對(duì)比值+設(shè)計(jì)大小+屏幕大小+字體放大比例以及MODE_MASK算出key
int key = Math.round((sizeInDp + subunitsDesignSize + screenSize) * AutoSizeConfig.getInstance().getInitScaledDensity()) & ~MODE_MASK;
isBaseOnWidth是true用屏幕寬比值算,否則用屏幕高比值算
targetDensity = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / sizeInDp;
根據(jù)targetDensity計(jì)算targetDensityDpi坝咐,targetScaledDensity循榆,targetXdpi,targetScreenWidthDp畅厢,targetScreenHeightDp;
放入緩存方便下次取出
mCache.put(key, new DisplayMetricsInfo(targetDensity, targetDensityDpi, targetScaledDensity, targetXdpi, targetScreenWidthDp, targetScreenHeightDp));
setDensity(activity, targetDensity, targetDensityDpi, targetScaledDensity, targetXdpi);
setScreenSizeDp(activity, targetScreenWidthDp, targetScreenHeightDp);
------>1. AutoSize.setDensity()
兼容Miui獲取Resources.class.getDeclaredField("mTmpMetrics"),一個(gè)是activity.getResources()冯痢,一個(gè)是getApplication().getResources();兩者順序是線activity再app
setDensity(DisplayMetrics displayMetrics, float density, int densityDpi, float scaledDensity, float xdpi)
設(shè)置三個(gè)值density框杜,densityDpi浦楣,scaledDensity
if (AutoSizeConfig.getInstance().getUnitsManager().isSupportDP()) {
displayMetrics.density = density;
displayMetrics.densityDpi = densityDpi;
}
if (AutoSizeConfig.getInstance().getUnitsManager().isSupportSP()) {
displayMetrics.scaledDensity = scaledDensity;
}
如果有附屬單位轉(zhuǎn)換displayMetrics.xdpi的值
------->2. AutoSize.setScreenSizeDp(Configuration configuration, int screenWidthDp, int screenHeightDp)
得到activityConfiguration和appConfiguration修改screenWidthDp和screenHeightDp
Configuration activityConfiguration = activity.getResources().getConfiguration();
Configuration appConfiguration = AutoSizeConfig.getInstance().getApplication().getResources().getConfiguration()
劉海屏適配
系統(tǒng)級(jí)適配規(guī)則
Google默認(rèn)的適配劉海屏策略是這樣的
- 如果應(yīng)用未適配劉海屏,需要系統(tǒng)對(duì)全屏顯示的應(yīng)用界面做特殊移動(dòng)處理(豎屏下移處理咪辱,橫屏右移處),因此此處出現(xiàn)了黑邊的現(xiàn)象振劳;如果應(yīng)用頁(yè)面布局不能做到自適應(yīng),就會(huì)出現(xiàn)布局問(wèn)題油狂;如果應(yīng)用布局能夠做到自適應(yīng)历恐,也會(huì)有黑邊無(wú)法全屏顯示的體驗(yàn)問(wèn)題
2)如果有狀態(tài)欄的App,則不受劉海屏的影響(有狀態(tài)肯定不是全屏专筷,那么就不會(huì)有下移的風(fēng)險(xiǎn))
在Android P版本中弱贼,通過(guò)DisplayCutout 類(lèi),可以確定非功能區(qū)域(劉海屏)的位置和形狀
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:
只有當(dāng)DisplayCutout完全包含在系統(tǒng)狀態(tài)欄中時(shí)磷蛹,才允許窗口延伸到DisplayCutout區(qū)域顯示吮旅。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:
該窗口決不允許與DisplayCutout區(qū)域重疊。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES :
該窗口始終允許延伸到屏幕短邊上的DisplayCutout
第一種樣式::LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
WindowManager.LayoutParams lp = this.getWindow().getAttributes();
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
this.getWindow().setAttributes(lp);
setContentView(R.layout.activity_android_p_demo_layout);
}
從圖中可以看出味咳,這個(gè)并不是完美的適配方案
第二種樣式樣式:LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
WindowManager.LayoutParams lp = this.getWindow().getAttributes();
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
this.getWindow().setAttributes(lp);
setContentView(R.layout.activity_android_p_demo_layout);
}
可以看出顯示效果和DEFAULT是一致的庇勃,LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER是不允許使用劉海屏區(qū)域,而LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT全屏窗口不允許使用劉海屏區(qū)域
第三種樣式樣式:LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
WindowManager.LayoutParams lp = this.getWindow().getAttributes();
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
this.getWindow().setAttributes(lp);
setContentView(R.layout.activity_android_p_demo_layout);
}
可以看出LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES已經(jīng)允許全屏app使用劉海屏了槽驶,只不過(guò)狀態(tài)欄那邊是白色
//設(shè)置頁(yè)面延伸到劉海區(qū)顯示
WindowManager.LayoutParams lp = mAc.getWindow().getAttributes();
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(lp);
//使內(nèi)容出現(xiàn)在status bar后邊责嚷,如果要使用全屏的話再加上View.SYSTEM_UI_FLAG_FULLSCREEN
requestWindowFeature(Window.FEATURE_NO_TITLE);
View decorView = mAc.getWindow().getDecorView();
int systemUiVisibility = decorView.getSystemUiVisibility();
int flags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN;
systemUiVisibility |= flags;
mAc.getWindow().getDecorView().setSystemUiVisibility(systemUiVisibility);
WindowInsets.getDisplayCutout() 來(lái)獲得 DisplayCutout object,里面包含了幾個(gè)有用的方法:
getBoundingRects():獲取劉海 / Cutout 所在的矩形區(qū)域的位置掂铐,多個(gè)劉海則返回多個(gè)區(qū)域(單位:像素)罕拂。
getSafeInsetLeft() / getSafeInsetTop() / getSafeInsetRight() / getSafeInsetBottom() :返回安全區(qū)上下左右的偏移值(單位:像素)
套用小米的處理話來(lái)說(shuō)
處理好同一頁(yè)面揍异,進(jìn)入與退出全屏模式(fullscreen mode)的過(guò)渡
因?yàn)樵谀J(rèn)模式 / LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 下,系統(tǒng)針對(duì)全屏與非全屏的頁(yè)面聂受,耳朵區(qū)的顯示邏輯不一樣蒿秦。如果開(kāi)發(fā)者沒(méi)有處理好烤镐,容易出現(xiàn)頁(yè)面可用區(qū)域跳變的問(wèn)題蛋济。針對(duì)這種頁(yè)面,我們建議開(kāi)發(fā)者主動(dòng)聲明是否使用耳朵區(qū)炮叶,以避免跳變碗旅。
我們總結(jié)一下:
if (context instanceof AppCompatActivity) {
AppCompatActivity app = (AppCompatActivity) context;
if (app.getSupportActionBar() != null) {
app.getSupportActionBar().hide();
}
//LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
// 只有當(dāng)DisplayCutout完全包含在系統(tǒng)狀態(tài)欄中時(shí),才允許窗口延伸到DisplayCutout區(qū)域顯示镜悉。
//LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
//該窗口決不允許與DisplayCutout區(qū)域重疊,但是會(huì)把狀態(tài)欄變成黑色祟辟,效果很差,建議這種情況使用DEFAULT侣肄。
//LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
//該窗口始終允許延伸到屏幕短邊上的DisplayCutout區(qū)域旧困。
//PS:如果需要應(yīng)用的布局延伸到劉海區(qū)顯示,需要設(shè)置SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN稼锅。
if (isUseImmersiveBars) {//是否使用沉浸式狀態(tài)欄
app.getWindow().getDecorView().
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); //設(shè)置頁(yè)面全屏顯示
} else {
if (isUseCutout) {//是否使用劉海
app.getWindow().getDecorView().
setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
//設(shè)置頁(yè)面全屏顯示
} else {
app.getWindow().getDecorView().setSystemUiVisibility(0);
}
}
if (cutoutMode == -1) {//是否做劉海屏適配
cutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
}
WindowManager.LayoutParams lp = app.getWindow().getAttributes();
lp.layoutInDisplayCutoutMode = cutoutMode;
//設(shè)置頁(yè)面延伸到劉海區(qū)顯示
app.getWindow().setAttributes(lp);
}
小米特殊處理:聲明 Maximum Aspect Ratio
Android 標(biāo)準(zhǔn)接口中吼具,支持應(yīng)用聲明其支持的最大屏幕高寬比(maximum aspect ratio)。具體聲明如下矩距,其中的 ratio_float 被定義為是高除以寬拗盒,以 16:9 為例,ratio_float = 16/9 = 1.778 (18:9則為2.0)锥债。
<application>
<meta-data android:name="android.max_aspect" android:value="ratio_float" />
</application>
若開(kāi)發(fā)者沒(méi)有聲明該屬性陡蝇,ratio_float 的默認(rèn)值為1.86,小于2.0哮肚,因此這類(lèi)應(yīng)用在全面屏手機(jī)上登夫,默認(rèn)不會(huì)全屏顯示,屏幕底部會(huì)留黑允趟∧詹撸考慮到將有更多 19.5:9 甚至更長(zhǎng)的手機(jī)出現(xiàn),建議開(kāi)發(fā)者聲明 Maximum Aspect Ratio ≥ 2.2 或更多拼窥。值得一提的是戏蔑,如果應(yīng)用的 android:resizeableActivity 已經(jīng)設(shè)置為 true,就不必設(shè)置 Maximum Aspect Ratio 了
Android Q
/**
* 小米刪除劉海區(qū)域
*
* @param context context
*/
public static void clearExtraFlag(Context context) {
int flag = 0x00000100 | 0x00000200 | 0x00000400;
//0x00000100 開(kāi)啟配置
//0x00000200 豎屏配置
//0x00000400 橫屏配置
//<meta-data
// android:name="notch.config"
// android:value="portrait|landscape"/>
if (context instanceof AppCompatActivity) {
AppCompatActivity app = (AppCompatActivity) context;
try {
Method method = Window.class.getMethod("clearExtraFlags",
int.class);
method.invoke(app.getWindow(), flag);
} catch (Exception e) {
Log.i(TAG, "addExtraFlags not found.");
}
}
}
/**
* 小米添加劉海區(qū)域
*
* @param context context
*/
public static void addExtraFlag(Context context) {
int flag = 0x00000100 | 0x00000200 | 0x00000400;
if (context instanceof AppCompatActivity) {
AppCompatActivity app = (AppCompatActivity) context;
try {
Method method = Window.class.getMethod("addExtraFlags",
int.class);
method.invoke(app.getWindow(), flag);
} catch (Exception e) {
Log.i(TAG, "addExtraFlags not found.");
}
}
}
/*劉海屏全屏顯示FLAG*/
private static final int FLAG_NOTCH_SUPPORT = 0x00010000;
/**
* 設(shè)置應(yīng)用窗口在華為劉海屏手機(jī)使用劉海區(qū)
*
* @param window 應(yīng)用頁(yè)面window對(duì)象
*/
public static void setFullScreenWindowLayoutInDisplayCutout(Window window) {
if (window == null) {
return;
}
WindowManager.LayoutParams layoutParams = window.getAttributes();
try {
Class layoutParamsExCls = Class.forName("com.huawei.android.view.LayoutParamsEx");
Constructor con = layoutParamsExCls.getConstructor(WindowManager.LayoutParams.class);
Object layoutParamsExObj = con.newInstance(layoutParams);
Method method = layoutParamsExCls.getMethod("addHwFlags", int.class);
method.invoke(layoutParamsExObj, FLAG_NOTCH_SUPPORT);
} catch (Exception e) {
Log.e(TAG, "other Exception");
}
}
/**
* 設(shè)置應(yīng)用窗口在華為劉海屏手機(jī)使不用劉海區(qū)
*
* @param window 應(yīng)用頁(yè)面window對(duì)象
*/
public static void setNotFullScreenWindowLayoutInDisplayCutout(Window window) {
if (window == null) {
return;
}
WindowManager.LayoutParams layoutParams = window.getAttributes();
try {
Class layoutParamsExCls = Class.forName("com.huawei.android.view.LayoutParamsEx");
Constructor con = layoutParamsExCls.getConstructor(WindowManager.LayoutParams.class);
Object layoutParamsExObj = con.newInstance(layoutParams);
Method method = layoutParamsExCls.getMethod("clearHwFlags", int.class);
method.invoke(layoutParamsExObj, FLAG_NOTCH_SUPPORT);
} catch (Exception e) {
Log.e(TAG, "other Exception");
}
}
劉海屏判斷
ANDROID P
if (context instanceof AppCompatActivity) {
AppCompatActivity app = (AppCompatActivity) context;
DisplayCutout cutout = app.getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
if (cutout == null) {
// listener可以無(wú)視
// 這里理解為無(wú)劉海
if (listener != null) {
listener.isHasCutout(false);
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "cutout==null, is not notch screen");//通過(guò)cutout是否為null判斷是否劉海屏手機(jī)
}
} else {
List<Rect> rects = cutout.getBoundingRects();
if (rects == null || rects.size() == 0) {
// listener可以無(wú)視
// 這里理解為無(wú)劉海
listener.isHasCutout(false);
} else {
listener.isHasCutout(true);
// listener可以無(wú)視
// 這里理解為有劉海
//如需用到cutout信息鲁纠,則使用
if (listener instanceof OnCutoutDetailListener) {
((OnCutoutDetailListener) listener).onCutout(cutout);
}
}
}
}
Android Q
各家方法判斷
/**
* 判斷用戶是否開(kāi)啟了隱藏劉海區(qū)域
*
* @param context
* @return
*/
public static boolean isHideNotchScreen4Xiaomi(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return Settings.Global.getInt(context.getContentResolver(), "force_black", 0) == 1;
}else{
return false;
}
}
/**
* 判斷小米是否有劉海屏
*
* @return 是否有劉海屏
*/
public static boolean hasNotchInScreenAtXiaomi() {
return SystemProperties.getint("ro.miui.notch", 0) == 1;
}
/**
* 華為手機(jī)是否有劉海屏
*
* @param context context
* @return 是否有劉海屏
*/
public static boolean hasNotchInScreenAtHuawei(Context context) {
boolean ret = false;
try {
ClassLoader cl = context.getClassLoader();
Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
ret = (boolean) get.invoke(HwNotchSizeUtil);
} catch (ClassNotFoundException e) {
Log.e(TAG, "hasNotchInScreen ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e(TAG, "hasNotchInScreen NoSuchMethodException");
} catch (Exception e) {
Log.e(TAG, "hasNotchInScreen Exception");
}
return ret;
}
/**
* 判斷是否有劉海屏
*
* @param context context
* @return 是否有劉海屏
*/
private static final int NOTCH_IN_SCREEN_VOIO = 0x00000020;//是否有凹槽
public static boolean hasNotchInScreenAtVoio(Context context) {
boolean ret = false;
try {
ClassLoader cl = context.getClassLoader();
Class FtFeature = cl.loadClass("android.util.FtFeature");
Method get = FtFeature.getMethod("isFeatureSupport", int.class);
ret = (boolean) get.invoke(FtFeature, NOTCH_IN_SCREEN_VOIO);
} catch (ClassNotFoundException e) {
Log.e(TAG, "hasNotchInScreen ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e(TAG, "hasNotchInScreen NoSuchMethodException");
} catch (Exception e) {
Log.e(TAG, "hasNotchInScreen Exception");
} finally {
return ret;
}
}
/**
* 判斷oppo是否有劉海屏
*
* @param context context
* @return 是否有劉海屏
*/
public static boolean hasNotchInScreenAtOppo(Context context) {
return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}
獲取劉海高度
/**
* 獲得小米劉海屏幕高度
*
* @param context context
* @return 小米屏幕高度
*/
public static int getNotchXiaomiHeight(Context context) {
int result = 0;
int resourceId = context.getResources().getIdentifier("notch_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
/**
* 獲得小米劉海屏幕寬度
*
* @param context context
* @return 小米屏幕寬度
*/
public static int getNotchXiaomiWidth(Context context) {
int result = 0;
int resourceId = context.getResources().getIdentifier("notch_width", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
/**
* 獲取華為劉海屏的劉海尺寸
*
* @param context context
* @return 劉海尺寸
*/
public static int[] getNotchSize4Huawei(Context context) {
int[] ret = new int[]{0, 0};
try {
ClassLoader cl = context.getClassLoader();
Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method get = HwNotchSizeUtil.getMethod("getNotchSize");
ret = (int[]) get.invoke(HwNotchSizeUtil);
} catch (ClassNotFoundException e) {
Log.e(TAG, "getNotchSize ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e(TAG, "getNotchSize NoSuchMethodException");
} catch (Exception e) {
Log.e(TAG, "getNotchSize Exception");
}
return ret;
}
狀態(tài)欄(沉浸式兩者:一種設(shè)置狀態(tài)欄顏色或者透明总棵,一種隱藏狀態(tài)欄,因?yàn)槿梁髣⒑N恢贸霈F(xiàn)黑色所以才會(huì)劉海屏適配)
狀態(tài)欄高度比劉海高 改含。所以如果需要移動(dòng)情龄,就移動(dòng)狀態(tài)欄高度。
關(guān)于狀態(tài)欄介紹:http://www.reibang.com/p/752f4551e134
/**
* 獲取狀態(tài)欄高度
*
* @param activity
* @return
*/
public static int getStatusBarHeight(Activity activity) {
int statusBarHeight = 0;
int resourceId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = activity.getResources().getDimensionPixelSize(resourceId);
}
return statusBarHeight;
}
/**
* 修改狀態(tài)欄為全透明
*
* @param activity
*/
@TargetApi(19)
public static void transparencyBar(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Window window = activity.getWindow();
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
/**
* 回復(fù)狀態(tài)欄
* PS:如果一開(kāi)始有沉浸式狀態(tài)欄,然后再恢復(fù)初始化骤视,那么狀態(tài)欄會(huì)變成黑色背景
* 如用到此方法鞍爱,可以做延遲操作改變自己想要的狀態(tài)欄顏色。
*
* @param activity
*/
@TargetApi(19)
public static void restoreBar(Activity activity, int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(0);
window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Window window = activity.getWindow();
window.setFlags(0,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
setStatusBarColor(activity, color);
}
/**
* 修改狀態(tài)欄顏色专酗,支持4.4以上版本
*
* @param activity
* @param colorId
*/
public static void setStatusBarColor(Activity activity, int colorId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
// window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(activity.getResources().getColor(colorId));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//使用SystemBarTint庫(kù)使4.4版本狀態(tài)欄變色睹逃,需要先將狀態(tài)欄設(shè)置為透明
transparencyBar(activity);
SystemBarTintManager tintManager = new SystemBarTintManager(activity);
tintManager.setStatusBarTintEnabled(true);
tintManager.setStatusBarTintResource(colorId);
}
}
public static void setStatusBarColor(Activity activity, int colorId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
// window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(activity.getResources().getColor(colorId));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//使用SystemBarTint庫(kù)使4.4版本狀態(tài)欄變色,需要先將狀態(tài)欄設(shè)置為透明
transparencyBar(activity);
SystemBarTintManager tintManager = new SystemBarTintManager(activity);
tintManager.setStatusBarTintEnabled(true);
tintManager.setStatusBarTintResource(colorId);
}
}
狀態(tài)欄字色和圖標(biāo)淺黑色
/**
* 狀態(tài)欄亮色模式祷肯,設(shè)置狀態(tài)欄黑色文字沉填、圖標(biāo),
* 適配4.4以上版本MIUIV佑笋、Flyme和6.0以上版本其他Android
*
* @param activity
* @return 1:MIUUI 2:Flyme 3:android6.0
*/
public static int statusBarLightMode(Activity activity) {
return statusBarLightMode(activity.getWindow());
}
/**
* 狀態(tài)欄亮色模式翼闹,設(shè)置狀態(tài)欄黑色文字、圖標(biāo)蒋纬,
* 適配4.4以上版本MIUIV猎荠、Flyme和6.0以上版本其他Android
*
* @return 1:MIUUI 2:Flyme 3:android6.0
*/
public static int statusBarLightMode(Window window) {
int result = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (MIUISetStatusBarLightMode(window, true)) {
result = 1;
} else if (FlymeSetStatusBarLightMode(window, true)) {
result = 2;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
result = 3;
}
}
return result;
}
/**
* 狀態(tài)欄亮色模式,設(shè)置狀態(tài)欄黑色文字蜀备、圖標(biāo)关摇,
* 適配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
*
* @param activity
* @return 1:MIUUI 2:Flyme 3:android6.0
*/
public static int statusBarDarkMode(Activity activity) {
int result = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (MIUISetStatusBarLightMode(activity.getWindow(), false)) {
result = 1;
} else if (FlymeSetStatusBarLightMode(activity.getWindow(), false)) {
result = 2;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.getWindow().getDecorView().
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
result = 3;
}
}
return result;
}
/**
* 設(shè)置狀態(tài)欄圖標(biāo)為深色和魅族特定的文字風(fēng)格
* 可以用來(lái)判斷是否為Flyme用戶
*
* @param window 需要設(shè)置的窗口
* @param dark 是否把狀態(tài)欄文字及圖標(biāo)顏色設(shè)置為深色
* @return boolean 成功執(zhí)行返回true
*/
public static boolean FlymeSetStatusBarLightMode(Window window, boolean dark) {
boolean result = false;
if (window != null) {
try {
WindowManager.LayoutParams lp = window.getAttributes();
Field darkFlag = WindowManager.LayoutParams.class
.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
Field meizuFlags = WindowManager.LayoutParams.class
.getDeclaredField("meizuFlags");
darkFlag.setAccessible(true);
meizuFlags.setAccessible(true);
int bit = darkFlag.getInt(null);
int value = meizuFlags.getInt(lp);
if (dark) {
value |= bit;
} else {
value &= ~bit;
}
meizuFlags.setInt(lp, value);
window.setAttributes(lp);
result = true;
} catch (Exception e) {
}
}
return result;
}
/**
* 需要MIUIV6以上
*
* @param dark 是否把狀態(tài)欄文字及圖標(biāo)顏色設(shè)置為深色
* @return boolean 成功執(zhí)行返回true
*/
public static boolean MIUISetStatusBarLightMode(Window window, boolean dark) {
boolean result = false;
if (window != null) {
Class clazz = window.getClass();
try {
int darkModeFlag = 0;
Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
if (dark) {
extraFlagField.invoke(window, darkModeFlag, darkModeFlag);//狀態(tài)欄透明且黑色字體
} else {
extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字體
}
result = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//開(kāi)發(fā)版 7.7.13 及以后版本采用了系統(tǒng)API琼掠,舊方法無(wú)效但不會(huì)報(bào)錯(cuò)拒垃,所以兩個(gè)方式都要加上
//SYSTEM_UI_FLAG_VISIBLE這個(gè)表示,不占據(jù)全屏瓷蛙,把狀態(tài)欄會(huì)空出來(lái)悼瓮,
if (dark) {
window.getDecorView().
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
window.getDecorView().
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
}
} catch (Exception e) {
}
}
return result;
}
華為:https://devcenter-test.huawei.com/consumer/cn/devservice/doc/50114
小米:https://dev.mi.com/console/doc/detail?pId=1293
Oppo:https://open.oppomobile.com/service/message/detail?id=61876
Vivo:https://dev.vivo.com.cn/documentCenter/doc/103
系統(tǒng)OS判斷
參考http://www.reibang.com/p/ba9347a5a05a
public class OSUtil {
private static final String TAG = "Rom";
public static final String ROM_MIUI = "MIUI";
public static final String ROM_EMUI = "EMUI";
public static final String ROM_FLYME = "FLYME";
public static final String ROM_OPPO = "OPPO";
public static final String ROM_SMARTISAN = "SMARTISAN";
public static final String ROM_VIVO = "VIVO";
public static final String ROM_QIKU = "QIKU";
private static final String KEY_VERSION_MIUI = "ro.miui.ui.version.name";
private static final String KEY_VERSION_EMUI = "ro.build.version.emui";
private static final String KEY_VERSION_OPPO = "ro.build.version.opporom";
private static final String KEY_VERSION_SMARTISAN = "ro.smartisan.version";
private static final String KEY_VERSION_VIVO = "ro.vivo.os.version";
private static String sName;
private static String sVersion;
public static boolean isEmui() {
return check(ROM_EMUI);
}
public static boolean isMiui() {
return check(ROM_MIUI);
}
public static boolean isVivo() {
return check(ROM_VIVO);
}
public static boolean isOppo() {
return check(ROM_OPPO);
}
public static boolean isFlyme() {
return check(ROM_FLYME);
}
public static boolean is360() {
return check(ROM_QIKU) || check("360");
}
public static boolean isSmartisan() {
return check(ROM_SMARTISAN);
}
public static String getName() {
if (sName == null) {
check("");
}
return sName;
}
public static String getVersion() {
if (sVersion == null) {
check("");
}
return sVersion;
}
public static boolean check(String rom) {
if (sName != null) {
return sName.equals(rom);
}
if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_MIUI))) {
sName = ROM_MIUI;
} else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_EMUI))) {
sName = ROM_EMUI;
} else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_OPPO))) {
sName = ROM_OPPO;
} else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_VIVO))) {
sName = ROM_VIVO;
} else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_SMARTISAN))) {
sName = ROM_SMARTISAN;
} else {
sVersion = Build.DISPLAY;
if (sVersion.toUpperCase().contains(ROM_FLYME)) {
sName = ROM_FLYME;
} else {
sVersion = Build.UNKNOWN;
sName = Build.MANUFACTURER.toUpperCase();
}
}
return sName.equals(rom);
}
public static String getProp(String name) {
String line = null;
BufferedReader input = null;
try {
Process p = Runtime.getRuntime().exec("getprop " + name);
input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
line = input.readLine();
input.close();
} catch (IOException ex) {
Log.e(TAG, "Unable to read prop " + name, ex);
return null;
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return line;
}
}