1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package androidx.leanback.widget; 18 19 import android.content.Context; 20 import android.graphics.Rect; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 import android.util.AttributeSet; 24 import android.util.Log; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.widget.FrameLayout; 28 29 import java.util.ArrayList; 30 31 /** 32 * Saves the focused grandchild position. 33 * Helps add persistent focus feature to various ViewGroups. 34 */ 35 class PersistentFocusWrapper extends FrameLayout { 36 37 private static final String TAG = "PersistentFocusWrapper"; 38 private static final boolean DEBUG = false; 39 40 private int mSelectedPosition = -1; 41 42 /** 43 * By default, focus is persisted when searching vertically 44 * but not horizontally. 45 */ 46 private boolean mPersistFocusVertical = true; 47 48 public PersistentFocusWrapper(Context context, AttributeSet attrs) { 49 super(context, attrs); 50 } 51 52 public PersistentFocusWrapper(Context context, AttributeSet attrs, int defStyle) { 53 super(context, attrs, defStyle); 54 } 55 56 int getGrandChildCount() { 57 ViewGroup wrapper = (ViewGroup) getChildAt(0); 58 return wrapper == null ? 0 : wrapper.getChildCount(); 59 } 60 61 /** 62 * Clears the selected position and clears focus. 63 */ 64 public void clearSelection() { 65 mSelectedPosition = -1; 66 if (hasFocus()) { 67 clearFocus(); 68 } 69 } 70 71 /** 72 * Persist focus when focus search direction is up or down. 73 */ 74 public void persistFocusVertical() { 75 mPersistFocusVertical = true; 76 } 77 78 /** 79 * Persist focus when focus search direction is left or right. 80 */ 81 public void persistFocusHorizontal() { 82 mPersistFocusVertical = false; 83 } 84 85 private boolean shouldPersistFocusFromDirection(int direction) { 86 return ((mPersistFocusVertical && (direction == FOCUS_UP || direction == FOCUS_DOWN)) 87 || (!mPersistFocusVertical 88 && (direction == FOCUS_LEFT || direction == FOCUS_RIGHT))); 89 } 90 91 @Override 92 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 93 if (DEBUG) Log.v(TAG, "addFocusables"); 94 if (hasFocus() || getGrandChildCount() == 0 95 || !shouldPersistFocusFromDirection(direction)) { 96 super.addFocusables(views, direction, focusableMode); 97 } else { 98 // Select a child in requestFocus 99 views.add(this); 100 } 101 } 102 103 @Override 104 public void requestChildFocus(View child, View focused) { 105 super.requestChildFocus(child, focused); 106 View view = focused; 107 while (view != null && view.getParent() != child) { 108 view = (View) view.getParent(); 109 } 110 mSelectedPosition = view == null ? -1 : ((ViewGroup) child).indexOfChild(view); 111 if (DEBUG) { 112 Log.v(TAG, "requestChildFocus focused " + focused + " mSelectedPosition " 113 + mSelectedPosition); 114 } 115 } 116 117 @Override 118 public boolean requestFocus(int direction, Rect previouslyFocusedRect) { 119 if (DEBUG) Log.v(TAG, "requestFocus mSelectedPosition " + mSelectedPosition); 120 ViewGroup wrapper = (ViewGroup) getChildAt(0); 121 if (wrapper != null && mSelectedPosition >= 0 && mSelectedPosition < getGrandChildCount()) { 122 if (wrapper.getChildAt(mSelectedPosition).requestFocus( 123 direction, previouslyFocusedRect)) { 124 return true; 125 } 126 } 127 return super.requestFocus(direction, previouslyFocusedRect); 128 } 129 130 static class SavedState extends View.BaseSavedState { 131 132 int mSelectedPosition; 133 134 SavedState(Parcel in) { 135 super(in); 136 mSelectedPosition = in.readInt(); 137 } 138 139 SavedState(Parcelable superState) { 140 super(superState); 141 } 142 143 @Override 144 public void writeToParcel(Parcel dest, int flags) { 145 super.writeToParcel(dest, flags); 146 dest.writeInt(mSelectedPosition); 147 } 148 149 public static final Parcelable.Creator<SavedState> CREATOR = 150 new Parcelable.Creator<SavedState>() { 151 @Override 152 public SavedState createFromParcel(Parcel in) { 153 return new SavedState(in); 154 } 155 156 @Override 157 public SavedState[] newArray(int size) { 158 return new SavedState[size]; 159 } 160 }; 161 } 162 163 @Override 164 protected Parcelable onSaveInstanceState() { 165 if (DEBUG) Log.v(TAG, "onSaveInstanceState"); 166 SavedState savedState = new SavedState(super.onSaveInstanceState()); 167 savedState.mSelectedPosition = mSelectedPosition; 168 return savedState; 169 } 170 171 @Override 172 protected void onRestoreInstanceState(Parcelable state) { 173 if (!(state instanceof SavedState)) { 174 super.onRestoreInstanceState(state); 175 return; 176 } 177 SavedState savedState = (SavedState) state; 178 mSelectedPosition = ((SavedState) state).mSelectedPosition; 179 if (DEBUG) Log.v(TAG, "onRestoreInstanceState mSelectedPosition " + mSelectedPosition); 180 super.onRestoreInstanceState(savedState.getSuperState()); 181 } 182 } 183