修改需求

1、新增Tile,点击后可以收起下拉状态栏,延时发送广播弹吐司

2、对锁屏界面的布局进行调整

3、对下拉状态栏的月日周时间格式,改为周日月

一、下拉菜单创建流程

1、先看布局图

下拉菜单有两种布局,需要分析两种布局的创建流程。

2、QsFragment的创建

先看status_bar_expanded.xml

 <!-- 主要看这个几个view的布局--><!-- 这个是锁屏界面的View--><includelayout="@layout/keyguard_status_view"android:visibility="gone" /><!-- QS快捷面板的Fragment--><FrameLayoutandroid:id="@+id/qs_frame"android:layout="@layout/qs_panel"android:layout_width="@dimen/qs_panel_width"android:layout_height="match_parent"android:layout_gravity="@integer/notification_panel_layout_gravity"android:clipToPadding="false"android:clipChildren="false"systemui:viewType="com.android.systemui.plugins.qs.QS" /><!-- 通知栏布局--><com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutandroid:id="@+id/notification_stack_scroller"android:layout_marginTop="@dimen/notification_panel_margin_top"android:layout_width="@dimen/notification_panel_width"android:layout_height="match_parent"android:layout_gravity="@integer/notification_panel_layout_gravity"android:layout_marginBottom="@dimen/close_handle_underlap" />

​ 在StatusBar.java中有如下代码,用于加载QSqs_frame的界面控制就被转移到QSFragment,相应的布局也就变成了qs_panel,(从QSFragmentonCreateView中可以看出,返回的是一个布局,而这个布局就是qs_panel

// Set up the quick settings tile panel
final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
if (container != null) {FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,mExtensionController.newExtension(QS.class).withPlugin(QS.class).withDefault(this::createDefaultQSFragment).build());mBrightnessMirrorController = new BrightnessMirrorController(mNotificationShadeWindowView,mNotificationPanelViewController,mNotificationShadeDepthControllerLazy.get(),(visible) -> {mBrightnessMirrorVisible = visible;updateScrimController();});fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {QS qs = (QS) f;if (qs instanceof QSFragment) {mQSPanel = ((QSFragment) qs).getQsPanel();mQSPanel.setBrightnessMirror(mBrightnessMirrorController);/* UNISOC: Bug 1074234, 885650, Super power feature @{ */mFooter = ((QSFragment) qs).getFooter();/* @} */}});
}

转到qs_panel.xml

   <!--这就是快捷面板的容器,布局风格对应下拉通知栏时展开的快捷面板,对应开头第二幅图--><com.android.systemui.qs.QSPanelandroid:id="@+id/quick_settings_panel"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@android:color/transparent"android:focusable="true"android:accessibilityTraversalBefore="@android:id/edit"><include layout="@layout/qs_footer_impl" /><include layout="@layout/qs_media_divider"android:id="@+id/divider"/></com.android.systemui.qs.QSPanel></com.android.systemui.qs.NonInterceptingScrollView><!--这就是快捷面板的容器,布局风格对应下拉通知栏时收起的快捷面板对应开头第一幅图--><include layout="@layout/quick_status_bar_expanded_header" />

​ 在QSFragment中有onCreateViewonViewCreated两个方法先后执行,在onViewCreated方法中有调用setHost(mHost)方法,分析setHost方法

//QSFragment.java
public void setHost(QSTileHost qsh) {mQSPanel.setHost(qsh, mQSCustomizer);mHeader.setQSPanel(mQSPanel);mFooter.setQSPanel(mQSPanel);mQSDetail.setHost(qsh);if (mQSAnimator != null) {mQSAnimator.setHost(qsh);}
}

​ 其中mQSPanel.setHost(qsh, mQSCustomizer)

//QSpanel.java
public void setHost(QSTileHost host, QSCustomizer customizer) {mHost = host;mHost.addCallback(this);setTiles(mHost.getTiles());if (mSecurityFooter != null) {mSecurityFooter.setHostEnvironment(host);}mCustomizePanel = customizer;if (mCustomizePanel != null) {mCustomizePanel.setHost(mHost);}
}

​ 这里的setTiles(mHost.getTiles())就是快捷面板添加的入口,而mHost.getTiles()的数据从QSTileHost.onTuningChanged中获取。

//QSTileHost.java
public void onTuningChanged(String key, String newValue) {if (!TILES_SETTING.equals(key)) {return;}Log.d(TAG, "Recreating tiles");if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);}//声明一个tile的列表用于存储加载的tilefinal List<String> tileSpecs = loadTileSpecs(mContext, newValue);int currentUser = ActivityManager.getCurrentUser();if (currentUser != mCurrentUser) {mUserContext = mContext.createContextAsUser(UserHandle.of(currentUser), 0);if (mAutoTiles != null) {mAutoTiles.changeUser(UserHandle.of(currentUser));}}if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(tile -> {Log.d(TAG, "Destroying tile: " + tile.getKey());mQSLogger.logTileDestroyed(tile.getKey(), "Tile removed");tile.getValue().destroy();});//根据键值对匹配所有的tilefinal LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>();for (String tileSpec : tileSpecs) {QSTile tile = mTiles.get(tileSpec);if (tile != null && (!(tile instanceof CustomTile)|| ((CustomTile) tile).getUser() == currentUser)) {if (tile.isAvailable()) {if (DEBUG) Log.d(TAG, "Adding " + tile);tile.removeCallbacks();if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) {tile.userSwitch(currentUser);}newTiles.put(tileSpec, tile);mQSLogger.logTileAdded(tileSpec);} else {tile.destroy();Log.d(TAG, "Destroying not available tile: " + tileSpec);mQSLogger.logTileDestroyed(tileSpec, "Tile not available");}} else {// This means that the tile is a CustomTile AND the user is different, so let's// destroy itif (tile != null) {tile.destroy();Log.d(TAG, "Destroying tile for wrong user: " + tileSpec);mQSLogger.logTileDestroyed(tileSpec, "Tile for wrong user");}Log.d(TAG, "Creating tile: " + tileSpec);try {tile = createTile(tileSpec);if (tile != null) {tile.setTileSpec(tileSpec);if (tile.isAvailable()) {newTiles.put(tileSpec, tile);mQSLogger.logTileAdded(tileSpec);} else {tile.destroy();Log.d(TAG, "Destroying not available tile: " + tileSpec);mQSLogger.logTileDestroyed(tileSpec, "Tile not available");}}} catch (Throwable t) {Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);}}}mCurrentUser = currentUser;List<String> currentSpecs = new ArrayList<>(mTileSpecs);mTileSpecs.clear();mTileSpecs.addAll(tileSpecs);mTiles.clear();mTiles.putAll(newTiles);if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {// If we didn't manage to create any tiles, set it to empty (default)Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");changeTiles(currentSpecs, loadTileSpecs(mContext, ""));} else {for (int i = 0; i < mCallbacks.size(); i++) {mCallbacks.get(i).onTilesChanged();}}
}//在此方法中可使用add()添加新建的Tile,效果与在config.xml中添加有所不同,在config.xml中添加需要对项目进行整编才有效果,
//但是在此使用add()添加时进入快捷面板的编辑界面时新添加的快捷图标会消失,但是不需要对项目整编就可实现
protected List<String> loadTileSpecs(Context context, String tileList ,boolean is_special_request) {/* @} */final Resources res = context.getResources();if (TextUtils.isEmpty(tileList)) {tileList = res.getString(R.string.quick_settings_tiles);if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);} else {if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);}/* UNISOC: Bug 1074234,885650,Super power feature @{ */if (!is_special_request) {if (UnisocPowerManagerUtil.isSuperPower()) {tileList = res.getString(R.string.quick_settings_tiles_superpower);}}if (!mIsEnableWifiDisplay) {tileList = tileList.replaceAll( ",cast|cast,|cast","");}final ArrayList<String> tiles = new ArrayList<String>();boolean addedDefault = false;Set<String> addedSpecs = new ArraySet<>();for (String tile : tileList.split(",")) {tile = tile.trim();if (tile.isEmpty()) continue;if (tile.equals("default")) {if (!addedDefault) {List<String> defaultSpecs = getDefaultSpecs(context);for (String spec : defaultSpecs) {/* UNISOC: modify for Bug1344100 @{ */if (shouldRemovedInGuestUser(spec)) {continue;}/* @} */if (!addedSpecs.contains(spec)) {tiles.add(spec);addedSpecs.add(spec);}}addedDefault = true;}} else {if (!addedSpecs.contains(tile)) {if (shouldRemovedInGuestUser(tile)) {continue;}tiles.add(tile);addedSpecs.add(tile);}}}return tiles;
}public QSTile createTile(String tileSpec) {if (!mIsEnableWifiDisplay && tileSpec.equals("cast"))return null;if (LocationFeaturesUtils.getInstance(mContext).isLocationDisabled() && tileSpec.equals("location"))return null;if (!BluetoothAdapter.isBluetoothSupported(mContext) && "bt".equals(tileSpec)) {return null;}for (int i = 0; i < mQsFactories.size(); i++) {QSTile t = mQsFactories.get(i).createTile(tileSpec);if (t != null) {return t;}}return null;
}//创建Tile
public QSTile createTile(String tileSpec) {QSTileImpl tile = createTileInternal(tileSpec);//分析这个方法的功能,其中是一个Switch语句,if (tile != null) {tile.handleStale(); // Tile was just created, must be stale.}return tile;
}

mHost就是QSHost对像,它的 getTiles()方法返回的是mTiles.values(),而 mTilesLinkedHashMap mTiles = new LinkedHashMap<>();数据由方法onTuningChanged(String key, String newValue)加载而来,由于onTuningChanged这个函数的创建比QSFragment更早,所以当我们调用mHost.getTiles()时,数据就已经准备好了。在loadTileSpecs函数里面有这一行 String defaultTileList = res.getString(R.string.quick_settings_tiles_default)(在config.xml中)获取我们需要加载在快捷面板上面的设置。根据defaultTileListcreateTile(tileSpec)创建对应的QSTile。因此还需要在QSFactoryImpl中声明自定义添加的快捷选项,至此数据创建完毕,后续就是数据的使用。

​ 再回到QSPanelControllerBasesetTiles(Collection tiles, boolean collapsedView)方法。可以看见是先清空数据再加载数据,通过addTile(final QSTile tile, boolean collapsedView)方法中调用mView.addTile(r),通过布局对象QSPanel.addTile(QSPanelControllerBase.TileRecord tileRecord))将数据通过mTileLayout.addTile(tileRecord)加载到视图,mTileLayoutQSTileLayout,加载其实现类PagedTileLayout,通过实现类的addTile(TileRecord tile)方法去保存数据,此类中有两个数属性如下

private final ArrayList<TileRecord> mTiles = new ArrayList<>();    //存储Tile数据
private final ArrayList<TilePage> mPages = new ArrayList<>();    //存储页面数据

​ 由于PagedTileLayout继承自ViewPager,所以其中有一个方法用于确定首页,可以发现这里的index=0,可以知道只有在第一页才会加载指定数量的Tile。

//PagedTileLayout.java
private void distributeTiles() {emptyAndInflateOrRemovePages();final int tileCount = mPages.get(0).maxTiles();if (DEBUG) Log.d(TAG, "Distributing tiles");int index = 0;final int NT = mTiles.size();for (int i = 0; i < NT; i++) {TileRecord tile = mTiles.get(i);if (mPages.get(index).mRecords.size() == tileCount) index++;if (DEBUG) {Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "+ index);}mPages.get(index).addTile(tile);}
}

至此QSTile的加载流程分析结束,下面就是创建一个自定义的QSTile

流程分析参考文档SystemUI之Qs Tile加载流程 - 简书 (jianshu.com)

一、添加自定义Tile并发送延时广播弹出吐司(代码中注释仅文档中是中文)

**1、 首先新建一个CustomTile类继承自QSTileImpl **

public class MyCustomTile extends QSTileImpl<QSTile.BooleanState>{public static final String ACTION = "com.MyCustomTile.MY_BROAD";protected final String TAG = "MyCustomTile";private Context mContext;private boolean isAction = false;private AcceptBroadcast recdiver;//在构造方法中注册广播,也可以在原有的LatencyTester.java文件中统一注册广播@Injectpublic MyCustomTile(QSHost host){super(host);mContext = host.getContext();//防重复注册if (recdiver == null) {recdiver = new AcceptBroadcast();IntentFilter filter = new IntentFilter(ACTION);mContext.registerReceiver(recdiver,filter);}}/** LONGCLICK EVENT* @return* */@Overridepublic Intent getLongClickIntent(){return new Intent(Settings.ACTION_DATE_SETTINGS);}@Overridepublic int getMetricsCategory(){return 0;}@Overridepublic boolean isAvailable(){return true;}@Overridepublic BooleanState newTileState(){return new BooleanState();}//若在点击事件中注册广播且不做判断,会出现每次点击都注册一次,造成严重的内存泄漏@Overrideprotected void handleClick( ){String MSG = mContext.getString(R.string.toast_msg);mHost.collapsePanels();isAction = !isAction;//利用线程的特性实现延时发送广播mHandler.postDelayed(new Runnable() {@Overridepublic void run() {Intent intent = new Intent(ACTION);Bundle bundle = new Bundle();bundle.putString("msg",MSG);intent.putExtras(bundle);Log.d(TAG, "run: Ready to broadcast");mContext.sendBroadcast(intent);Log.d(TAG, "run: End of broadcast transmission +"+intent.getExtras().getString("msg"));}},2000);}@Overrideprotected void handleUpdateState(BooleanState state, Object agr){state.state = isAction ? Tile.STATE_ACTIVE: Tile.STATE_INACTIVE;state.icon = ResourceIcon.get(R.drawable.ic_custom_tile);state.label = mContext.getString(R.string.custom_tile);}@Overridepublic CharSequence getTileLabel(){return getState().label;}static class AcceptBroadcast extends BroadcastReceiver{protected final String TAG = "AcceptBroadcast";@Overridepublic void onReceive(Context context, Intent intent) {if (intent != null && intent.getAction().equals(ACTION)) ;{Log.d(TAG, "onReceive: Accept Broadcast");Bundle extras = intent.getExtras();String MSG = extras.getString("msg");Log.d(TAG, MSG);Toast.makeText(context,MSG,Toast.LENGTH_SHORT).show();}}}//在管理器中注销已经注册的广播,防止资源占用@Overrideprotected void handleDestroy() {super.handleDestroy();mContext.unregisterReceiver(recdiver);}
}

2、在QSFactoryImpl中注册自定义的快捷图标一共有5步

  1. 导包
  2. 声明Provider变量
  3. 在构造方法中注入Provider
  4. QSTileImpl中装载自定义的Tile

具体的写法可以参照原有Tile的写法。

3、在QSTileHost中的loadTileSpecs方法中通过add( )添加自定义的Tile.

至此添加一个自定义快捷方式添加完成。

4、实现效果


点击2秒后会发送广播并弹出一个来自广播接收器的吐司

二、将锁屏界面的日期移到时间上面
同样使用AS自带的uiautomatorviewer查看锁屏界面的布局

可以发现日期所在的布局文件id,此时就可以定位到锁屏界面的布局文件keyguar_status_view.xml

进而找到被包含的layoutkeyguard_clock_switch.xml

从获取的锁屏布局可以分析出这里的include标签就是日期的所在位置,至此分析完成。只需依据需求调整位置即可

android:layout_above="@id/xxx"  --将控件置于指定ID控件之上
android:layout_below="@id/xxx"  --将控件置于指定ID控件之下
android:layout_alignParentTop="true"  --将当前控件和父控件的顶部对齐

实现效果如上图所示。

三、更改状态栏中时间的显示格式

获取到显示日期的id之后再OpenGrok search网站上搜索相关文件,可以定位到显示日期的控件是一个自定义的DateView
跳转到DateView后分析其中的实现方法发现主要的实现日期格式的方法如下:

protected void updateClock() {if (mDateFormat == null) {final Locale l = Locale.getDefault();DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l);//TINNO BEGIN, add by hao.tang2@tinno.com for KFCBBNHYUPEA-7if (SystemProperties.getBoolean("ro.feature.date_format", false)) {String dateFormat = Settings.System.getString(getContext().getContentResolver(),Settings.System.DATE_FORMAT);if (!TextUtils.isEmpty(dateFormat)) {format = new SimpleDateFormat(dateFormat);}}//TINNO ENDformat.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);mDateFormat = format;}mCurrentTime.setTime(System.currentTimeMillis());//        final String text = mDateFormat.format(mCurrentTime);//add   2022.08.19 startSimpleDateFormat simpleDateFormat = new SimpleDateFormat(getContext().getString(R.string.date_format));final String text = simpleDateFormat.format(mCurrentTime);//add  2022.08.19 endif (!text.equals(mLastText)) {//add by shaowen.chen for mut.if (SystemProperties.get("ro.product.brand").equals("Multilaser")) {final String replaceStr = text.replaceAll("\\.","");setText(replaceStr);mLastText = replaceStr;} else {setText(text);mLastText = text;}}
}

只需添加一个格式化日期的代码即可更改日期的显示格式。实现效果如上图所示。

日期格式

格式 示例
dd/MM/yyyy 06/03/2007
dd-MMM-yyyy 06-Mar-2007
MM/dd/yyyy 03/06/2007
MMM dd, yyyy Mar 06, 2007
MMMMM dd, yyyy March 06, 2007
yyyy.MM.dd 2007.06.03
yyyy/MM/dd 2007/06/03
yyyy-MM-dd 2007-06-03

日期格式清单

格式 示例
dd-MM-yyyy HH’h’mm 06-03-2007 13h44
dd-MM-yyyy HH’h’MM’min’ 06-03-2007 13h44min
dd-MMM-yyyy HH:mm 06-Mar-2007 13:44
dd/MM/yyyy HH’h’mm 06/03/2007 13h44
dd/MM/yyyy HH:mm 06/03/2007 13:44
dd/MM/yyyy hh:mm a zzz 06/03/2007 01:44 PM PST
dd/MM/yyyy HH:mm zzz 06/03/2007 13:44 PST
dd/MM/yyyy HH:mm:ss 06/03/2007 13:44:25
dd/MM/yyyy hh:mm:ss a zzz 06/03/2007 01:44:25 PM PST
dd.MM.yyyy HH:mm:ss 06.03.2007 13:44:25
MM/dd/yyyy HH:mm 03/06/2007 13:44
MM/dd/yyyy hh:mm a zzz 03/06/2007 01:44 PM PST
MM/dd/yyyy HH:mm zzz 03/06/2007 13:44 PST
MM/dd/yyyy HH:mm:ss 03/06/2007 13:44:25
MM/dd/yyyy hh:mm:ss a zzz 03/06/2007 01:44:25 PM PST
MMMMM dd, yyyy hh:mm a zzz March 06, 2007 01:44 PM PST
yyyy-MM-dd HH.mm 2007-03-06 13.44
yyyy-MM-dd hh:mm a zzz 2007-03-06 01:44 PM PST
yyyy.MM.dd hh:mm a zzz 2007.03.06 01:44 PM PST
yyyy/MM/dd hh:mm a zzz 2007/03/06 01:44 PM PST
yyyy/MM/dd HH:mm zzz 2007/03/06 13:44 PST

从 A 到 Z 和从 a 到 z 的字母是解释的字符。所有其他字符都是复制的字符。

虽然从 A 到 Z 和从 a 到 z 的所有字母都是解释的字符,但并非所有字母都分配了解释。

虽然所有信件从 下面的段落描述格式中的字母的解释。

以下图表显示用于设置部分日期的字母的解释。字母重复的次数会影响其设置日期的一部分的方式。另外,大写和小写字母具有不同的解释。

字母序列 描述 示例
d 一个月中一位或两位数的日期 1 - 31
dd 一个月中两位数的日期 01 - 31
DDD 一年中三位数的日期 001 - 366
EEE 一周中缩写的日期 Mon - Sun
EEEE 一周中日期的全称 Monday - Sunday
M 一位或两位数的月份 1 - 12
MM 两位数的月份 01 - 12
MMM 三个字母的月份缩写 Jan - Dec
MMMMM 月份的全称 January - December
y 一位或两位数的年份 0 - 99
yy 两位数的年份 00 - 99
yyyy 四位数的年份 1999、2000、2010

以下图表显示用于设置部分时间的字母的解释。

字母的重复次数会影响设置时间的一部分的方式,大写和小写字母具有不同的解释。

如果仅具有日期值的格式中包含字母用于设置其中的时间的格式,那么该值的时间部分将看起来像是午夜。

字母序列 描述 示例
a 表示 AM 或 PM AM 或 PM
h 上午/下午 (1-12) 中一位或两位数的小时数 1 - 12
hh 上午/下午 (01-12) 中两位数的小时数 01 - 12
H 一天 (0-23) 中一位或两位数的小时数 0 - 23
HH 一天 (00-23) 中两位数的小时数 00 - 23
m 一小时中一位或两位数的分钟数 0 - 59
mm 一小时中两位数的分钟数 00 - 59
s 一分钟中一位或两位数的秒数 0 - 59
S 一位、两位或三位数的毫秒数 0 - 999
ss 一分钟中两位数的秒数 00 - 59
SSS 三位数的毫秒数 000 - 999
z 或 zzz 三个字母的时区缩写 EST、CST 等。
Z 相对于 GMT 的时区 -0500、-0600 等。

一天 (0-23) 中一位或两位数的小时数 | 0 - 23 |
| HH | 一天 (00-23) 中两位数的小时数 | 00 - 23 |
| m | 一小时中一位或两位数的分钟数 | 0 - 59 |
| mm | 一小时中两位数的分钟数 | 00 - 59 |
| s | 一分钟中一位或两位数的秒数 | 0 - 59 |
| S | 一位、两位或三位数的毫秒数 | 0 - 999 |
| ss | 一分钟中两位数的秒数 | 00 - 59 |
| SSS | 三位数的毫秒数 | 000 - 999 |
| z 或 zzz | 三个字母的时区缩写 | EST、CST 等。 |
| Z | 相对于 GMT 的时区 | -0500、-0600 等。 |

日期格式参考链接:日期格式 - 简书 (jianshu.com)

SystemUI修改相关推荐

  1. 2020-10-24 车机UI的SystemUI修改

    1.状态栏 Layout / status_bar.xml 音量条修改 layout-sw600dp/volume_dialog_row.xml layout-sw600dp/volume_dialo ...

  2. android设置UI界面背景,Android ROM定制——界面美化基础(framework-res、SystemUI修改)...

    前言:很多童鞋对美化很感兴趣,都想拥有一个完完全全属于自己风格的手机系统!对那些自定义全局背景,1%电量显示,透明下拉菜单...都很感冒! 美化的重点跟难点就在于对"framework-re ...

  3. android system ui修改,【教程】一些有关于SystemUI修改的教程

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 1.状态栏显示运营商 找到这一段 加入 2.状态栏下拉时候万恶的日期显示隐藏 自己对比下,你就懂了,不好表述 3.状态栏时间居中:搜索clock 删除带有 ...

  4. Android ROM定制——界面美化基础

    Android ROM定制--界面美化基础 前言:很多童鞋对美化很感兴趣,都想拥有一个完完全全属于自己风格的手机系统!对那些自定义全局背景,1%电量显示,透明下拉菜单...都很感冒!美化的重点跟难点就 ...

  5. Android ROM定制

    前言:很多童鞋对美化很感兴趣,都想拥有一个完完全全属于自己风格的手机系统!对那些自定义全局背景,1%电量显示,透明下拉菜单...都很感冒!美化的重点跟难点就在于对"framework-res ...

  6. Android 12.0 rom定制专栏系列解读

    一.前言 在从事android系统rom定制化的这几年里,经历了坎坎坷坷,开发过好几种类型的产品,也随着google对android系统的更新加快,也需要跟随上时代的进步,所以需要把平时工作中遇到的问 ...

  7. android 8.1 新增自定义系统服务

    新增一个自定义的系统服务,并尝试在SystemUI中调用自定义service 一.新建相关文件以及定义服务的注册与启动 1.新建aidl文件 到android8.1/frameworks/base/c ...

  8. Android 9.0 rom定制化系列讲解导读

    1.1前言 本专栏主要是作者本人在9.0的系统rom定制化开发中,在 frameworks定制化实战功能系列的解读,把从事几年的frameworks定制化功能的经验的积累总结一下, 开发过平板,广告机 ...

  9. android怎么美化ui,安卓教程第一期最终篇(转)systemui.apk最全修改美化

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 1.status_bar_latest_event.xml的修改: 源代码: xmlns:android="http://schemas.and ...

最新文章

  1. layDate的使用
  2. ABAP--如何创建动态结构的报表
  3. UVA 413|LA 5388|POJ 1492|ZOJ 1338 Up and Down Sequences
  4. KindEditor 给KindEditor赋值
  5. 云栖大会上,阿里巴巴重磅发布前端知识图谱!
  6. 进程共享变量#pragma data_seg用法
  7. mysql开启yum search pt-mysql_Centos使用MySQL工具Percona Toolkit
  8. MySQL常用命令大全
  9. Linux学习总结(45)——Linux服务器出现卡慢的基本解决方法
  10. MFC之添加PNG,JPG图片
  11. 工业自动化控制-组态王1
  12. 商务与经济统计(13版,Python)笔记 01-02章
  13. 机器人操作系统二 ROS2:设计、架构和野外使用 - 机器翻译
  14. 泛微e9隐藏明细表_泛微Ecology权限整理大全,相当全要点
  15. java 发卡器_充值发卡器
  16. 支付宝支付demo运行流程
  17. 最长的指定瑕疵度的元音字串 —— 最优解法(C++实现)
  18. 初步理解pagerank算法
  19. 教师资格证报名显示内部服务器出错,教师资格证考试报名6个“坑”,小心报名失败!...
  20. article标签和aside标签是什么?

热门文章

  1. [Arcpy] 0 Arcpy初识 | Arcpy开发思路
  2. 人机交互-11-往年试卷
  3. Android中的阿里云仓库
  4. 【python 接口开发】如何用python开发自己的接口
  5. 行人重识别通用数据集及介绍
  6. Spring MVC过滤器-超类 GenericFilterBean
  7. eclipse/myeclipse下web项目部署时报没有webroot的错误
  8. 使用Python 爬取视频
  9. NR/5G - Measurement, GAP, SFTD
  10. Maven使用与学习