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