屏幕適配

部分內(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)系如下


pingmu0.png

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ō)的像素


pingmu1.png

pingmu2.png

屏幕分辨率限定符(不推薦)

屏幕分辨率限定符適配需要在 res 文件夾下創(chuàng)建各種屏幕分辨率對(duì)應(yīng)的 values-xxx 文件夾宣决,如下圖:


pingmu3.png

然后根據(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 文件:


pingmu4.PNG

假設(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 文件:


pingmu5.PNG

可以通過(guò)ScreenMatch生成

需要自己安裝ScreenMatch插件滑潘,第一次生成,會(huì)生成兩個(gè)文件
將screenMatch_example_dimens拷貝到value目錄锨咙,并且重命名成dimens;
screenMatch.properties文件中我們可以設(shè)置匹配哪些寬帶的屏幕追逮、忽略哪些以及匹配 哪個(gè)項(xiàng)目等等酪刀。
然后我再做一次生成操作就可以生成了各種swXXXdp目錄了
最后使用android:layout_width="@dimen/x720"


pingmu6.PNG

pingmu7.PNG

pingmu8.PNG

平板適配

新建layout-sw720dp目錄粹舵,里面添加layout布局文件。 使用平板打開(kāi)APP自動(dòng)切換骂倘。

也可以使用最小寬度符以及下面的適配 眼滤。

常見(jiàn)適配方案

布局適配

  1. 避免寫(xiě)死控件尺寸,使用wrap_content, match_parent
  2. LinearLayout xxx:weight="0.5“
  3. RelativeLayout xxx:layout_centerInParent="true" ...
  4. 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)化
  5. Percent-support-lib xxx:layout_widthPercent="30%" ...寬占父布局寬度多少场刑,
    LayoutParams中屬性的獲取
    onMeasure中,改變params.width為百分比計(jì)算結(jié)果蚪战,測(cè)量
    如果測(cè)量值過(guò)小且設(shè)置的w/h是wrap_content牵现,重新測(cè)量

圖片資源適配

  1. .9圖或者SVG圖實(shí)現(xiàn)縮放
  2. 備用位圖匹配不同分辨率

動(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)的適配劉海屏策略是這樣的

  1. 如果應(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è)并不是完美的適配方案


pingmu11.png

第二種樣式樣式: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);
}
pingmu12.png

可以看出顯示效果和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)欄那邊是白色


pingmu13.png
//設(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;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市艰猬,隨后出現(xiàn)的幾起案子横堡,更是在濱河造成了極大的恐慌,老刑警劉巖冠桃,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件命贴,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡食听,警方通過(guò)查閱死者的電腦和手機(jī)胸蛛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)狗准,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蔼囊,“玉大人放接,你說(shuō)我怎么就攤上這事冈欢。” “怎么了迈喉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵仑扑,是天一觀的道長(zhǎng)放航。 經(jīng)常有香客問(wèn)我,道長(zhǎng)嚷量,這世上最難降的妖魔是什么陋桂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蝶溶,結(jié)果婚禮上嗜历,老公的妹妹穿的比我還像新娘。我一直安慰自己身坐,他們只是感情好秸脱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布落包。 她就那樣靜靜地躺著部蛇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪咐蝇。 梳的紋絲不亂的頭發(fā)上涯鲁,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音有序,去河邊找鬼抹腿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛旭寿,可吹牛的內(nèi)容都是我干的警绩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼盅称,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼肩祥!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起缩膝,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤混狠,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后疾层,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體将饺,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年痛黎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了予弧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡湖饱,死狀恐怖掖蛤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情琉历,我是刑警寧澤坠七,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布水醋,位于F島的核電站,受9級(jí)特大地震影響彪置,放射性物質(zhì)發(fā)生泄漏拄踪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一拳魁、第九天 我趴在偏房一處隱蔽的房頂上張望惶桐。 院中可真熱鬧,春花似錦潘懊、人聲如沸姚糊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)救恨。三九已至,卻和暖如春释树,著一層夾襖步出監(jiān)牢的瞬間肠槽,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工奢啥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秸仙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓桩盲,卻偏偏與公主長(zhǎng)得像寂纪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子赌结,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 本文參考自: Google的官方權(quán)威適配文檔 郭霖:Android官方提供的支持不同屏幕大小的全部方法 Storm...
    M悇芐冋憶閱讀 12,818評(píng)論 5 56
  • 更新:由于該適配方案越來(lái)越多人使用捞蛋,也有很多人遇到不太理解的問(wèn)題。所以為了大家更好的使用姑曙,我將文章很多內(nèi)容更新了襟交,...
    代碼小王子閱讀 1,306評(píng)論 0 2
  • 在我們學(xué)習(xí)如何進(jìn)行屏幕適配之前,我們需要先了解下為什么Android需要進(jìn)行屏幕適配伤靠。 由于Android系統(tǒng)的開(kāi)...
    知青的葉閱讀 1,504評(píng)論 0 2
  • 更新:由于該適配方案越來(lái)越多人使用捣域,也有很多人遇到不太理解的問(wèn)題。所以為了大家更好的使用宴合,我將文章很多內(nèi)容更新了焕梅,...
    wildma閱讀 230,309評(píng)論 355 1,136
  • Climbing WormTime Limit: 2000/1000 MS (Java/Others) Me...
    xcpooo閱讀 257評(píng)論 0 0