View焦点态默认背景色的源码分析

将从源码层面分析焦点背景色的产生机制,并提供完整的Java解决方案。以下分析基于Android Framework源码(API 30)。

焦点背景色产生机制源码分析

  1. 焦点状态触发背景色

    java

    scss 复制代码
    // View.java
    public void setFocused(boolean focused) {
        if (focused) {
            // 焦点状态改变时刷新Drawable状态
            refreshDrawableState();
            // 请求重绘
            invalidate(true);
        }
    }
  2. 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;
    }
  3. 默认焦点背景实现

    java

    csharp 复制代码
    // Theme.java
    public Drawable getDefaultFocusHighlightDrawable() {
        // 从主题获取默认焦点高亮Drawable
        return mResources.getDrawable(
            com.android.internal.R.drawable.default_focused_highlight,
            mTheme
        );
    }
  4. 焦点背景绘制流程

    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();
        });
    }
}

关键实现原理

  1. 系统机制覆盖

    java

    arduino 复制代码
    // 直接关闭焦点高亮绘制管线
    view.setDefaultFocusHighlightEnabled(false);
    • 该方法会设置mDefaultFocusHighlight为null,跳过drawDefaultFocusHighlight()流程
  2. 状态Drawable欺骗

    java

    arduino 复制代码
    // 创建透明焦点状态
    drawable.addState(new int[]{android.R.attr.state_focused}, 
                    new ColorDrawable(Color.TRANSPARENT));
    • 当系统查询焦点状态时,返回透明Drawable
  3. 焦点事件拦截

    java

    scss 复制代码
    view.setOnFocusChangeListener((v, hasFocus) -> {
        if (hasFocus) {
            // 立即重置背景状态
            v.setBackgroundResource(android.R.color.transparent);
        }
    });
  4. 绘制流程优化

    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()中触发修复逻辑。

相关推荐
BD_Marathon9 小时前
【MySQL】函数
android·数据库·mysql
西西学代码9 小时前
安卓开发---耳机的按键设置的UI实例
android·ui
maki07713 小时前
虚幻版Pico大空间VR入门教程 05 —— 原点坐标和项目优化技巧整理
android·游戏引擎·vr·虚幻·pico·htc vive·大空间
千里马学框架14 小时前
音频焦点学习之AudioFocusRequest.Builder类剖析
android·面试·智能手机·车载系统·音视频·安卓framework开发·audio
fundroid17 小时前
掌握 Compose 性能优化三步法
android·android jetpack
TeleostNaCl18 小时前
如何在 IDEA 中使用 Proguard 自动混淆 Gradle 编译的Java 项目
android·java·经验分享·kotlin·gradle·intellij-idea
旷野说19 小时前
Android Studio Narwhal 3 特性
android·ide·android studio
maki0771 天前
VR大空间资料 01 —— 常用VR框架对比
android·ue5·游戏引擎·vr·虚幻·pico
xhBruce1 天前
InputReader与InputDispatcher关系 - android-15.0.0_r23
android·ims
领创工作室1 天前
安卓设备分区作用详解-测试机红米K40
android·java·linux