一、焦点态默认背景色的源码分析
1. View 状态管理核心逻辑(View.java)
java
// View.java
public void setFocused(boolean focused) {
final boolean previouslyFocused = hasFocus();
if (focused != previouslyFocused) {
mPrivateFlags = (mPrivateFlags & ~PFLAG_FOCUSED) | (focused ? PFLAG_FOCUSED : 0);
// 焦点状态变化时,触发Drawable状态更新
refreshDrawableState();
notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.TYPE_VIEW_FOCUSED);
// ... 其他逻辑 ...
}
}
public void refreshDrawableState() {
final int[] drawableState = getDrawableState();
boolean needRefresh = false;
// 更新背景Drawable的状态
final Drawable bg = mBackground;
if (bg != null && bg.isStateful()) {
needRefresh |= bg.setState(drawableState);
}
// ... 其他Drawable的状态更新 ...
if (needRefresh) {
invalidate(); // 重绘View
}
}
2. StateListDrawable 状态匹配机制(StateListDrawable.java)
java
// StateListDrawable.java
@Override
public boolean setState(int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
int newIndex = selectState(stateSet);
if (newIndex < 0) {
newIndex = mDefaultIndex;
}
if (newIndex != mCurIndex) {
mCurIndex = newIndex;
onStateChange(stateSet);
return true;
}
}
return false;
}
// 核心方法:选择与当前状态匹配的Drawable
protected int selectState(int[] stateSet) {
for (int i = 0; i < mStateSpecs.length; i++) {
int[] stateSpec = mStateSpecs[i];
if (StateSet.stateSetMatches(stateSpec, stateSet)) {
return i; // 找到匹配的状态集合,返回对应Drawable的索引
}
}
return -1; // 未找到匹配项
}
二、移除焦点态背景色的 Java 实现方案
方案 1:为 View 设置不含 focused 状态的自定义背景
java
// 方法1:使用透明背景
Button button = findViewById(R.id.button);
button.setBackgroundColor(Color.TRANSPARENT);
// 方法2:自定义StateListDrawable(不含focused状态)
StateListDrawable stateList = new StateListDrawable();
stateList.addState(new int[]{android.R.attr.state_pressed},
getResources().getDrawable(R.drawable.pressed_bg));
stateList.addState(StateSet.WILD_CARD,
getResources().getDrawable(R.drawable.default_bg));
button.setBackground(stateList);
方案 2:禁用 View 的焦点能力
java
Button button = findViewById(R.id.button);
button.setFocusable(false);
button.setFocusableInTouchMode(false);
方案 3:覆盖主题中的默认焦点背景
java
// 在Activity的onCreate中动态应用主题属性
TypedValue outValue = new TypedValue();
getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
// 创建不含focused状态的替代Drawable
StateListDrawable newDrawable = new StateListDrawable();
newDrawable.addState(new int[]{android.R.attr.state_pressed},
getResources().getDrawable(R.drawable.pressed_bg));
newDrawable.addState(StateSet.WILD_CARD,
getResources().getDrawable(R.drawable.default_bg));
// 应用到当前View
button.setBackground(newDrawable);
方案 4:手动清除 focused 状态的 Drawable(反射实现)
java
public static void removeFocusedStateFromBackground(View view) {
Drawable background = view.getBackground();
if (background instanceof StateListDrawable) {
StateListDrawable stateListDrawable = (StateListDrawable) background;
try {
// 获取StateListDrawable的私有字段mStateList
Field stateListField = StateListDrawable.class.getDeclaredField("mStateList");
stateListField.setAccessible(true);
Object stateList = stateListField.get(stateListDrawable);
// 获取mStateList的类型(通常是一个数组)
Class<?> stateListClass = stateList.getClass();
int length = Array.getLength(stateList);
// 创建新的状态列表,过滤掉包含focused的状态
List<Object> newStateList = new ArrayList<>();
for (int i = 0; i < length; i++) {
Object entry = Array.get(stateList, i);
// 获取每个条目的状态集
Field stateSetField = entry.getClass().getDeclaredField("mStateSet");
stateSetField.setAccessible(true);
int[] stateSet = (int[]) stateSetField.get(entry);
// 检查是否包含focused状态
boolean containsFocused = false;
for (int state : stateSet) {
if (state == android.R.attr.state_focused) {
containsFocused = true;
break;
}
}
// 保留不包含focused的状态
if (!containsFocused) {
newStateList.add(entry);
}
}
// 将新的状态列表设置回StateListDrawable
Object newStateListArray = Array.newInstance(stateListClass.getComponentType(), newStateList.size());
for (int i = 0; i < newStateList.size(); i++) {
Array.set(newStateListArray, i, newStateList.get(i));
}
stateListField.set(stateListDrawable, newStateListArray);
// 触发Drawable重绘
stateListDrawable.invalidateSelf();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 使用示例
Button button = findViewById(R.id.button);
removeFocusedStateFromBackground(button);
三、全局应用方案(自定义主题)
java
// styles.xml
<style name="AppTheme" parent="Theme.MaterialComponents.Light">
<!-- 覆盖默认的可选择背景 -->
<item name="android:selectableItemBackground">@drawable/no_focus_background</item>
<item name="android:selectableItemBackgroundBorderless">@drawable/no_focus_background</item>
</style>
// no_focus_background.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@color/pressed_color"/>
<item android:drawable="@color/default_color"/>
</selector>
四、关键源码总结
-
焦点状态触发流程:
setFocused(true)→refreshDrawableState()→bg.setState(drawableState)→ 匹配focused状态的 Drawable -
背景色来源:
主题默认属性android:selectableItemBackground,通常是包含focused状态的StateListDrawable -
移除核心:
- 确保背景 Drawable 不包含
focused状态定义 - 或禁用 View 的焦点能力
- 或通过反射修改现有 Drawable 的状态定义
- 确保背景 Drawable 不包含