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

相关推荐
飞猿_SIR33 分钟前
android定制系统完全解除应用安装限制
android
索迪迈科技1 小时前
影视APP源码 SK影视 安卓+苹果双端APP 反编译详细视频教程+源码
android·影视app源码·sk影视
孔丘闻言1 小时前
python调用mysql
android·python·mysql
萧雾宇3 小时前
Android Compose打造仿现实逼真的烟花特效
android·flutter·kotlin
翻滚丷大头鱼4 小时前
android 性能优化—ANR
android·性能优化
翻滚丷大头鱼4 小时前
android 性能优化—内存泄漏,内存溢出OOM
android·性能优化
拜无忧4 小时前
【教程】flutter常用知识点总结-针对小白
android·flutter·android studio
拜无忧5 小时前
【教程】Flutter 高性能项目架构创建指南:从入门到高性能架构
android·flutter·android studio
用户2018792831675 小时前
故事:公司的 "私人储物柜" 系统(ThreadLocalMap)
android·java
CYRUS_STUDIO5 小时前
如何防止 so 文件被轻松逆向?精准控制符号导出 + JNI 动态注册
android·c++·安全