1.替换导航栏图标
导航栏上recent,home,back这三个图标在NavigationBarView.java中加载
frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar.java
private void updateIcons(Configuration oldConfig) {
final boolean orientationChange = oldConfig.orientation != mConfiguration.orientation;
final boolean densityChange = oldConfig.densityDpi != mConfiguration.densityDpi;
final boolean dirChange = oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection();
if (orientationChange || densityChange) {
mDockedIcon = getDrawable(R.drawable.ic_sysbar_docked);
mHomeDefaultIcon = getHomeDrawable();
}
if (densityChange || dirChange) {
mRecentIcon = getDrawable(R.drawable.ic_sysbar_recent); // 加载recent
getCursorLeftButton().updateIcon(mLightIconColor, mDarkIconColor);
getCursorRightButton().updateIcon(mLightIconColor, mDarkIconColor);
mContextualButtonGroup.updateIcons(mLightIconColor, mDarkIconColor);
}
if (orientationChange || densityChange || dirChange) {
mBackIcon = getBackDrawable();
}
}
...
public @DrawableRes int getBackDrawableRes() {
return chooseNavigationIconDrawableRes(R.drawable.ic_sysbar_back,
R.drawable.ic_sysbar_back_quick_step); // 加载back
}
...
public KeyButtonDrawable getHomeDrawable() {
final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI();
KeyButtonDrawable drawable = quickStepEnabled
? getDrawable(R.drawable.ic_sysbar_home_quick_step)
: getDrawable(R.drawable.ic_sysbar_home); // 加载home
orientHomeButton(drawable);
return drawable;
}
所以只要把这三个图标的资源替换以下就可以替换修改图标了。
为了不动旧的图标资源我选择增加新的图标资源然后修改代码中使用的资源id
图片资源放到frameworks/base/packages/SystemUI/res/drawable下
![](https://i-blog.csdnimg.cn/blog_migrate/1c6aca3dec5a4898a134f21487cdd0cb.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9bd25dc7a2ac6ee208c8eeac886a3ec8.png)
bat_dock
![](https://i-blog.csdnimg.cn/blog_migrate/ee36400a40e93f3609dc21b8e3f6802f.png)
bat_back
![](https://i-blog.csdnimg.cn/blog_migrate/ae183c126085017f99a358baecd7b88f.png)
bat_home
然后修改代码加载新的图标
frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar.java
private void updateIcons(Configuration oldConfig) {
final boolean orientationChange = oldConfig.orientation != mConfiguration.orientation;
final boolean densityChange = oldConfig.densityDpi != mConfiguration.densityDpi;
final boolean dirChange = oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection();
if (orientationChange || densityChange) {
mDockedIcon = getDrawable(R.drawable.ic_sysbar_docked);
mHomeDefaultIcon = getHomeDrawable();
}
if (densityChange || dirChange) {
// BAT
mRecentIcon = getDrawable(R.drawable.bat_dock); // 加载recent
getCursorLeftButton().updateIcon(mLightIconColor, mDarkIconColor);
getCursorRightButton().updateIcon(mLightIconColor, mDarkIconColor);
mContextualButtonGroup.updateIcons(mLightIconColor, mDarkIconColor);
}
if (orientationChange || densityChange || dirChange) {
mBackIcon = getBackDrawable();
}
}
...
public @DrawableRes int getBackDrawableRes() {
// BAT
return chooseNavigationIconDrawableRes(R.drawable.bat_back, R.drawable.ic_sysbar_back_quick_step); // 加载back
}
...
public KeyButtonDrawable getHomeDrawable() {
final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI();
// BAT
KeyButtonDrawable drawable = quickStepEnabled
? getDrawable(R.drawable.ic_sysbar_home_quick_step)
: getDrawable(R.drawable.bat_home, R.drawable.bat_home_light); // 加载home
orientHomeButton(drawable);
return drawable;
}
修改完后重新编译应该就能看到导航栏图标的效果了。
但是此时会看到在黑暗模式下或者把状态栏拉下来时图标会变成白色。
这是android为了导航栏图标能适应背景作的功能,在frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java中实现的,大家有兴趣可以详细研究。
但是白色图标显然不符合蝙蝠侠风格,所以我把适应背景作的功能做了一些修改。
让图标始终是黑色,然后在图标背后叠加一个白色阴影。当背景变暗时,白色阴影逐渐显现,使黑色图标不会淹没在黑色的背景里。
白色阴影资源使用黑色图标改为白色偏蓝,然后像素向外扩大一圈,然后高斯模糊一遍。
代码上在KeyButtonDrawable中按造阴影绘制的部分做一个蝙蝠阴影的绘制流程就好了。
frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
class KeyButtonDrawable {
// ... ...
private final Paint mBatLightPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
// ... ...
private KeyButtonDrawable(Drawable d, Drawable batd, ShadowDrawableState state) {
mState = state;
if (batd != null) {
// 传入蝙蝠背景资源,设置mEnableBatLigth标志
mState.mBatBaseHeight = batd.getIntrinsicHeight();
mState.mBatBaseWidth = batd.getIntrinsicWidth();
mState.mBatChangingConfigurations = batd.getChangingConfigurations();
mState.mBatChildState = batd.getConstantState();
mState.mEnableBatLigth = true;
}
if (d != null) {
mState.mBaseHeight = d.getIntrinsicHeight();
mState.mBaseWidth = d.getIntrinsicWidth();
mState.mChangingConfigurations = d.getChangingConfigurations();
mState.mChildState = d.getConstantState();
}
if (canAnimate()) {
mAnimatedDrawable = (AnimatedVectorDrawable) mState.mChildState.newDrawable().mutate();
mAnimatedDrawable.setCallback(mAnimatedDrawableCallback);
setDrawableBounds(mAnimatedDrawable);
}
}
private void regenerateBitmapBatLightCache() {
final int width = getIntrinsicWidth();
final int height = getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
// Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
final Drawable batd = mState.mBatChildState.newDrawable().mutate();
setDrawableBounds(batd);
canvas.save();
if (mState.mHorizontalFlip) {
canvas.scale(-1f, 1f, width * 0.5f, height * 0.5f);
}
batd.draw(canvas);
canvas.restore();
mState.mLastDrawnBatLigth = bitmap;
}
public void draw(Canvas canvas) {
Rect bounds = getBounds();
if (bounds.isEmpty()) {
return;
}
if (mAnimatedDrawable != null) {
mAnimatedDrawable.draw(canvas);
} else {
// If no cache or previous cached bitmap is hardware/software acceleration does not
// match the current canvas on draw then regenerate
boolean hwBitmapChanged = mState.mIsHardwareBitmap != canvas.isHardwareAccelerated();
if (hwBitmapChanged) {
mState.mIsHardwareBitmap = canvas.isHardwareAccelerated();
}
if (mState.mLastDrawnIcon == null || hwBitmapChanged) {
regenerateBitmapIconCache();
}
canvas.save();
canvas.translate(mState.mTranslationX, mState.mTranslationY);
canvas.rotate(mState.mRotateDegrees, getIntrinsicWidth() / 2, getIntrinsicHeight() / 2);
if (mState.mShadowSize > 0) {
if (mState.mLastDrawnShadow == null || hwBitmapChanged) {
regenerateBitmapShadowCache();
}
// Translate (with rotation offset) before drawing the shadow
final float radians = (float) (mState.mRotateDegrees * Math.PI / 180);
final float shadowOffsetX = (float) (Math.sin(radians) * mState.mShadowOffsetY
+ Math.cos(radians) * mState.mShadowOffsetX) - mState.mTranslationX;
final float shadowOffsetY = (float) (Math.cos(radians) * mState.mShadowOffsetY
- Math.sin(radians) * mState.mShadowOffsetX) - mState.mTranslationY;
canvas.drawBitmap(mState.mLastDrawnShadow, shadowOffsetX, shadowOffsetY,
mShadowPaint);
}
// 绘制蝙蝠阴影背景
if (mState.mEnableBatLigth) {
if (mState.mLastDrawnBatLigth == null || hwBitmapChanged) {
regenerateBitmapBatLightCache();
}
// 使用mDarkIntensity决定蝙蝠阴影的不透明度
mBatLightPaint.setAlpha((int)(255 * (1.0f - mState.mDarkIntensity)));
canvas.drawBitmap(mState.mLastDrawnBatLigth, null, bounds, mBatLightPaint);
}
canvas.drawBitmap(mState.mLastDrawnIcon, null, bounds, mIconPaint);
canvas.restore();
}
}
private static class ShadowDrawableState extends ConstantState {
// ... ...
Bitmap mLastDrawnBatLigth; // 蝙蝠阴影背景资源
boolean mEnableBatLigth = false; // 启用蝙蝠阴影背景标志
// ... ...
int mBatChangingConfigurations;
int mBatBaseWidth;
int mBatBaseHeight;
ConstantState mBatChildState;
}
}
效果如下,可以看当黑色的蝙蝠标志下面有白色的阴影
![](https://i-blog.csdnimg.cn/blog_migrate/938fb053e9821c6e1576179dcdcd9749.png)
代码连接https://github.com/1193561652/android_frameworks_base-1.git