網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
背景
項(xiàng)目中為了適應(yīng)產(chǎn)品形態(tài)需要對(duì)Android系統(tǒng)狀態(tài)欄系統(tǒng)圖標(biāo)以及時(shí)鐘和電池等做客制化,滿足不同用戶群體的視覺(jué)特性,那在定制過(guò)程中需要注意哪些事項(xiàng)?圖標(biāo)icon是否可以任意大小?狀態(tài)欄多顏色模式下圖標(biāo)如何適配?復(fù)雜狀態(tài)圖標(biāo)如何調(diào)整邏輯?
狀態(tài)欄是什么?
首先來(lái)看下?tīng)顟B(tài)欄載體是什么?狀態(tài)欄本質(zhì)其實(shí)就是一個(gè)懸浮窗,在systemui初始化時(shí)創(chuàng)建顯示。SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
protected void inflateStatusBarWindow(Context context) {
mStatusBarWindow = (StatusBarWindowView) mInjectionInflater.injectable(
LayoutInflater.from(context)).inflate(R.layout.super_status_bar, null);
}
由上可知狀態(tài)欄就是使用super_status_bar.xml布局創(chuàng)建的一個(gè)懸浮窗。而這個(gè)布局包含了狀態(tài)欄所有內(nèi)容,應(yīng)用通知,系統(tǒng)圖標(biāo),時(shí)鐘等。其主體內(nèi)容如下
<com.android.systemui.statusbar.phone.StatusBarWindowView ... <FrameLayout android:id="@+id/status_bar_container" android:layout_width="match_parent" android:layout_height="wrap_content" /> ... </com.android.systemui.statusbar.phone.StatusBarWindowView>
其中包含status_bar_container 的framelayout的容器即為狀態(tài)欄的view,在代碼中通過(guò)fragmentmanager替換了了這個(gè)container。
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
...
FragmentHostManager.get(mStatusBarWindow)
.addTagListener(...).getFragmentManager()
.beginTransaction()
.replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
CollapsedStatusBarFragment.TAG)
.commit();
而CollapsedStatusBarFragment的實(shí)現(xiàn)就是加載了status_bar.xml 這個(gè)布局。
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.status_bar, container, false);
}
status_bar.xml 布局內(nèi)容就是顯示出來(lái)的狀態(tài)欄布局。這樣狀態(tài)欄整體布局就比較清晰,包含了應(yīng)用通知,系統(tǒng)圖標(biāo), 時(shí)鐘,電池等。
<com.android.systemui.statusbar.phone.PhoneStatusBarView ... android:layout_height="@dimen/status_bar_height" android:id="@+id/status_bar" ... > ... <LinearLayout android:id="@+id/status_bar_contents" ... <!-- 左側(cè)顯示區(qū)域 整體權(quán)重只占了1--> <FrameLayout android:layout_height="match_parent" android:layout_width="0dp" android:layout_weight="1"> ... <LinearLayout android:id="@+id/status_bar_left_side" ... > <!-- 時(shí)鐘 --> <com.android.systemui.statusbar.policy.Clock android:id="@+id/clock" ... android:textAppearance="@style/TextAppearance.StatusBar.Clock" /> <!-- 應(yīng)用通知icon區(qū)域 --> <com.android.systemui.statusbar.AlphaOptimizedFrameLayout android:id="@+id/notification_icon_area" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:orientation="horizontal" android:clipChildren="false"/> </LinearLayout> </FrameLayout> ... <!-- 中間icon顯示區(qū)域 --> <com.android.systemui.statusbar.AlphaOptimizedFrameLayout android:id="@+id/centered_icon_area" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" android:clipChildren="false" android:gravity="center_horizontal|center_vertical"/> <!-- 系統(tǒng)icon顯示區(qū)域--> <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:orientation="horizontal" android:gravity="center_vertical|end" > <!-- 系統(tǒng)icon實(shí)際顯示布局 --> <include layout="@layout/system_icons" /> </com.android.keyguard.AlphaOptimizedLinearLayout> </LinearLayout> ... </com.android.systemui.statusbar.phone.PhoneStatusBarView>
系統(tǒng)icon區(qū)域 system_icons.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res-auto" android:id="@+id/system_icons" ...> <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons" android:layout_width="0dp" android:layout_weight="1" .../> <com.android.systemui.statusbar.phone.seewo.BatteryImageView android:id="@+id/battery" .../> </LinearLayout>
整個(gè)狀態(tài)欄整體布局示意如下:
其中我們需要定制的從UI設(shè)計(jì)稿中可以看出,是三個(gè)區(qū)域,時(shí)鐘, 系統(tǒng)icon,電池, 應(yīng)用通知在這個(gè)項(xiàng)目中不需要,可以直接去掉通知信息功能,就不會(huì)顯示出來(lái)。clock和battery都是自定義控件,比較好處理。重點(diǎn)看下系統(tǒng)icon實(shí)現(xiàn)。
系統(tǒng)ICON布局
由上客制系統(tǒng)圖標(biāo)區(qū)域包含一個(gè)statusIcons 的容器view,還有battery 顯示view。
其布局也是自定義view, StatusIconContainer.java
<com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons" android:layout_width="0dp" .../>
其實(shí)現(xiàn)是基于AlphaOptimizedLinearLayout布局實(shí)現(xiàn)的一個(gè)自定義布局。AlphaOptimizedLinearLayout是繼承自LinearLayout只是覆蓋了
public boolean hasOverlappingRendering() {
return false;
}
該方法用來(lái)標(biāo)記當(dāng)前view是否存在過(guò)度繪制,存在返回ture,不存在返回false,默認(rèn)返回為true。 在android的View里有透明度的屬性,當(dāng)設(shè)置透明度setAlpha的時(shí)候,android里默認(rèn)會(huì)把當(dāng)前view繪制到offscreen buffer中,然后再顯示出來(lái)。 這個(gè)offscreen buffer 可以理解為一個(gè)臨時(shí)緩沖區(qū),把當(dāng)前View放進(jìn)來(lái)并做透明度的轉(zhuǎn)化,然后在顯示到屏幕上。這個(gè)過(guò)程是消耗資源的,所以應(yīng)該盡量避免這個(gè)過(guò)程。而當(dāng)繼承了hasOverlappingRendering()方法返回false后,android會(huì)自動(dòng)進(jìn)行合理的優(yōu)化,避免使用offscreen buffer。
系統(tǒng)icon繪制流程會(huì)比較多。 先從頂層view StatusIconContainer的繪制來(lái)分析。view的繪制離不開(kāi)三個(gè)步驟,onMeasure, onLayout, onDraw,現(xiàn)在來(lái)一一拆解查看。
StatusIconContainer -- onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 獲取到所有需要展示的view,注意看不可見(jiàn)的,icon處于blocked狀態(tài)的,
// 還有需要忽略的都不會(huì)被加入mMeasureViews中
for (int i = 0; i < count; i++) {
StatusIconDisplayable icon = (StatusIconDisplayable) getChildAt(i);
if (icon.isIconVisible() && !icon.isIconBlocked()
&& !mIgnoredSlots.contains(icon.getSlot())) {
mMeasureViews.add((View) icon);
}
}
int visibleCount = mMeasureViews.size();
// 計(jì)算最大可見(jiàn)的icon數(shù)量,默認(rèn)為7
int maxVisible = visibleCount <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
int totalWidth = mPaddingLeft + mPaddingRight;
boolean trackWidth = true;
int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
mNeedsUnderflow = mShouldRestrictIcons && visibleCount > MAX_ICONS;
for (int i = 0; i < mMeasureViews.size(); i++) {
// Walking backwards
View child = mMeasureViews.get(visibleCount - i - 1);
//測(cè)量每個(gè)childview的寬
measureChild(child, childWidthSpec, heightMeasureSpec);
if (mShouldRestrictIcons) {
// 計(jì)算總的寬度
if (i < maxVisible && trackWidth) {
totalWidth += getViewTotalMeasuredWidth(child);
} else if (trackWidth) {
// 超過(guò)最大可見(jiàn)數(shù)量時(shí) 需要給省略點(diǎn)計(jì)算空間。
totalWidth += mUnderflowWidth;
trackWidth = false;
}
} else {
totalWidth += getViewTotalMeasuredWidth(child);
}
}
// 通過(guò)setMeasuredDimension設(shè)置view的寬高
if (mode == MeasureSpec.EXACTLY) {
...
setMeasuredDimension(width, MeasureSpec.getSize(heightMeasureSpec));
} else {
...
setMeasuredDimension(totalWidth, MeasureSpec.getSize(heightMeasureSpec));
}
}
從上面可以看出來(lái), onMeaure主要時(shí)計(jì)算每個(gè)子view的寬高,并計(jì)算出父view的整的寬度,其中會(huì)給超過(guò)最大數(shù)量的情況下 計(jì)算省略點(diǎn)的寬度,可以視項(xiàng)目情況來(lái)決定這個(gè)省略點(diǎn)的數(shù)量,其可在代碼中通過(guò)常量來(lái)自定義。
StatusIconContainer --?onLayout
protected void onLayout(boolean changed, int l, int t, int r, int b) {
float midY = getHeight() / 2.0f;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
int top = (int) (midY - height / 2.0f);
child.layout(0, top, width, top + height);
}
// 重置每個(gè)view的狀態(tài)。通過(guò)StatusIconState重置狀態(tài)
resetViewStates();
// 重新依據(jù)實(shí)際情況計(jì)算每個(gè)icon的顯示狀態(tài),下面單獨(dú)拎出來(lái)講。
calculateIconTranslations();
// 應(yīng)用view的狀態(tài),包含icon顯示的動(dòng)畫(huà)。
applyIconStates();
}
onLayou常規(guī)是計(jì)算每個(gè)view的寬高,并按預(yù)定的規(guī)則排放,然后計(jì)算每個(gè)view的位置。calculateIconTranslations顯示邏輯會(huì)比較多,單獨(dú)拎出來(lái)講:
private void calculateIconTranslations() {
mLayoutStates.clear();
...
//
for (int i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
StatusIconDisplayable iconView = (StatusIconDisplayable) child;
StatusIconState childState = getViewStateFromChild(child);
if (!iconView.isIconVisible() || iconView.isIconBlocked()
|| mIgnoredSlots.contains(iconView.getSlot())) {
childState.visibleState = STATE_HIDDEN;
if (DEBUG) Log.d(TAG, "skipping child (" + iconView.getSlot() + ") not visible");
continue;
}
childState.visibleState = STATE_ICON;
// 位置顯示的關(guān)鍵點(diǎn), translationX 初始值是整個(gè)view的寬度,這樣計(jì)算每個(gè)view
// 的實(shí)際布局位置
childState.xTranslation = translationX - getViewTotalWidth(child);
mLayoutStates.add(0, childState);
translationX -= getViewTotalWidth(child);
}
// Show either 1-MAX_ICONS icons, or (MAX_ICONS - 1) icons + overflow
int totalVisible = mLayoutStates.size();
int maxVisible = totalVisible <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
mUnderflowStart = 0;
int visible = 0;
int firstUnderflowIndex = -1;
for (int i = totalVisible - 1; i >= 0; i--) {
StatusIconState state = mLayoutStates.get(i);
// Allow room for underflow if we found we need it in onMeasure
// 這里比較關(guān)鍵 從列表中逆序獲取到每個(gè)view的位置,如果view的xTranslation 下雨
// 小于顯示的內(nèi)容就停止,后續(xù)就從這個(gè)index開(kāi)始繪制
if (mNeedsUnderflow && (state.xTranslation < (contentStart + mUnderflowWidth))||
(mShouldRestrictIcons && visible >= maxVisible)) {
firstUnderflowIndex = i;
break;
}
mUnderflowStart = (int) Math.max(contentStart, state.xTranslation - mUnderflowWidth);
visible++;
}
//后續(xù)邏輯就是配置是否顯示icon和顯示多少個(gè)dot
...
}
onLayout邏輯較多,簡(jiǎn)單來(lái)說(shuō)就是通過(guò)每個(gè)子view的xTranslation和整體的view空間,計(jì)算需要顯示多少icon,同時(shí)要給省略點(diǎn)預(yù)留空間。簡(jiǎn)單示意如下??赡艹^(guò)空間的就用dot來(lái)顯示。
StatusIconContainer -- onDraw
這塊沒(méi)有定制處理,只是做了debug的一些信息繪制.
至此系統(tǒng)icon的頂層view分析完成,其主要是通過(guò)子view的狀態(tài)以及父view的空間等情況來(lái)決定是否需要顯示哪些icon,以及顯示省略點(diǎn)符號(hào)。接下來(lái)再看每個(gè)子view的情況。
子view就是顯示狀態(tài)欄上icon,但是其封裝了一層繼承自AnimatedImageView,帶動(dòng)畫(huà)效果的ImageView。
子view的具體實(shí)現(xiàn) StatusBarIconView
SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconView.java
挑其中重點(diǎn)解析。圖片的縮放,怎么把任意圖片的大小適合在狀態(tài)欄顯示。
private void updateIconScaleForSystemIcons() {
float iconHeight = getIconHeight();
if (iconHeight != 0) {
mIconScale = mSystemIconDesiredHeight / iconHeight;
} else {
mIconScale = mSystemIconDefaultScale;
}
}
先獲取到mIconScale需要縮放的比例,mSystemIconDesiredHeight 是配置的全局的system icon的大小。
mSystemIconDesiredHeight = res.getDimension(
com.android.internal.R.dimen.status_bar_system_icon_size);
存在icon的情況下,通過(guò)獲取實(shí)際的icon的大小, 計(jì)算出 mIconScale.
在onDraw的時(shí)候 通過(guò)canvas.scale 把畫(huà)布以icon的中心點(diǎn)根據(jù)mIconScale縮放到system_icon_size. 但是這樣存在一個(gè)問(wèn)題,icon的實(shí)際大小還是原大小,只是顯示小了。其它部分包含動(dòng)畫(huà)就不再細(xì)講。
@Override
protected void onDraw(Canvas canvas) {
if (mIconAppearAmount > 0.0f) {
canvas.save();
canvas.scale(mIconScale * mIconAppearAmount, mIconScale * mIconAppearAmount,
getWidth() / 2, getHeight() / 2);
super.onDraw(canvas);
canvas.restore();
}
...
}
到這里狀態(tài)欄布局以及系統(tǒng)圖標(biāo)的view繪制大體分析完成。 接下來(lái)看icon是怎么控制添加,刪除以及更新的。
狀態(tài)欄圖標(biāo)顯示邏輯控制
狀態(tài)欄圖標(biāo)顯示邏輯是通過(guò) StatusBarIconControllerImpl 這個(gè)類(lèi)來(lái)實(shí)現(xiàn)管理, 在對(duì)象構(gòu)造的時(shí)候默認(rèn)初始化
public StatusBarIconControllerImpl(Context context) {
super(context.getResources().getStringArray(
com.android.internal.R.array.config_statusBarIcons));
...
}
config_statusBarIcons 這個(gè)array中包含了所有支持的icon。如有需要定制圖標(biāo)順序可在這個(gè)列表中對(duì)圖標(biāo)對(duì)應(yīng)的item進(jìn)行調(diào)整。
<string-array name="config_statusBarIcons"> <item><xliff:g id="id">@string/status_bar_alarm_clock</xliff:g></item> ... <item><xliff:g id="id">@string/status_bar_battery</xliff:g></item> <item><xliff:g id="id">@string/status_bar_sensors_off</xliff:g></item> </string-array>
這些 icon字串信息當(dāng)做一個(gè)title信息,保存在mSlots列表中。而Slot中包含StatusBarIconHolder:
public static class Slot {
private final String mName;
private StatusBarIconHolder mHolder;
...
}
public class StatusBarIconHolder {
public static final int TYPE_ICON = 0;
public static final int TYPE_WIFI = 1;
public static final int TYPE_MOBILE = 2;
private StatusBarIcon mIcon;
private WifiIconState mWifiState;
private MobileIconState mMobileState;
...
}
啟動(dòng)mIcon即為顯示的圖標(biāo)資源保存類(lèi)。其中包含了圖標(biāo)顯示狀態(tài),標(biāo)簽信息以及狀態(tài)信息等。將其都保存在mSlogs的列表中,方便管理顯示。
總結(jié)數(shù)據(jù)保存鏈條 Slots--> StatusBarIconHolder --> StatusBarIcon;
關(guān)鍵view管理
在狀態(tài)欄初始化的時(shí)候 CollapsedStatusBarFragment 的view創(chuàng)建中 onViewCreated對(duì)系統(tǒng)icon管理進(jìn)行初始化。
mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
mDarkIconManager.setShouldLog(true);
Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
DarkIconManager 構(gòu)造函數(shù)中傳入了system_icon的容器viewgroup, 負(fù)責(zé)view的增加和刪除。StatusBarIconController管理DarkIconManager. 這樣顯示圖標(biāo)區(qū)域控制部分與顯示部分關(guān)聯(lián)起來(lái)。
圖標(biāo)如何更新?
控制管理的實(shí)現(xiàn)策略類(lèi)都在 PhoneStatusBarPolicy 這個(gè)里面實(shí)現(xiàn)。 具體實(shí)現(xiàn)通過(guò)StatusBarIconControllerImpl類(lèi)實(shí)現(xiàn),可以通過(guò)如下接口更新顯示圖標(biāo)。
mIconController.setIcon(mSlotVolume, volumeIconId, volumeDescription);
mIconController.setIconVisibility(mSlotVolume, volumeVisible);
以音量更新為例。setIcon流程分析。
@Override
public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
// 檢查是否在列表中存在holder,默認(rèn)初始情況下是都沒(méi)有holder的,需要新建
int index = getSlotIndex(slot);
StatusBarIconHolder holder = getIcon(index, 0);
if (holder == null) {
先通過(guò)resoureid和 contentDescription創(chuàng)建一個(gè)StatusBarIcon實(shí)例
StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
Icon.createWithResource(
mContext, resourceId), 0, 0, contentDescription);
// 通過(guò)icon封裝一個(gè)holder。
holder = StatusBarIconHolder.fromIcon(icon);
// 將holder賦值給mSlots
setIcon(index, holder);
} else {
holder.getIcon().icon = Icon.createWithResource(mContext, resourceId);
holder.getIcon().contentDescription = contentDescription;
handleSet(index, holder);
}
}
傳入slot為icon的title, resourceId為資源文件,contentDescription為描述字串。如果判斷為沒(méi)有holder就會(huì)新建一個(gè)holder類(lèi),并傳入mSlots的列表中。
@Override
public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
boolean isNew = getIcon(index, holder.getTag()) == null;
super.setIcon(index, holder);
if (isNew) {
// 通過(guò)tag判斷如果是新的就加到systemicon中
addSystemIcon(index, holder);
} else {
//已經(jīng)存在的直接設(shè)置
handleSet(index, holder);
}
}
private void addSystemIcon(int index, StatusBarIconHolder holder) {
String slot = getSlotName(index);
int viewIndex = getViewIndex(index, holder.getTag());
boolean blocked = mIconBlacklist.contains(slot);
mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, holder));
}
onIconAdded是在DarkIconManager中實(shí)現(xiàn)。
protected void onIconAdded(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
addHolder(index, slot, blocked, holder);
}
protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
switch (holder.getType()) {
case TYPE_ICON:
return addIcon(index, slot, blocked, holder.getIcon());
case TYPE_WIFI:
return addSignalIcon(index, slot, holder.getWifiState());
case TYPE_MOBILE:
return addMobileIcon(index, slot, holder.getMobileState());
}
return null;
}
protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
StatusBarIcon icon) {
StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
view.set(icon); //mGroup 即為狀態(tài)欄系統(tǒng)圖標(biāo)的容器view。這里就完成了view的添加
mGroup.addView(view, index, onCreateLayoutParams());
return view;
}
這樣就通過(guò)setIcon把圖標(biāo)添加到了系統(tǒng)圖標(biāo)區(qū),然后再通過(guò)setIconVisibility顯示出圖標(biāo)。顯示的邏輯和setIcon差不多,只是增加了visible狀態(tài),可以自行分析。更新過(guò)程中有兩個(gè)特殊的圖標(biāo),wifi和數(shù)據(jù)網(wǎng)絡(luò),其狀態(tài)會(huì)包含多個(gè),正常都是只有顯示與否邏輯,所以這里邏輯會(huì)多一些,但是原理一樣的。
至此就完成了整個(gè)系統(tǒng)圖標(biāo)顯示控制分析。
如何定制?
數(shù)量和順序 通過(guò)配置 config_statusBarIcons 增刪自己需要的圖標(biāo)。
狀態(tài)欄大小定制 framework/base/core/res/res/values/dimens.xml
配置項(xiàng) | 說(shuō)明 |
---|---|
status_bar_height_portrait | 狀態(tài)欄高度 |
status_bar_system_icon_intrinsic_size | 系統(tǒng)圖標(biāo)期望大小, 用于icon的縮放,和icon_size設(shè)置一樣大小即可 |
status_bar_system_icon_size | 系統(tǒng)圖標(biāo)大小 |
SystemUI/res/values/dimens.xml
配置項(xiàng) | 說(shuō)明 |
---|---|
status_bar_padding_start | 狀態(tài)欄離左側(cè)空間 |
status_bar_padding_end | 狀態(tài)欄離右側(cè)空間 |
signal_cluster_battery_padding | 系統(tǒng)圖標(biāo)離電池圖標(biāo)距離 |
圖標(biāo)顯示定制 通過(guò)上述分析代碼 setIcon找到對(duì)應(yīng)的icon進(jìn)行替換自己項(xiàng)目的icon, StatusIconContainer中需要修改MAX_DOTS為0,不顯示省略點(diǎn)。再onLayout的時(shí)候需要根據(jù)項(xiàng)目設(shè)置icon的間距, child.layout中增加r值。
顯示邏輯策略都在PhoneStatusBarPhicy中實(shí)現(xiàn),尤其是系統(tǒng)原生沒(méi)有支持的圖標(biāo)邏輯會(huì)定制較多。
注意事項(xiàng):
圖標(biāo)選擇,使用新的svg圖標(biāo)時(shí), 寬高最好和系統(tǒng)system_icon_size設(shè)置為一致,原生邏輯會(huì)把圖標(biāo)縮放到高度和system_icon一致,倒是寬度卻保持了原有圖標(biāo)寬,導(dǎo)致顯示布局不對(duì)。
原文鏈接:https://juejin.cn/post/7157878498525184013
相關(guān)推薦
- 2022-08-11 Python中函數(shù)的創(chuàng)建及調(diào)用_python
- 2022-04-18 前端,服務(wù)端渲染引入第三方依賴window全局對(duì)象的插件,window is not defined
- 2022-08-22 如何使用Python?Matplotlib繪制條形圖_python
- 2022-10-14 m基于高階累積量和信號(hào)子空間的信噪比估計(jì)方法的matlab仿真
- 2022-02-10 el-tree在el-form中的rules校驗(yàn)
- 2022-09-07 python?sklearn?畫(huà)出決策樹(shù)并保存為PDF的實(shí)現(xiàn)過(guò)程_python
- 2022-05-06 Spring Boot 使用 RequiredArgsConstructor 參數(shù) onConstr
- 2022-02-19 SharDingJDBC-4.0.0-RC1按月水平分表
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支