Home | History | Annotate | Download | only in widget
      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