• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.tv.menu;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Rect;
22 import android.support.annotation.NonNull;
23 import android.util.AttributeSet;
24 import android.util.Log;
25 import android.util.TypedValue;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.widget.LinearLayout;
29 import android.widget.TextView;
30 
31 import com.android.tv.R;
32 import com.android.tv.menu.Menu.MenuShowReason;
33 
34 public abstract class MenuRowView extends LinearLayout {
35     private static final String TAG = "MenuRowView";
36     private static final boolean DEBUG = false;
37 
38     /**
39      * For setting ListView visible, and TitleView visible with the selected text size and color
40      * without animation.
41      */
42     public static final int ANIM_NONE_SELECTED = 1;
43     /**
44      * For setting ListView gone, and TitleView visible with the deselected text size and color
45      * without animation.
46      */
47     public static final int ANIM_NONE_DESELECTED = 2;
48     /**
49      * An animation for the selected item list view.
50      */
51     public static final int ANIM_SELECTED = 3;
52     /**
53      * An animation for the deselected item list view.
54      */
55     public static final int ANIM_DESELECTED = 4;
56 
57     private TextView mTitleView;
58     private View mContentsView;
59 
60     private final float mTitleViewAlphaDeselected;
61     private final float mTitleViewScaleSelected;
62 
63     /**
64      * The lastly focused view. It is used to keep the focus while navigating the menu rows and
65      * reset when the menu is popped up.
66      */
67     private View mLastFocusView;
68     private MenuRow mRow;
69 
70     private final OnFocusChangeListener mOnFocusChangeListener = new OnFocusChangeListener() {
71         @Override
72         public void onFocusChange(View v, boolean hasFocus) {
73             onChildFocusChange(v, hasFocus);
74         }
75     };
76 
77     /**
78      * Returns the alpha value of the title view when it's deselected.
79      */
getTitleViewAlphaDeselected()80     public float getTitleViewAlphaDeselected() {
81         return mTitleViewAlphaDeselected;
82     }
83 
84     /**
85      * Returns the scale value of the title view when it's selected.
86      */
getTitleViewScaleSelected()87     public float getTitleViewScaleSelected() {
88         return mTitleViewScaleSelected;
89     }
90 
MenuRowView(Context context)91     public MenuRowView(Context context) {
92         this(context, null);
93     }
94 
MenuRowView(Context context, AttributeSet attrs)95     public MenuRowView(Context context, AttributeSet attrs) {
96         this(context, attrs, 0);
97     }
98 
MenuRowView(Context context, AttributeSet attrs, int defStyleAttr)99     public MenuRowView(Context context, AttributeSet attrs, int defStyleAttr) {
100         this(context, attrs, defStyleAttr, 0);
101     }
102 
MenuRowView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)103     public MenuRowView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
104         super(context, attrs, defStyleAttr, defStyleRes);
105         Resources res = context.getResources();
106         TypedValue outValue = new TypedValue();
107         res.getValue(R.dimen.menu_row_title_alpha_deselected, outValue, true);
108         mTitleViewAlphaDeselected = outValue.getFloat();
109         float textSizeSelected =
110                 res.getDimensionPixelSize(R.dimen.menu_row_title_text_size_selected);
111         float textSizeDeselected =
112                 res.getDimensionPixelSize(R.dimen.menu_row_title_text_size_deselected);
113         mTitleViewScaleSelected = textSizeSelected / textSizeDeselected;
114     }
115 
116     @Override
onFinishInflate()117     protected void onFinishInflate() {
118         super.onFinishInflate();
119         mTitleView = (TextView) findViewById(R.id.title);
120         mContentsView = findViewById(getContentsViewId());
121         if (mContentsView.isFocusable()) {
122             mContentsView.setOnFocusChangeListener(mOnFocusChangeListener);
123         }
124         if (mContentsView instanceof ViewGroup) {
125             setOnFocusChangeListenerToChildren((ViewGroup) mContentsView);
126         }
127         // Make contents view invisible in order that the view participates in the initial layout.
128         // The visibility is set to GONE after the first layout finishes.
129         // If not, we can't see the contents view animation for the first time it is shown.
130         // TODO: Find a better way to resolve this issue.
131         mContentsView.setVisibility(INVISIBLE);
132     }
133 
setOnFocusChangeListenerToChildren(ViewGroup parent)134     private void setOnFocusChangeListenerToChildren(ViewGroup parent) {
135         int childCount = parent.getChildCount();
136         for (int i = 0; i < childCount; ++i) {
137             View child = parent.getChildAt(i);
138             if (child.isFocusable()) {
139                 child.setOnFocusChangeListener(mOnFocusChangeListener);
140             }
141             if (child instanceof ViewGroup) {
142                 setOnFocusChangeListenerToChildren((ViewGroup) child);
143             }
144         }
145     }
146 
getContentsViewId()147     abstract protected int getContentsViewId();
148 
149     /**
150      * Returns the title view.
151      */
getTitleView()152     public final TextView getTitleView() {
153         return mTitleView;
154     }
155 
156     /**
157      * Returns the contents view.
158      */
getContentsView()159     public final View getContentsView() {
160         return mContentsView;
161     }
162 
163     /**
164      * Initialize this view. e.g. Set the initial selection.
165      * This method is called when the main menu is visible.
166      * Subclass of {@link MenuRowView} should override this to set correct mLastFocusView.
167      *
168      * @param reason A reason why this is initialized. See {@link MenuShowReason}
169      */
initialize(@enuShowReason int reason)170     public void initialize(@MenuShowReason int reason) {
171         mLastFocusView = null;
172     }
173 
getMenu()174     protected Menu getMenu() {
175         return mRow == null ? null : mRow.getMenu();
176     }
177 
onBind(MenuRow row)178     public void onBind(MenuRow row) {
179         if (DEBUG) Log.d(TAG, "onBind: row=" + row);
180         mRow = row;
181         mTitleView.setText(row.getTitle());
182     }
183 
184     @Override
onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)185     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
186         // Expand view here so initial focused item can be shown.
187         return getInitialFocusView().requestFocus();
188     }
189 
190     @NonNull
getInitialFocusView()191     private View getInitialFocusView() {
192         if (mLastFocusView == null) {
193             return mContentsView;
194         }
195         return mLastFocusView;
196     }
197 
198     /**
199      * Sets the view which needs to have focus when this row appears.
200      * Subclasses should call this in {@link #initialize} if needed.
201      */
setInitialFocusView(@onNull View v)202     protected void setInitialFocusView(@NonNull View v) {
203         mLastFocusView = v;
204     }
205 
206     /**
207      * Called when the focus of a child view is changed.
208      * The inherited class should override this method instead of calling
209      * {@link android.view.View#setOnFocusChangeListener(android.view.View.OnFocusChangeListener)}.
210      */
onChildFocusChange(View v, boolean hasFocus)211     protected void onChildFocusChange(View v, boolean hasFocus) {
212         if (hasFocus) {
213             mLastFocusView = v;
214         }
215     }
216 
217     /**
218      * Returns the ID of row object bound to this view.
219      */
getRowId()220     public String getRowId() {
221         return mRow == null ? null : mRow.getId();
222     }
223 
224     /**
225      * Called when this row is selected.
226      *
227      * @param showTitle If {@code true}, the title is not hidden immediately after the row is
228      * selected even though hideTitleWhenSelected() is {@code true}.
229      */
onSelected(boolean showTitle)230     public void onSelected(boolean showTitle) {
231         if (mRow.hideTitleWhenSelected() && !showTitle) {
232             // Title view should participate in the layout even though it is not visible.
233             mTitleView.setVisibility(INVISIBLE);
234         } else {
235             mTitleView.setVisibility(VISIBLE);
236             mTitleView.setAlpha(1.0f);
237             mTitleView.setScaleX(mTitleViewScaleSelected);
238             mTitleView.setScaleY(mTitleViewScaleSelected);
239         }
240         // Making the content view visible will cause it to set a focus item
241         // So we store mLastFocusView and reset it
242         View lastFocusView = mLastFocusView;
243         mContentsView.setVisibility(VISIBLE);
244         mLastFocusView = lastFocusView;
245     }
246 
247     /**
248      * Called when this row is deselected.
249      */
onDeselected()250     public void onDeselected() {
251         mTitleView.setVisibility(VISIBLE);
252         mTitleView.setAlpha(mTitleViewAlphaDeselected);
253         mTitleView.setScaleX(1.0f);
254         mTitleView.setScaleY(1.0f);
255         mContentsView.setVisibility(GONE);
256     }
257 
258     /**
259      * Returns the preferred height of the contents view. The top/bottom padding is excluded.
260      */
getPreferredContentsHeight()261     public int getPreferredContentsHeight() {
262         return mRow.getHeight();
263     }
264 }
265