Home | History | Annotate | Download | only in menu
      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      */
     61     public float getTitleViewAlphaDeselected() {
     62         return mTitleViewAlphaDeselected;
     63     }
     64 
     65     /**
     66      * Returns the scale value of the title view when it's selected.
     67      */
     68     public float getTitleViewScaleSelected() {
     69         return mTitleViewScaleSelected;
     70     }
     71 
     72     public MenuRowView(Context context) {
     73         this(context, null);
     74     }
     75 
     76     public MenuRowView(Context context, AttributeSet attrs) {
     77         this(context, attrs, 0);
     78     }
     79 
     80     public MenuRowView(Context context, AttributeSet attrs, int defStyleAttr) {
     81         this(context, attrs, defStyleAttr, 0);
     82     }
     83 
     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
     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 
    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 
    128     abstract protected int getContentsViewId();
    129 
    130     /**
    131      * Returns the title view.
    132      */
    133     public final TextView getTitleView() {
    134         return mTitleView;
    135     }
    136 
    137     /**
    138      * Returns the contents view.
    139      */
    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      */
    151     public void initialize(@MenuShowReason int reason) {
    152         mLastFocusView = null;
    153     }
    154 
    155     protected Menu getMenu() {
    156         return mRow == null ? null : mRow.getMenu();
    157     }
    158 
    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
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    242     public int getPreferredContentsHeight() {
    243         return mRow.getHeight();
    244     }
    245 }
    246