将从源码层面分析焦点背景色的产生机制,并提供完整的Java解决方案。以下分析基于Android Framework源码(API 30)。
焦点背景色产生机制源码分析
-
焦点状态触发背景色:
java
scss// View.java public void setFocused(boolean focused) { if (focused) { // 焦点状态改变时刷新Drawable状态 refreshDrawableState(); // 请求重绘 invalidate(true); } }
-
Drawable状态更新:
java
csharp// View.java protected void drawableStateChanged() { final Drawable d = mBackground; if (d != null && d.isStateful()) { // 应用当前视图状态到背景Drawable d.setState(getDrawableState()); } } protected int[] getDrawableState() { if (mFocused) { // 将STATE_FOCUSED加入状态集 return StateSet.get(StateSet.VIEW_STATE_FOCUSED); } return StateSet.WILD_CARD; }
-
默认焦点背景实现:
java
csharp// Theme.java public Drawable getDefaultFocusHighlightDrawable() { // 从主题获取默认焦点高亮Drawable return mResources.getDrawable( com.android.internal.R.drawable.default_focused_highlight, mTheme ); }
-
焦点背景绘制流程:
java
scss// View.java public void draw(Canvas canvas) { // 步骤1:绘制背景 drawBackground(canvas); // 步骤2:如果是焦点视图,绘制焦点高亮 if (isFocused()) { drawDefaultFocusHighlight(canvas); } } private void drawDefaultFocusHighlight(Canvas canvas) { if (mDefaultFocusHighlight != null) { // 绘制默认焦点高亮 mDefaultFocusHighlight.draw(canvas); } }
完整解决方案(Java代码)
java
scss
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import android.view.View;
import android.widget.TextView;
import com.google.android.material.tabs.TabLayout;
public class FocusBackgroundFixer {
// 方案1:全局禁用默认焦点高亮(API 26+)
public static void disableDefaultFocusHighlight(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 直接关闭系统级焦点高亮绘制
view.setDefaultFocusHighlightEnabled(false);
}
}
// 方案2:自定义状态Drawable替换(兼容所有版本)
public static void setCustomFocusDrawable(View view) {
// 创建透明状态Drawable
StateListDrawable drawable = new StateListDrawable();
// 焦点状态使用透明Drawable
drawable.addState(
new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.TRANSPARENT)
);
// 默认状态
drawable.addState(
new int[]{},
view.getBackground() // 保留原有背景
);
view.setBackground(drawable);
}
// 方案3:动态拦截焦点背景绘制
public static void interceptFocusDrawing(View view) {
view.addOnLayoutChangeListener((v, left, top, right, bottom,
oldLeft, oldTop, oldRight, oldBottom) -> {
// 清除焦点状态标记
v.setSelected(false);
v.setActivated(false);
// 移除焦点相关Drawable
if (v.getBackground() != null) {
v.getBackground().setState(StateSet.WILD_CARD);
}
});
}
// 针对TextView的特殊处理(跑马灯场景)
public static void fixMarqueeTextView(TextView textView) {
// 保留焦点能力(跑马灯需要焦点)
textView.setFocusable(true);
textView.setFocusableInTouchMode(true);
// 方案1:禁用默认高亮
disableDefaultFocusHighlight(textView);
// 方案2:透明背景覆盖
textView.setBackgroundColor(Color.TRANSPARENT);
// 防止背景重绘
textView.setWillNotDraw(true);
}
// 针对TabLayout的焦点修复
public static void fixTabLayoutFocus(TabLayout tabLayout) {
// 遍历所有Tab
for (int i = 0; i < tabLayout.getTabCount(); i++) {
TabLayout.Tab tab = tabLayout.getTabAt(i);
if (tab != null) {
View tabView = tab.view;
// 禁用默认焦点高亮
disableDefaultFocusHighlight(tabView);
// 自定义焦点处理
tabView.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
// 焦点状态自定义逻辑(无背景变化)
v.setBackgroundResource(android.R.color.transparent);
}
});
// 清除现有焦点状态
tabView.clearFocus();
}
}
// TabLayout整体焦点控制
tabLayout.setFocusable(false);
tabLayout.setFocusableInTouchMode(false);
}
// 应用场景整合
public static void applyLoginScenarioFix(TextView marqueeView, TabLayout tabLayout) {
// 修复悬浮窗TextView
fixMarqueeTextView(marqueeView);
// 修复TabLayout
fixTabLayoutFocus(tabLayout);
// 全局焦点策略
marqueeView.post(() -> {
// 主动转移焦点到安全区域
marqueeView.clearFocus();
tabLayout.clearFocus();
});
}
}
关键实现原理
-
系统机制覆盖:
java
arduino// 直接关闭焦点高亮绘制管线 view.setDefaultFocusHighlightEnabled(false);
- 该方法会设置
mDefaultFocusHighlight
为null,跳过drawDefaultFocusHighlight()
流程
- 该方法会设置
-
状态Drawable欺骗:
java
arduino// 创建透明焦点状态 drawable.addState(new int[]{android.R.attr.state_focused}, new ColorDrawable(Color.TRANSPARENT));
- 当系统查询焦点状态时,返回透明Drawable
-
焦点事件拦截:
java
scssview.setOnFocusChangeListener((v, hasFocus) -> { if (hasFocus) { // 立即重置背景状态 v.setBackgroundResource(android.R.color.transparent); } });
-
绘制流程优化:
java
arduino// 设置不会触发重绘 textView.setWillNotDraw(true);
- 避免焦点变化触发不必要的重绘
不同场景推荐方案
场景 | 推荐方案 | 原理说明 |
---|---|---|
悬浮窗TextView | fixMarqueeTextView() |
保留焦点能力但禁用视觉反馈 |
TabLayout | fixTabLayoutFocus() |
逐Tab处理避免内部状态冲突 |
密码登录后跳转场景 | applyLoginScenarioFix() |
组合方案+焦点重置 |
低版本兼容(API<26) | setCustomFocusDrawable() |
状态Drawable覆盖方案 |
高级调试技巧
java
java
// 焦点状态跟踪器
view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
// 打印焦点路径
logFocusPath(v);
}
private void logFocusPath(View v) {
StringBuilder path = new StringBuilder();
View current = v;
while (current != null) {
path.insert(0, current.getClass().getSimpleName() +
" [F:" + current.hasFocus() + "] -> ");
current = (current.getParent() instanceof View) ?
(View) current.getParent() : null;
}
Log.d("FocusPath", path.toString());
}
});
这些方案直接从Android焦点机制源码层面解决问题,既保留了必要的焦点功能(如跑马灯滚动),又消除了视觉上的背景色异常。实际应用时应根据具体场景组合使用,并在onResume()
和onWindowFocusChanged()
中触发修复逻辑。