MenuRowView.java revision 95961816a768da387f0b5523cf4363ace2044089
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
17package com.android.tv.menu;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.graphics.Rect;
22import android.support.annotation.NonNull;
23import android.util.AttributeSet;
24import android.util.Log;
25import android.util.TypedValue;
26import android.view.View;
27import android.view.ViewGroup;
28import android.widget.LinearLayout;
29import android.widget.TextView;
30import com.android.tv.R;
31import com.android.tv.menu.Menu.MenuShowReason;
32
33public 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. */
60    public float getTitleViewAlphaDeselected() {
61        return mTitleViewAlphaDeselected;
62    }
63
64    /** Returns the scale value of the title view when it's selected. */
65    public float getTitleViewScaleSelected() {
66        return mTitleViewScaleSelected;
67    }
68
69    public MenuRowView(Context context) {
70        this(context, null);
71    }
72
73    public MenuRowView(Context context, AttributeSet attrs) {
74        this(context, attrs, 0);
75    }
76
77    public MenuRowView(Context context, AttributeSet attrs, int defStyleAttr) {
78        this(context, attrs, defStyleAttr, 0);
79    }
80
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
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
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
125    protected abstract int getContentsViewId();
126
127    /** Returns the title view. */
128    public final TextView getTitleView() {
129        return mTitleView;
130    }
131
132    /** Returns the contents view. */
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     */
144    public void initialize(@MenuShowReason int reason) {
145        mLastFocusView = null;
146    }
147
148    protected Menu getMenu() {
149        return mRow == null ? null : mRow.getMenu();
150    }
151
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
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
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     */
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     */
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. */
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     */
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. */
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. */
229    public int getPreferredContentsHeight() {
230        return mRow.getHeight();
231    }
232}
233