Android屏幕適配很麻煩嗎唬涧?不疫赎!太簡單了。碎节。捧搞。

轉(zhuǎn)載請作明出處:http://www.reibang.com/p/4254ea9d1b27

前言

作為一個Android開發(fā)人員,你還在為了適配各種尺寸的屏幕而苦惱嗎狮荔?你還在為了出現(xiàn)一個新的機型而修改著數(shù)不盡的dimens和layout嗎胎撇?你還在為了UI給的奇葩尺寸的設(shè)計圖而絞盡奶汁計算距離嗎?如果你為了這些事情而苦惱殖氏,那么看完這篇文章晚树,希望可以幫你減少開發(fā)時間,減緩生命的流逝速度雅采。爵憎。。


不知道大家有沒有看過前一段時間今日頭條技術(shù)團(tuán)隊發(fā)表的一篇關(guān)于Android屏幕適配的文章:一種極低成本的Android屏幕適配方式婚瓜。沒有看過的朋友可以先看看了解一下再回來宝鼓,可以更好的理解。我是無意中點開的這篇文章巴刻,但是看過之后眼前一亮-------Android屏幕適配要是真的這么簡單愚铡,那些辛辛苦苦沒日沒夜做適配的前輩們是不是死得太慘了。冈涧。茂附。。督弓。营曼。。

不比比太多愚隧,開搞蒂阱!

測試與思考

不得不說今日頭條的大神們的想法真的非常獨到,成本極其低廉狂塘,還特別好用录煤。他們給出的最終方案是這樣的:

private static float sRoncompatDennsity;
private static float sRoncompatScaledDensity;
private void setCustomDensity(@NonNull Activity activity, final @NonNull Application application) {

    //application
    final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();

    if (sRoncompatDennsity == 0) {
        sRoncompatDennsity = appDisplayMetrics.density;
        sRoncompatScaledDensity = appDisplayMetrics.scaledDensity;
        application.registerComponentCallbacks(new ComponentCallbacks() {
            @Override
            public void onConfigurationChanged(Configuration newConfig) {
                if (newConfig != null && newConfig.fontScale > 0) {
                    sRoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                }
            }

            @Override
            public void onLowMemory() {

            }
        });
    }

    //計算寬為360dp 同理可以設(shè)置高為640dp的根據(jù)實際情況
    final float targetDensity = appDisplayMetrics.widthPixels / 360;
    final float targetScaledDensity = targetDensity * (sRoncompatScaledDensity / sRoncompatDennsity);
    final int targetDensityDpi = (int) (targetDensity * 160);

    appDisplayMetrics.density = targetDensity;
    appDisplayMetrics.densityDpi = targetDensityDpi;
    appDisplayMetrics.scaledDensity = targetScaledDensity;

    //activity
    final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();

    activityDisplayMetrics.density = targetDensity;
    activityDisplayMetrics.densityDpi = targetDensityDpi;
    activityDisplayMetrics.scaledDensity = targetScaledDensity;
}

看到這篇文章之后我趕緊就寫了一個demo測試了一下,發(fā)現(xiàn)了一點小問題荞胡。
我們UI給出的設(shè)計圖尺寸為1334*720妈踊,如果我按照寬度作為適配標(biāo)準(zhǔn)的話,按照設(shè)計圖720px的寬度泪漂,屏幕的寬度應(yīng)為360dp廊营,也就是這樣:

final float targetDensity = appDisplayMetrics.widthPixels / 360;

這樣做的話寬度適配的比例是沒有任何問的歪泳,但是我在想,如果某一個頁面需要以高度來做適配(也就是內(nèi)容剛好縱向填充全屏)的話露筒,是不是改成這樣就可以了:

final float targetDensity = appDisplayMetrics.heightPixels / 667呐伞;

但是運行之后發(fā)現(xiàn),高度上的差異很大,運行在不同分辨率和尺寸的手機上,頁面中的每一部分內(nèi)容在縱向上的比例不盡相同翔忽,沒有達(dá)到很好的適配的效果。
思考了許久過后我發(fā)現(xiàn)一個問題:我手邊的測試機的寬度是兩個720和兩個1080癣防,而高度有1280,1440,1780和一個全面屏的2160。Android的開原性導(dǎo)致了Android設(shè)備的尺寸的碎片化太嚴(yán)重掌眠,而通過查看測試機的尺寸參數(shù)會發(fā)現(xiàn)劣砍,如果用這四個手機來測試的話,寬度可以直接整除扇救,而高度不可以(并且我手邊的測試機的寬度也可以整除,如果有寬度沒法整除的手機呢香嗓?)迅腔。但是用今日頭條給出的方法,做除法后結(jié)果會取整靠娱,那會不會是由于用縱向計算出來的density取整影響了精度沧烈,從而導(dǎo)致了效果不盡人意呢?

來像云,繼續(xù)改

問題修復(fù)

發(fā)現(xiàn)上述問題之后我就著手去修改锌雀,將計算結(jié)果取余后在賦值給targetDensity,經(jīng)過反復(fù)的測試與實驗迅诬,我重新修改了targetDensity的計算方法:

float targetDensity = 0;
try {
    Double division = Operation.division(appDisplayMetrics.heightPixels, 667);
    //由于手機的長寬不盡相同,肯定會有除不盡的情況,有失精度,所以在這里把所得結(jié)果做了一個保留兩位小數(shù)的操作
    DecimalFormat df = new DecimalFormat("0.00");
    String s = df.format(division);
    targetDensity = Float.parseFloat(s);
} catch (NumberFormatException e) {
    e.printStackTrace();
}

但是有熱心的網(wǎng)友給我留言腋逆,說如果把系統(tǒng)的語言改成葡萄牙語之后會報異常,頁面完全就是慘不忍睹侈贷。惩歉。。抱著對外國歷史友人的疑問我繼續(xù)去測試俏蛮,發(fā)現(xiàn)了這樣的問題:



仔細(xì)看3虐觥!搏屑!小數(shù)點竟然是中文逗號(目前發(fā)現(xiàn)的語言當(dāng)中争涌,葡語和印尼語是這樣,其他語言未經(jīng)測試)@绷怠A恋妗模软!沒辦法,只能繼續(xù)修改了包警,后來經(jīng)過這位熱心網(wǎng)友提醒撵摆,我將這部分代碼修改成:

float targetDensity = appDisplayMetrics.heightPixels / 667f;

這樣一來就完全沒有問題了,也不需要做保留兩位小數(shù)處理了(發(fā)現(xiàn)問題的朋友們害晦,原諒我學(xué)藝不精特铝。。壹瘟。也感謝這位熱心網(wǎng)友的指正)

繼續(xù)測試后發(fā)現(xiàn)鲫剿,高度上的適配結(jié)果讓人非常滿意〉竟欤可是還有一個問題灵莲,我們一般來說做適配都是以手機的寬度為基準(zhǔn),但是一個app里面避免不了偶爾一兩個頁面是按照高度為基準(zhǔn)(就是內(nèi)容縱向填充全屏的頁面)做適配的殴俱。但是上述方法只能保證一個方向政冻,那我就讓它可以自由的切換適配的基準(zhǔn)方向不就好了。

最終方案

繼續(xù)修改之后我得到了最終的方案线欲,修改過后這個類中的所有內(nèi)容如下:

private static float appDensity;
private static float appScaledDensity;
private static DisplayMetrics appDisplayMetrics;
private static int barHeight;

public static void setDensity(@NonNull Application application) {
   //獲取application的DisplayMetrics
   appDisplayMetrics = application.getResources().getDisplayMetrics();
   //獲取狀態(tài)欄高度
   barHeight = AppUtils.getStatusBarHeight(application);

    if (appDensity == 0) {
        //初始化的時候賦值
        appDensity = appDisplayMetrics.density;
        appScaledDensity = appDisplayMetrics.scaledDensity;

        //添加字體變化的監(jiān)聽
        application.registerComponentCallbacks(new ComponentCallbacks() {
            @Override
            public void onConfigurationChanged(Configuration newConfig) {
                //字體改變后,將appScaledDensity重新賦值
                if (newConfig != null && newConfig.fontScale > 0) {
                    appScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                }
            }

            @Override
            public void onLowMemory() {
            }
        });
    }

    //此方法在BaseActivity中做初始化(如果不封裝BaseActivity的話,直接用下面那個方法就好了)
    public static void setDefault(Activity activity) {
        setAppOrientation(activity, AppUtils.WIDTH);
    }

    //此方法用于在某一個Activity里面更改適配的方向
    public static void setOrientation(Activity activity, String orientation) {
        setAppOrientation(activity, orientation);
    }

    /**
     * targetDensity
     * targetScaledDensity
     * targetDensityDpi
     * 這三個參數(shù)是統(tǒng)一修改過后的值
     * <p>
     * orientation:方向值,傳入width或height
     */
    private static void setAppOrientation(@Nullable Activity activity, String orientation) {

        float targetDensity;

        if (orientation.equals("height")) {
            targetDensity = (appDisplayMetrics.heightPixels - barHeight) / 667f;
        } else {
            targetDensity = appDisplayMetrics.widthPixels / 360f;
        }

        float targetScaledDensity = targetDensity * (appScaledDensity / appDensity);
        int targetDensityDpi = (int) (160 * targetDensity);

        /**
         *
         * 最后在這里將修改過后的值賦給系統(tǒng)參數(shù)
         *
         * 只修改Activity的density值
         */

        DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        activityDisplayMetrics.density = targetDensity;
        activityDisplayMetrics.scaledDensity = targetScaledDensity;
        activityDisplayMetrics.densityDpi = targetDensityDpi;
    }
}

在這個類的初始化方法里面我是默認(rèn)的以寬度來作為基準(zhǔn)(這是在Activity中設(shè)置的方法明场,存在于此Activity下的fragment,dialog和PopupWindow都會受到此效果的影響李丰,也就是說苦锨,在Activity中設(shè)置一次之后,Activity下的其他子View都無需再設(shè)置一次)趴泌。

使用方法

自己創(chuàng)建一個類舟舒,將最終方案里面的代碼復(fù)制粘貼就可以使用了

使用方法:在Application的onCreate()方法中



在BaseActivity中:



如果只是適配一個方向的話,只設(shè)置這兩句就可以了(我在utils里面設(shè)置了默認(rèn)按照寬度適配嗜憔,可以根據(jù)自己的需求修改默認(rèn)的適配方向秃励,見下圖)

若app中有某一個頁面需要縱向適配的話(注意代碼中的注釋):

/**
 *
 * 由于是個人封裝,此方法需要寫在onCreate()中的setContentView()方法前面,切換方向的效果才會生效
 */
@Override
public void setOrientation() {
    Density.setOrientation(this, AppUtils.HEIGHT);
}

最后貼出適配的效果圖(顏色只是為了看的直觀一點。痹筛。莺治。)


橫向

縱向

敲黑板!V愠怼谣旁!

使用此方法,只需要一個dimens文件滋早,一個layout文件就足矣榄审,在xml布局中直接只用dp就可以了(Android P的劉海屏需要單獨適配layout)

結(jié)語

由于是自己寫的demo,還沒有大面積測試(但是今日頭條已經(jīng)啟用了該方案杆麸,現(xiàn)在一個多月過去了還沒聽說出現(xiàn)什么問題搁进,那就說明應(yīng)該還可以)浪感,要是各位看官有條件大范圍測試的話,出現(xiàn)什么問題可以反饋給我饼问,我們可以一起討論該如何修改影兽,共同進(jìn)步。
這是我入行以來寫的第一篇文章莱革,有寫的不好的地方歡迎指正峻堰,以后還會繼續(xù)努力多寫文章的,好的東西需要分享盅视。

這里是github地址捐名,demo里面還有BaseRecyclerViewAdapterHelperQMUI的使用方法

這篇文章如果幫到你的話,點個喜歡再走唄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闹击,一起剝皮案震驚了整個濱河市镶蹋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赏半,老刑警劉巖贺归,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異断箫,居然都是意外死亡牧氮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門瑰枫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丹莲,你說我怎么就攤上這事光坝。” “怎么了甥材?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵盯另,是天一觀的道長。 經(jīng)常有香客問我洲赵,道長鸳惯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任叠萍,我火速辦了婚禮芝发,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘苛谷。我一直安慰自己辅鲸,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布腹殿。 她就那樣靜靜地躺著独悴,像睡著了一般例书。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上刻炒,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天决采,我揣著相機與錄音,去河邊找鬼坟奥。 笑死树瞭,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的筏勒。 我是一名探鬼主播移迫,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼管行!你這毒婦竟也來了厨埋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤捐顷,失蹤者是張志新(化名)和其女友劉穎荡陷,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迅涮,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡废赞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了叮姑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唉地。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖传透,靈堂內(nèi)的尸體忽然破棺而出耘沼,到底是詐尸還是另有隱情,我是刑警寧澤朱盐,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布群嗤,位于F島的核電站,受9級特大地震影響兵琳,放射性物質(zhì)發(fā)生泄漏狂秘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一躯肌、第九天 我趴在偏房一處隱蔽的房頂上張望者春。 院中可真熱鬧,春花似錦清女、人聲如沸碧查。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽忠售。三九已至传惠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間稻扬,已是汗流浹背卦方。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留泰佳,地道東北人盼砍。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像逝她,于是被迫代替她去往敵國和親浇坐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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