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