Launcher3 一鍵改變Icon Shape 原理淺析

Launcher3 一鍵改變Icon Shape 原理淺析

在Android O Launcher3 Google 團隊增加了一個新特性,可以在設置里面更改 桌面Icon 形狀,分別可以改為系統默認、方形、方圓形、圓形、淚珠形。
在Android P Launcher3 Google團隊繼續保持這一神奇特性,那么,看上去好高大上神奇的特性是怎樣實現的呢?帶著這個疑問,follow me》》》》》

下面我們基于Android P Launcher3 分析Launcher3 實現基本原理。

一.先看桌面設置中的菜單實現:

源碼位置 Launcher3\src\com\android\launcher3\SettingsActivity.javaPreference iconShapeOverride = findPreference(IconShapeOverride.KEY_PREFERENCE);if (iconShapeOverride != null) {if (IconShapeOverride.isSupported(getActivity())) {IconShapeOverride.handlePreferenceUi((ListPreference) iconShapeOverride);} else {getPreferenceScreen().removePreference(iconShapeOverride);}}

可以看到isSupported方法是是否支持設置圖標形狀的判斷條件。

public static boolean isSupported(Context context) {    if (!Utilities.ATLEAST_OREO) {return false;}// Only supported when developer settings is enabledif (Settings.Global.getInt(context.getContentResolver(),Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 1) {return false;}try {if (getSystemResField().get(null) != Resources.getSystem()) {// Our assumption that mSystem is the system resource is not true.return false;}} catch (Exception e) {// Ignore, not supportedreturn false;}return getConfigResId() != 0;
}

由源碼 可以看出 滿足幾個條件才能看到設置選項

1.判斷系統SDK 版本是否>=26
2.是否打開了開發者選項。如果開發者選項沒打開,就看不到這個菜單。(至于為神馬開發者模式才可以看到待追蹤!!!可能讓廠商在適配此特性吧)
3.大概意思就是獲取不到mSystem,如果獲取不到,說明當前系統存在問題。

二.菜單出現后,我們選擇其中一種形狀來設置:

<string-array translatable="false" name="icon_shape_override_paths_values"><item></item><item>M50,0L100,0 100,100 0,100 0,0z</item><item>M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z</item><item>M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0</item><item>M50,0A50,50,0,0 1 100,50 L100,85 A15,15,0,0 1 85,100 L50,100 A50,50,0,0 1 50,0z</item>
</string-array><string-array translatable="false" name="icon_shape_override_paths_names"><!-- Option to not change the icon shape on home screen. [CHAR LIMIT=50] --><item>@string/icon_shape_system_default</item><item>Square</item><item>Squircle</item><item>Circle</item><item>Teardrop</item>
</string-array>

發現每個Item對應一個path 矢量圖的string值。

private static class PreferenceChangeHandler implements OnPreferenceChangeListener {    private final Context mContext;private PreferenceChangeHandler(Context context) {mContext = context;}@Overridepublic boolean onPreferenceChange(Preference preference, Object o) {String newValue = (String) o;if (!getAppliedValue(mContext).equals(newValue)) {// Value has changedProgressDialog.show(mContext,null /* title */,mContext.getString(R.string.icon_shape_override_progress),true /* indeterminate */,false /* cancelable */);new LooperExecuter(LauncherModel.getWorkerLooper()).execute(new OverrideApplyHandler(mContext, newValue));}return false;}
}
private static class OverrideApplyHandler implements Runnable {    private final Context mContext;private final String mValue;private OverrideApplyHandler(Context context, String value) {mContext = context;mValue = value;}@Overridepublic void run() {// Synchronously write the preference.prefs(mContext).edit().putString(KEY_PREFERENCE, mValue).commit();// Clear the icon cache.LauncherAppState.getInstance(mContext).getIconCache().clear();// Wait for ittry {Thread.sleep(PROCESS_KILL_DELAY_MS);} catch (Exception e) {Log.e(TAG, "Error waiting", e);}// Schedule an alarm before we kill ourself.Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME).setPackage(mContext.getPackageName()).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);PendingIntent pi = PendingIntent.getActivity(mContext, RESTART_REQUEST_CODE,homeIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);mContext.getSystemService(AlarmManager.class).setExact(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 50, pi);// Kill processandroid.os.Process.killProcess(android.os.Process.myPid());}
}

設置的時候執行上面代碼,主要將設置的保存到本地,清除圖標緩存,然后kill Launcher process 重啟launcher。

三.怎樣通過矢量圖工作的:

源碼位置 :Launcher3\src\com\android\launcher3\graphics\IconShapeOverride.java

IconShapeOverride.apply(getContext());private static int getConfigResId() {    return Resources.getSystem().getIdentifier("config_icon_mask", "string", "android");
}

public static void apply(Context context) {    if (!Utilities.isAtLeastO()) {return;}String path = getAppliedValue(context);if (TextUtils.isEmpty(path)) {return;}if (!isSupported(context)) {return;}// magictry {Resources override =new ResourcesOverride(Resources.getSystem(), getConfigResId(), path);getSystemResField().set(null, override);} catch (Exception e) {Log.e(TAG, "Unable to override icon shape", e);// revert value.prefs(context).edit().remove(KEY_PREFERENCE).apply();}
}

其中ResourcesOverride是繼承了Resources,并且重寫了getString方法。

private static class ResourcesOverride extends Resources {   private final int mOverrideId;private final String mOverrideValue;@SuppressWarnings("deprecated")public ResourcesOverride(Resources parent, int overrideId, String overrideValue) {super(parent.getAssets(), parent.getDisplayMetrics(), parent.getConfiguration());mOverrideId = overrideId;mOverrideValue = overrideValue;}@NonNull@Overridepublic String getString(int id) throws NotFoundException {if (id == mOverrideId) {return mOverrideValue;}return super.getString(id);}
}

在根據源碼看下getSystemResField方法:

private static Field getSystemResField() throws Exception {
Field staticField = Resources.class.getDeclaredField("mSystem");
staticField.setAccessible(true);
return staticField;
}

這個方法是反射系統Resources中mSystem變量。

小結:

從Launcher 源代碼可以看出大概的意思就是Launcher中將Resources 的mSystem設置成了ResourcesOverride對象,
也就是說Resources的getSystem方法獲取的是我們重寫的ResourcesOverride,當調用getString方法的時候,走的也是重寫的方法。getString方法里面判斷了如果string id 是config_icon_mask這個的時候,返回我們傳入的mOverrideValue,這個mOverrideValue就是用戶選擇的圖標形狀值。

2.pmg.jpg

追蹤下 AdaptiveIconDrawable的構造方法:

/**
* The one constructor to rule them all. This is called by all public
* constructors to set the state and initialize local properties.
*/

AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) {mLayerState = createConstantState(state, res);if (sMask == null) {sMask = PathParser.createPathFromPathData(Resources.getSystem().getString(R.string.config_icon_mask));}mMask = PathParser.createPathFromPathData(Resources.getSystem().getString(R.string.config_icon_mask));mMaskMatrix = new Matrix();mCanvas = new Canvas();mTransparentRegion = new Region();
}

此方法的Resources.getSystem().getString(R.string.config_icon_mask),通過getString方法,如果id是config_icon_mask,則返回的是mOverrideValue,mOverrideValue就是上面5種里面的一種。

四.Launcher是如何獲取應用圖標的:?

public Drawable getFullResIcon(LauncherActivityInfo info) {
return mIconProvider.getIcon(info, mIconDpi);
}public Drawable getIcon(LauncherActivityInfo info, int iconDpi) {return info.getIcon(iconDpi);
}

最終調用到LauncherActivityInfo的getIcon方法

/**
* Returns the icon for this activity, without any badging for the profile

 * @param density The preferred density of the icon, zero for default density. Use* density DPI values from {@link DisplayMetrics}.* @see #getBadgedIcon(int)* @see DisplayMetrics* @return The drawable associated with the activity.*/public Drawable getIcon(int density) {// TODO: Go through LauncherAppsServicefinal int iconRes = mActivityInfo.getIconResource();Drawable icon = null;// Get the preferred density icon from the app's resourcesif (density != 0 && iconRes != 0) {try {final Resources resources= mPm.getResourcesForApplication(mActivityInfo.applicationInfo);icon = resources.getDrawableForDensity(iconRes, density);} catch (NameNotFoundException | Resources.NotFoundException exc) {}}// Get the default density iconif (icon == null) {icon = mActivityInfo.loadIcon(mPm);}return icon;
}

通過以上步驟可以看出,Launcher獲取應用圖標的時候時候,如果該應用是支持AdaptiveIcon的話,返回的圖標就是根據形狀裁剪出來的AdaptiveIconDrawable,Launcher從系統拿到的圖標已經是想要的形狀圖標了。
這就是在把launcher進程kill掉,重啟 launcher 重新獲取加載就是被裁減過的Icon形狀了

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/207579.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/207579.shtml
英文地址,請注明出處:http://en.pswp.cn/news/207579.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

python中的下劃線

本文介紹了Python中單下劃線和雙下劃線的5種表現形式&#xff0c;以及一些使用方法。其中有一些含義僅僅是依照約定&#xff0c;被視作是對程序員的提示&#xff0c;而有一些含義是由Python解釋器嚴格執行的。 單前導下劃線&#xff1a;_var單末尾下劃線&#xff1a;var_雙前導…

PHP介紹及安裝

一、PHP語言介紹 1. PHP是一種用于創建動態交互性網站的服務器端腳本語言。PHP文件通常包含HTML標簽和一些PHP腳本代碼,這些PHP代碼可以放置在文檔的任意位置。 2. PHP文件是什么 PHP文件是一種包含有效的HTML、JavaScript代碼和PHP代碼的文件。PHP代碼在服務器上執行,并將…

《網絡彈性法案》協議達成,歐盟立法進一步臨近實施

歐盟的《網絡彈性法案》規定了所有硬件和軟件的強制性網絡安全要求 《網絡彈性法案》&#xff08;CRA&#xff09;是歐洲議會和歐洲理事會就即將實施的重要立法達成的政治協議。該法案于 2022 年 9 月由歐洲委員會首次提出&#xff0c;旨在提高數字產品的網絡安全&#xff0c;造…

離高薪測試你可能只差這個理解:python 內存管理機制

近期有小伙伴跟我反饋 &#xff0c;面試有遇到面試官問 python 內存管理機制相關的問題&#xff0c;因為之前沒有特地的去了解過&#xff0c;所以不知道怎么回答。 所以今天就專門寫了這篇 python 內存管理機制的文章&#xff0c;來給大家系統的梳理一下內存管理機制的知識點&…

訪問控制技術

訪問控制是在身份認證的基礎上&#xff0c;根據不同身份的用戶對用戶的訪問請求加以限制。身份認證關心的是“你是誰&#xff0c;你是否擁有你所聲明的身份”這個問題&#xff1b;而訪問控制則關心“你能做什么&#xff0c;不能做什么”的問題。 在訪問控制過程中&#xff0c;一…

1.查看表的基本結構,表的詳細結構和修改表名

查看表的基本結構,表的詳細結構和修改表名 1.查看數據表基本結構 有強迫癥或健忘癥的小伙伴們在建好數據庫和表以后&#xff0c;通常會懷疑自己剛才是不是敲錯了&#xff0c;怎么辦&#xff1f;如果不是使用圖形界面是不是就沒法查看啦&#xff1f; 不存在的&#xff0c;這就…

大創項目推薦 醫學大數據分析 - 心血管疾病分析

文章目錄 1 前言1 課題背景2 數據處理3 數據可視化4 最后 1 前言 &#x1f525; 優質競賽項目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于大數據的心血管疾病分析 該項目較為新穎&#xff0c;適合作為競賽課題方向&#xff0c;學長非常推薦&#xff01; &#x1f9…

給Flask加上百度翻譯功能,這樣可以用中文進行搜索了

上一篇博客&#xff1a;Flask之手搓bootstrap翻頁-CSDN博客 里&#xff0c;對 OMDb API - The Open Movie Database 的搜索&#xff0c;只能使用英文&#xff0c;才能搜索出電影信息&#xff0c;如果使用中文&#xff0c;是搜索不到結果的。這里就需要使用翻譯&#xff0c;把中…

剪映最新版的4.9,主要更新的功能(于2023年12月2日發布)

新增“多軌道音頻”功能&#xff1a;用戶可以將多個音頻軌道疊加在一起&#xff0c;并對每個音頻軌道進行單獨的編輯。這使得用戶可以更靈活地控制視頻的音頻效果。新增“音頻調音”功能&#xff1a;用戶可以使用音頻調音功能對視頻的音頻進行調節&#xff0c;包括音量、音調、…

QString::arg()函數用法(數字前補零)

QString中的arg方法類似于 (1)“C中的printf中使用的格式輸出符”和 (2)“C中string的append方法”的結合體。 常用的兩種格式如下&#xff1a; 1. 用于填充字符串中的%1,%2…為給定的參數。 //原型&#xff1a; QString QString::arg(const QString & a1) &#xff08…

多功能智能遙測終端機 5G/4G+北斗多信道 視頻采集傳輸

計訊物聯多功能智能遙測終端機&#xff0c;全網通5G/4G無線通信、弱信號地區北斗通信&#xff0c;多信道自動切換保障通信聯通&#xff0c;豐富網絡接口及行業應用接口&#xff0c;支持水利、環保、工業傳感器、控制終端、智能終端接入&#xff0c;模擬量/數字量/信號量采集&am…

camera2對攝像頭編碼h264

MediaCodec編碼攝像頭數據 前置&#xff1a;保存的一些成員變量 // 攝像頭開啟的 handler private Handler cameraHandler; // Camera session 會話 handler private Handler sessionHandler; //這里是個Context都行 private AppCompatActivity mActivity; // 這個攝像頭所有需…

深入理解 Python 中的 eval 函數

更多資料獲取 &#x1f4da; 個人網站&#xff1a;ipengtao.com eval 是 Python 中一個強大而靈活的函數&#xff0c;它允許將字符串作為代碼執行。然而&#xff0c;由于其潛在的安全風險&#xff0c;使用時需要謹慎。本文將深入探討 eval 函數的各個方面&#xff0c;包括基本…

delphi/python 實現小紅書xhs用戶作品列表和圖片/視頻無水印解析

技術學習&#xff0c;請勿用與非法用途&#xff01;&#xff01;&#xff01; 成品圖用戶作品列表接口 /api/sns/web/v1/user_posted?num30&cursor&user_id642bf0850000000011022c4e&image_scenes http Get方式&#xff0c;請求頭需要帶上x-s x-t簽名驗證筆記明細…

直流負載箱的技術發展趨勢和創新有哪些?

直流負載箱廣泛應用于電子、通信、航空航天等領域&#xff0c;隨著科技的不斷發展&#xff0c;直流負載箱也在不斷創新和改進&#xff0c;直流負載箱在負載電流和電壓的測量方面要求高精度和高穩定性。未來的發展趨勢是提高負載箱的測量精度和穩定性&#xff0c;以滿足更高要求…

記錄一些好的文章

高效編寫可維護代碼&#xff1a; 如何高效編寫可維護代碼&#xff1f; | 菜鳥教程 (runoob.com)

計算平均分并輸出低于平均分的學生成績

從鍵盤上輸入若干&#xff08;<20&#xff09;個學生的成績&#xff0c;統計計算出平均成績&#xff0c;并輸出低于平均分的學生成績&#xff0c;用輸入負數結束輸入。 輸入格式: 在一行中輸入若干&#xff08;<20&#xff09;個學生的實型成績&#xff0c;用輸入負數結…

uniapp 使用 $emit和$on——$on中無法為data中的變量賦值

問題在于this的指向&#xff0c; 解決辦法是使用變量保存$on&#xff0c;其次再為data中的值賦值 以下是具體代碼&#xff1a; 1、html代碼&#xff1a; <view class"form_picker" click"selePositionFun()"><view class""><inp…

Git

第1章 Git 概述 Git 是一個免費的、開源的分布式版本控制系統&#xff0c;可以快速高效地處理從小型到大型的各種項目。 Git 易于學習&#xff0c;占地面積小&#xff0c;性能極快。 它具有廉價的本地庫&#xff0c;方便的暫存區域和多個工作流分支等特性。其性能優于 Subversi…

系統設計之數據庫

為您的項目選擇正確的數據庫是一項復雜的任務。許多數據庫選項都適合不同的用例&#xff0c;很快就會導致決策疲勞。 我們希望這份備忘單提供高級指導&#xff0c;以找到符合您項目需求的正確服務并避免潛在的陷阱。 注意&#xff1a;Google 關于其數據庫用例的文檔有限。盡管…