We will analyze the mechanism of generating focus background colors from the source code level and provide a complete Java solution. The following analysis is based on the Android Framework source code (API 30).
1.Focus state triggers background color:
// View.java
public void setFocused(boolean focused) {
if (focused) {
// Refresh Drawable state when the focus state changes
refreshDrawableState();
// Request to redraw
invalidate(true);
}
}2. Drawable status update:
// View.java
protected void drawableStateChanged() {
final Drawable d = mBackground;
if (d != null && d.isStateful()) {
// Apply the current view state to the background Drawable
d.setState(getDrawableState());
}
}
protected int[] getDrawableState() {
if (mFocused) {
// Add State-FOCUS-ED to the state set
return StateSet.get(StateSet.VIEW_STATE_FOCUSED);
}
return StateSet.WILD_CARD;
}3. Default focus background implementation:
// Theme.java
public Drawable getDefaultFocusHighlightDrawable() {
// Get default focus highlight Drawable from theme
return mResources.getDrawable(
com.android.internal.R.drawable.default_focused_highlight,
mTheme
);
}4. Focus background drawing process:
// View.java
public void draw(Canvas canvas) {
// Step 1: Draw the background
drawBackground(canvas);
// Step 2: If it is a focus view, draw a focus highlight
if (isFocused()) {
drawDefaultFocusHighlight(canvas);
}
}
private void drawDefaultFocusHighlight(Canvas canvas) {
if (mDefaultFocusHighlight != null) {
// Draw default focus highlight
mDefaultFocusHighlight.draw(canvas);
}
}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 {
// Option 1: Disable default focus highlighting globally (API 26+)
public static void disableDefaultFocusHighlight(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Directly turn off system level focus highlighting drawing
view.setDefaultFocusHighlightEnabled(false);
}
}
// Option 2: Custom Drawable state replacement (compatible with all versions)
public static void setCustomFocusDrawable(View view) {
// Create a transparent state Drawable
StateListDrawable drawable = new StateListDrawable();
// Use transparent Drawable for focus state
drawable.addState(
new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.TRANSPARENT)
);
// default state
drawable.addState(
new int[]{},
view.getBackground() // Keep the original background
);
view.setBackground(drawable);
}
// Option 3: Dynamic interception of focus background drawing
public static void interceptFocusDrawing(View view) {
view.addOnLayoutChangeListener((v, left, top, right, bottom,
oldLeft, oldTop, oldRight, oldBottom) -> {
// Clear focus status marker
v.setSelected(false);
v.setActivated(false);
// Remove focus related Drawable
if (v.getBackground() != null) {
v.getBackground().setState(StateSet.WILD_CARD);
}
});
}
// Special processing for TextView (ticker scene)
public static void fixMarqueeTextView(TextView textView) {
// Retain focus ability (ticker requires focus)
textView.setFocusable(true);
textView.setFocusableInTouchMode(true);
// Option 1: Disable default highlighting
disableDefaultFocusHighlight(textView);
// Option 2: Transparent Background Coverage
textView.setBackgroundColor(Color.TRANSPARENT);
// Prevent background repainting
textView.setWillNotDraw(true);
}
// Focus repair for TabLayout
public static void fixTabLayoutFocus(TabLayout tabLayout) {
// Traverse all tabs
for (int i = 0; i < tabLayout.getTabCount(); i++) {
TabLayout.Tab tab = tabLayout.getTabAt(i);
if (tab != null) {
View tabView = tab.view;
// Disable default focus highlighting
disableDefaultFocusHighlight(tabView);
// Custom focus processing
tabView.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
// Custom logic for focus state (no background changes)
v.setBackgroundResource(android.R.color.transparent);
}
});
// Clear the existing focus state
tabView.clearFocus();
}
}
// TabLayout overall focus control
tabLayout.setFocusable(false);
tabLayout.setFocusableInTouchMode(false);
}
// Application scenario integration
public static void applyLoginScenarioFix(TextView marqueeView, TabLayout tabLayout) {
// Fix floating window TextView
fixMarqueeTextView(marqueeView);
// Fix TabLayout
fixTabLayoutFocus(tabLayout);
// Global Focus Strategy
marqueeView.post(() -> {
// Proactively shift focus to a safe area
marqueeView.clearFocus();
tabLayout.clearFocus();
});
}
}
1. System mechanism coverage:
// Directly turn off the focus, highlight and draw the pipeline
view.setDefaultFocusHighlightEnabled(false);This method will set mDefault FocusHighlight() to null and skip the drawDefault FocusHighlight() process
2. Drawable state deception:
// Create transparent focus state
drawable.addState(new int[]{android.R.attr.state_focused},
new ColorDrawable(Color.TRANSPARENT));When the system queries the focus status, it returns transparent Drawable
3. Focus event interception:
view.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
// Immediately reset the background status
v.setBackgroundResource(android.R.color.transparent);
}
});4. Drawing process optimization:
// Setting will not trigger redraw
textView.setWillNotDraw(true);Avoid unnecessary redrawing triggered by focus changes
| scene | Recommended Solution | Principle Description |
|---|---|---|
| Suspended Window TextView | fixMarqueeTextView() | Retain focus ability but disable visual feedback |
| TabLayout | fixTabLayoutFocus() | Tab by Tab Processing to Avoid Internal State Conflicts |
| Jump scene after password login | applyLoginScenarioFix() | Combination scheme+focus reset |
| Low version compatibility (API<26) | setCustomFocusDrawable() | Drawable Coverage Scheme for Status |
// Focus state tracker
view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
// Print focus path
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());
}
});These solutions directly solve the problem from the source code level of the Android focus mechanism, retaining necessary focus functions (such as scrolling tickers) while eliminating visual background color anomalies. In practical applications, it should be combined and used according to specific scenarios, and the repair logic should be triggered in onResume() and onWindowFocusChanged().