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

相关推荐
4***99741 天前
Kotlin序列处理
android·开发语言·kotlin
t***D2641 天前
Kotlin在服务端开发中的生态建设
android·开发语言·kotlin
玲珑Felone1 天前
flutter 状态管理--InheritedWidget、Provider原理解析
android·flutter·ios
BoomHe1 天前
车载应用配置系统签名
android·android studio
路人甲ing..1 天前
用 Android Studio 自带的模拟 Android Emulator 调试
android·java·ide·ubuntu·kotlin·android studio
路人甲ing..1 天前
Android Studio 模拟器报错 The emulator process for AVD xxxxx has terminated.
android·java·ide·kotlin·android studio
弥巷1 天前
【Android】 View事件分发机制源码分析
android·java
wanna1 天前
安卓自学小笔记第一弹
android·笔记
Kapaseker1 天前
五分钟实战 Compose 展开/收起动画
android·kotlin