今日頭條屏幕適配方案

今日頭條屏幕適配方案

原理

1.無論我們?cè)趚ml中使用何種尺寸單位(dp畴栖、sp、pt......),最后在繪制時(shí)都會(huì)給我們轉(zhuǎn)成px
2.我們選定一種尺寸單位(dp八千、sp吗讶、pt ......)作為我們的適配單位,然后篡改這個(gè)單位與px之間的轉(zhuǎn)化比例恋捆,然后在xml中使用選定的單位做適配

原理解析
px值 = dp值 * metrics.density

解釋:當(dāng)前設(shè)備屏幕總像素/ 設(shè)計(jì)圖總寬度(單位為 dp) = density

density 的意思就是 1dp 占當(dāng)前設(shè)備多少像素

常規(guī)的density 在每個(gè)設(shè)備上都是固定的照皆,DPI / 160 = density,屏幕的總 px 寬度 / density = 屏幕的總 dp 寬度

舉個(gè)例子:

設(shè)備 1沸停,屏幕寬度為 1080px膜毁,480DPI,屏幕總 dp 寬度為 1080 / (480 / 160) = 360dp

設(shè)備 2星立,屏幕寬度為 1440爽茴,560DPI,屏幕總 dp 寬度為 1440 / (560 / 160) = 411dp

屏幕的總 dp 寬度在不同的設(shè)備上是會(huì)變化的绰垂,但是我們?cè)诓季种刑顚懙?dp 值卻是固定不變的

這會(huì)導(dǎo)致什么呢室奏?假設(shè)我們布局中有一個(gè) View 的寬度為 100dp,在設(shè)備 1 中 該 View 的寬度占整個(gè)屏幕寬度的 27.8% (100 / 360 = 0.278)
但在設(shè)備 2 中該 View 的寬度就只能占整個(gè)屏幕寬度的 24.3% (100 / 411 = 0.243)劲装,可以看到這個(gè) View 在像素越高的屏幕上胧沫,dp 值雖然沒變,但是與屏幕的實(shí)際比例卻發(fā)生了較大的變化占业,所以肉眼的觀看效果绒怨,會(huì)越來越小,這就導(dǎo)致了傳統(tǒng)的填寫 dp 的屏幕適配方式產(chǎn)生了較大的誤差

這時(shí)我們要想完美適配谦疾,那就必須保證這個(gè) View 在任何分辨率的屏幕上南蹂,與屏幕的比例都是相同的

1.改變每個(gè) View 的 dp 值?不現(xiàn)實(shí)念恍,在每個(gè)設(shè)備上都要通過代碼動(dòng)態(tài)計(jì)算 View 的 dp 值六剥,工作量太大——————pass
2.每個(gè) View 的 dp 值是固定不變的晚顷,那我們只要保證每個(gè)設(shè)備的屏幕總 dp 寬度不變,就能保證每個(gè) View 在所有分辨率的屏幕上與屏幕的比例都保持不變疗疟,從而完成等比例適配该默,并且這個(gè)屏幕總 dp 寬度如果還能保證和設(shè)計(jì)圖的寬度一致的話,那我們?cè)诓季謺r(shí)就可以直接按照設(shè)計(jì)圖上的尺寸填寫 dp 值

屏幕的總 px 寬度 / density = 屏幕的總 dp 寬度   

這個(gè)公式就是把上面公式中的 屏幕的總 dp 寬度 換成 設(shè)計(jì)圖總寬度策彤,原理都是一樣的栓袖,只要 density 根據(jù)不同的設(shè)備進(jìn)行實(shí)時(shí)計(jì)算并作出改變
density是指的手機(jī)的屏幕密度,由系統(tǒng)提供店诗,不同的手機(jī)的density可能不同裹刮;所以我們不能直接使用系統(tǒng)的density,需要篡改density來達(dá)到適配的目的

方案實(shí)現(xiàn)

一般只對(duì)寬度適配必搞,畢竟高度可以滑動(dòng)解決必指!所以方案中給出的是用dp適配寬度

建議

選用dp作為適配單位,給出的理由是項(xiàng)目中大部分都是使用dp做單位
即使適配方案萬一失效了呢恕洲,咋辦塔橡?選用dp至少很大程度可以防止出現(xiàn)頁面顯示不全,顯示效果太差的問題霜第! 侵入性低

優(yōu)點(diǎn)

1.在頁面布局時(shí)不需要額外的代碼和操作
2.侵入性非常低葛家,該方案和項(xiàng)目完全解耦,在項(xiàng)目布局時(shí)不會(huì)依賴哪怕一行該方案的代碼泌类,而且使用的還是 Android 官方的 API癞谒,意味著當(dāng)你遇到什么問題無法解決,想切換為其他屏幕適配方案時(shí)刃榨,基本不需要更改之前的代碼
3.可適配三方庫的控件和系統(tǒng)的控件(不止是 Activity 和 Fragment弹砚,Dialog、Toast 等所有系統(tǒng)控件都可以適配)枢希,由于修改的 density 在整個(gè)項(xiàng)目中是全局
4.不會(huì)有任何性能的損耗

缺點(diǎn)

當(dāng)某個(gè)系統(tǒng)控件或三方庫控件的設(shè)計(jì)圖尺寸和和我們項(xiàng)目自身的設(shè)計(jì)圖尺寸差距非常大時(shí)桌吃,這個(gè)問題就越嚴(yán)重
具體的就是比如第三方庫的設(shè)計(jì)圖是給的640dp的,而自身項(xiàng)目給的是320dp苞轿,當(dāng)一個(gè)View 64dp寬的時(shí)候茅诱,第三方中就是10% ,而在自身項(xiàng)目中就是20% 搬卒,相當(dāng)于差距比較大的時(shí)候瑟俭,就存在問題
處理方式就是針對(duì)不同的activity 去做特殊適配就可以

代碼

首先要在Application初始化,針對(duì)封裝的可以在BaseActivity中初始化契邀,放在onCreate()之前摆寄,也可以單個(gè)activity不適配,看具體注釋

/**
 * 通過修改系統(tǒng)參數(shù)來適配android設(shè)備
 */

public class DensityUtils {
    private static float appDensity;
    private static float appScaledDensity;
    private static DisplayMetrics appDisplayMetrics;
    private static int barHeight;
    public final static String WIDTH = "width";
    public final static String HEIGHT = "height";

    /**
     * 在Application里初始化一下
     * @param application
     */
    public static void setDensity(@NonNull final Application application) {
        //獲取application的DisplayMetrics
        appDisplayMetrics = application.getResources().getDisplayMetrics();
        //獲取狀態(tài)欄高度
        barHeight = getStatusBarHeight(application);

        if (appDensity == 0) {
            //初始化的時(shí)候賦值
            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的話,直接用下面那個(gè)方法就好)
 * 在setContentView()之前設(shè)置
 * @param activity
 */
public static void setDefault(Activity activity) {
    setAppOrientation(activity, WIDTH);
}

/**
 * 此方法用于在某一個(gè)Activity里面更改適配的方向
 * 在setContentView()之前設(shè)置
 * @param activity
 * @param orientation
 */
public static void setOrientation(Activity activity, String orientation) {
    setAppOrientation(activity, orientation);
}

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

    float targetDensity;

    if (orientation.equals("height")) {
        targetDensity = (appDisplayMetrics.heightPixels - barHeight) / 667f;//設(shè)計(jì)圖的高度 單位:dp
    } else {
        targetDensity = appDisplayMetrics.widthPixels / 360f;//設(shè)計(jì)圖的寬度 單位:dp
    }

    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;
}

/**
 * 獲取狀態(tài)欄高度
 *
 * @param context
 * @return
 */
public static int getStatusBarHeight(Context context) {
    int result = 0;
    int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = context.getResources().getDimensionPixelSize(resourceId);
    }
    return result;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市椭迎,隨后出現(xiàn)的幾起案子锐帜,更是在濱河造成了極大的恐慌,老刑警劉巖畜号,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異允瞧,居然都是意外死亡简软,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門述暂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痹升,“玉大人,你說我怎么就攤上這事畦韭√鄱辏” “怎么了?”我有些...
    開封第一講書人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵艺配,是天一觀的道長(zhǎng)察郁。 經(jīng)常有香客問我,道長(zhǎng)转唉,這世上最難降的妖魔是什么皮钠? 我笑而不...
    開封第一講書人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮赠法,結(jié)果婚禮上麦轰,老公的妹妹穿的比我還像新娘。我一直安慰自己砖织,他們只是感情好款侵,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著侧纯,像睡著了一般新锈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上茂蚓,一...
    開封第一講書人閱讀 52,713評(píng)論 1 312
  • 那天壕鹉,我揣著相機(jī)與錄音,去河邊找鬼聋涨。 笑死晾浴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的牍白。 我是一名探鬼主播脊凰,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了狸涌?” 一聲冷哼從身側(cè)響起切省,我...
    開封第一講書人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎帕胆,沒想到半個(gè)月后朝捆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡懒豹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年芙盘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脸秽。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡儒老,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出记餐,到底是詐尸還是另有隱情驮樊,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布片酝,位于F島的核電站囚衔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏钠怯。R本人自食惡果不足惜佳魔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晦炊。 院中可真熱鬧鞠鲜,春花似錦、人聲如沸断国。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽稳衬。三九已至霞捡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間薄疚,已是汗流浹背碧信。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留街夭,地道東北人砰碴。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像板丽,于是被迫代替她去往敵國(guó)和親呈枉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

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