Android M Launcher3屏幕適配

前言

我們知道Launcher3在不同的設(shè)備上,都能夠很好的適配屏幕,包括桌面圖標(biāo)大小、字體大小、桌面及應(yīng)用列表的行列數(shù)等。那么它是怎么做到的呢?

LauncherAppState初始化

要想知道Launcher3是如何做到屏幕適配的,我們首先從Launcher的初始化開始分析。我們知道Launcher的初始化是從Launcher.java的onCreate方法開始的,我們先看下onCreate最先做了什么事情。

    super.onCreate(savedInstanceState);
    LauncherAppState.setApplicationContext(getApplicationContext());
    LauncherAppState app = LauncherAppState.getInstance();
    
    ...

我們看到Launcher在onCreate方法開始首先初始化LauncherAppState,這一初始化過程主要做了兩件事情,1)設(shè)置應(yīng)用上下文;2)獲取LauncherAppState的單例對(duì)象。在獲取取LauncherAppState的單例對(duì)象過程中,如果LauncherAppState的單例對(duì)象不存在,則會(huì)初始化一個(gè)。在LauncherAppState的構(gòu)造方法中有一系列的初始化。

private LauncherAppState() {
    if (sContext == null) {
        throw new IllegalStateException("LauncherAppState inited before app context set");
    }

    Log.v(Launcher.TAG, "LauncherAppState inited");

    if (sContext.getResources().getBoolean(R.bool.debug_memory_enabled)) {
        MemoryTracker.startTrackingMe(sContext, "L");
    }

    mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
    mIconCache = new IconCache(sContext, mInvariantDeviceProfile);
    mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);

    mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
    mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));
    mModel = new LauncherModel(this, mIconCache, mAppFilter);

    LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);

    // Register intent receivers
    IntentFilter filter = new IntentFilter();
    filter.addAction(Intent.ACTION_LOCALE_CHANGED);
    filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
    // For handling managed profiles
    filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
    filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);

    sContext.registerReceiver(mModel, filter);
    UserManagerCompat.getInstance(sContext).enableAndResetCache();
    }

在上面代碼中我們看到有mInvariantDeviceProfile的初始化,接下來我們分析下它是如何加載一些默認(rèn)屏幕配置的。

InvariantDeviceProfile的初始化

從上面代碼我們可以看到在LauncherAppState的構(gòu)造方法中通過new InvariantDeviceProfile的方式得到一個(gè)mInvariantDeviceProfile對(duì)象,我們來看下這個(gè)new的過程中做了什么事情。先上代碼,我們?cè)僖徊揭徊椒治觥?/p>

InvariantDeviceProfile(Context context) {
        //獲取WindowManager服務(wù)
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        DisplayMetrics dm = new DisplayMetrics();
        display.getMetrics(dm);

        Point smallestSize = new Point();
        Point largestSize = new Point();
        display.getCurrentSizeRange(smallestSize, largestSize);

        // This guarantees that width < height
        //獲取最小的寬高
        minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm);
        minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);

        //通過最小寬高和預(yù)定義的配置文件獲取一個(gè)最接近的配置文件列表
        ArrayList<InvariantDeviceProfile> closestProfiles =
                findClosestDeviceProfiles(minWidthDps, minHeightDps, getPredefinedDeviceProfiles());
        //獲取一個(gè)差值計(jì)算過的配置文件,用于配置圖標(biāo)及圖標(biāo)字體的大小
        InvariantDeviceProfile interpolatedDeviceProfileOut =
                invDistWeightedInterpolate(minWidthDps,  minHeightDps, closestProfiles);

        InvariantDeviceProfile closestProfile = closestProfiles.get(0);
        numRows = closestProfile.numRows;
        numColumns = closestProfile.numColumns;
        numHotseatIcons = closestProfile.numHotseatIcons;
        hotseatAllAppsRank = (int) (numHotseatIcons / 2);
        defaultLayoutId = closestProfile.defaultLayoutId;
        numFolderRows = closestProfile.numFolderRows;
        numFolderColumns = closestProfile.numFolderColumns;
        minAllAppsPredictionColumns = closestProfile.minAllAppsPredictionColumns;

        iconSize = interpolatedDeviceProfileOut.iconSize;
        iconBitmapSize = Utilities.pxFromDp(iconSize, dm);
        iconTextSize = interpolatedDeviceProfileOut.iconTextSize;
        hotseatIconSize = interpolatedDeviceProfileOut.hotseatIconSize;
        fillResIconDpi = getLauncherIconDensity(iconBitmapSize);

        // If the partner customization apk contains any grid overrides, apply them
        // Supported overrides: numRows, numColumns, iconSize
        applyPartnerDeviceProfileOverrides(context, dm);

        Point realSize = new Point();
        display.getRealSize(realSize);
        // The real size never changes. smallSide and largeSide will remain the
        // same in any orientation.
        int smallSide = Math.min(realSize.x, realSize.y);
        int largeSide = Math.max(realSize.x, realSize.y);

        landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize,
                largeSide, smallSide, true /* isLandscape */);
        portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
                smallSide, largeSide, false /* isLandscape */);
    }

通過上述代碼我們可以看到首先通過context.getSystemService獲取一個(gè)WindowMnager實(shí)例,WindowManager是繼承自ViewManager的一個(gè)接口,其實(shí)現(xiàn)類是WindowManagerImpl.java。
WindowManager中存在成員變量Display,可以通過wm.getDefaultDisplay獲取Display實(shí)例,其實(shí)DefaulDisplay就是手機(jī)的默認(rèn)屏幕(其他的屏幕可以是通過HDMI連接的屏幕)。
獲取默認(rèn)屏幕之后,根據(jù)屏幕最小寬高之后,從px轉(zhuǎn)換成Dp,得到minWidthDps,minHeightDps這兩個(gè)變量。然后根據(jù)最小的寬、高及預(yù)定義的配置文件getPredefinedDeviceProfiles(),得到一個(gè)最接近的配置文件列表。我們先來看下這個(gè)預(yù)定義的配置文件是什么?

預(yù)定義配置文件

其實(shí)這個(gè)預(yù)定義的配置文件,是google默認(rèn)添加的一些設(shè)備Launcher的具體顯示參數(shù)。如下所示:

 predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus S",
                296, 491.33f, 4, 4, 4, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));

這條配置信息就是顯示的Nexus S的Launcher顯示的具體參數(shù),依次為:最小的寬296、高491.33,桌面列表圖標(biāo)的行4、列數(shù)4,文件夾中圖標(biāo)的行4、列4,應(yīng)用列表中圖標(biāo)的預(yù)設(shè)行數(shù)4,圖標(biāo)的大小48px,圖標(biāo)下方字體大小13px,dock欄圖標(biāo)個(gè)數(shù)及圖標(biāo)大小,最后一個(gè)xml是默認(rèn)桌面的配置文件。
通過上邊的一條配置信息我們就可以看出它規(guī)定了桌面顯示的個(gè)個(gè)具體信息。有了這些顯示的具體信息我們是如何得到最接近的配置列表的呢?

獲取與當(dāng)前屏幕最近進(jìn)的配置文件

其實(shí)獲取最接近的配置文件很簡(jiǎn)單就是通過最小的寬高和預(yù)制進(jìn)去的寬高做一個(gè)平方和的平方根得到一個(gè)值,根據(jù)這個(gè)值講預(yù)制的列表做一個(gè)從小到大的排序。

//平方和的平方根運(yùn)算
float dist(float x0, float y0, float x1, float y1) {
        return (float) Math.hypot(x1 - x0, y1 - y0);
}

//通過排序得到一個(gè)最近配置列表,最接近的排在最前邊
ArrayList<InvariantDeviceProfile> findClosestDeviceProfiles(
            final float width, final float height, ArrayList<InvariantDeviceProfile> points) {

        // Sort the profiles by their closeness to the dimensions
        ArrayList<InvariantDeviceProfile> pointsByNearness = points;
        Collections.sort(pointsByNearness, new Comparator<InvariantDeviceProfile>() {
            public int compare(InvariantDeviceProfile a, InvariantDeviceProfile b) {
                return (int) (dist(width, height, a.minWidthDps, a.minHeightDps)
                        - dist(width, height, b.minWidthDps, b.minHeightDps));
            }
        });

        return pointsByNearness;
    }

配置當(dāng)前屏幕的顯示參數(shù)

得到最接近的配置列表(closestProfiles.get(0);)之后,設(shè)置如下參數(shù):

        numRows = closestProfile.numRows;
        numColumns = closestProfile.numColumns;
        numHotseatIcons = closestProfile.numHotseatIcons;
        hotseatAllAppsRank = (int) (numHotseatIcons / 2);
        defaultLayoutId = closestProfile.defaultLayoutId;
        numFolderRows = closestProfile.numFolderRows;
        numFolderColumns = closestProfile.numFolderColumns;
        minAllAppsPredictionColumns = closestProfile.minAllAppsPredictionColumns;

通過上邊的代碼我們發(fā)現(xiàn)并不是通過closestProfile設(shè)置圖標(biāo)及圖標(biāo)字體的大小。而是另外的配置文件interpolatedDeviceProfileOut。

        iconSize = interpolatedDeviceProfileOut.iconSize;
        iconBitmapSize = Utilities.pxFromDp(iconSize, dm);
        iconTextSize = interpolatedDeviceProfileOut.iconTextSize;
        hotseatIconSize = interpolatedDeviceProfileOut.hotseatIconSize;
        fillResIconDpi = getLauncherIconDensity(iconBitmapSize);

那么這個(gè)interpolatedDeviceProfileOut是從哪里來的呢?我們回到上邊InvariantDeviceProfile初始化的代碼。我們可以看到InvariantDeviceProfile又是通過一些插值運(yùn)算得到的。

InvariantDeviceProfile invDistWeightedInterpolate(float width, float height,
                ArrayList<InvariantDeviceProfile> points) {
        float weights = 0;

        InvariantDeviceProfile p = points.get(0);
        if (dist(width, height, p.minWidthDps, p.minHeightDps) == 0) {
            return p;
        }

        InvariantDeviceProfile out = new InvariantDeviceProfile();
        for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) {
            p = new InvariantDeviceProfile(points.get(i));
            float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);
            weights += w;
            out.add(p.multiply(w));
        }
        return out.multiply(1.0f/weights);
    }

我們可以簡(jiǎn)單理解為當(dāng)我們的屏幕無法在預(yù)制列表中找到最佳(屏幕完全一致)配置時(shí),為了找到最合適顯示當(dāng)前屏幕的圖標(biāo)的大小,我們需要找到接近的(KNEARESTNEIGHBOR)三個(gè)配置,然后通過一個(gè)加權(quán)運(yùn)算(weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);)得到一個(gè)平均值,以達(dá)到最合適的圖標(biāo)及圖標(biāo)字體大小。至此Launcher已經(jīng)完成了顯示的一系列參數(shù),最后我們?cè)倩氐絚losestProfile.minAllAppsPredictionColumns,這是一個(gè)預(yù)設(shè)的參數(shù),在實(shí)際的顯示中會(huì)根據(jù)圖標(biāo)的大小由launcher動(dòng)態(tài)調(diào)整。

總結(jié)

我們看到Launcher的屏幕適配其實(shí)就是得到一些預(yù)制的配置參數(shù),通過計(jì)算得到一個(gè)最接近的配置文件,通過該配置文件為當(dāng)前屏幕的顯示做一些參數(shù)設(shè)置,已達(dá)到適配的目的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,057評(píng)論 25 709
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,568評(píng)論 19 139
  • Ubuntu的發(fā)音 Ubuntu,源于非洲祖魯人和科薩人的語言,發(fā)作 oo-boon-too 的音。了解發(fā)音是有意...
    螢火蟲de夢(mèng)閱讀 100,678評(píng)論 9 468
  • 滬杭車中 匆匆匆!催催催! 一卷煙,一片山,幾點(diǎn)云影, 一道水,一條橋,一支櫓聲, 一林松,一叢竹,紅葉紛紛: 艷...
    舒嘉儀閱讀 601評(píng)論 0 0
  • 1、初中時(shí)候,一個(gè)平時(shí)特嚴(yán)厲的老師正在黑板抄題,說了一句不好意思抄錯(cuò)行了。正當(dāng)班級(jí)靜靜的時(shí)候,二貨同桌因?yàn)槲也鹊搅?..
    冬天的一把火i閱讀 247評(píng)論 0 0

友情鏈接更多精彩內(nèi)容