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

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui