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.graphics.Rect;
     21 import android.util.AttributeSet;
     22 import android.util.Log;
     23 import android.view.LayoutInflater;
     24 import android.view.View;
     25 import android.view.ViewParent;
     26 import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
     27 import android.widget.FrameLayout;
     28 
     29 import com.android.tv.menu.Menu.MenuShowReason;
     30 
     31 import java.util.ArrayList;
     32 import java.util.List;
     33 
     34 /**
     35  * A view that represents TV main menu.
     36  */
     37 public class MenuView extends FrameLayout implements IMenuView {
     38     static final String TAG = MenuView.class.getSimpleName();
     39     static final boolean DEBUG = false;
     40 
     41     private final LayoutInflater mLayoutInflater;
     42     private final List<MenuRow> mMenuRows = new ArrayList<>();
     43     private final List<MenuRowView> mMenuRowViews = new ArrayList<>();
     44 
     45     @MenuShowReason private int mShowReason = Menu.REASON_NONE;
     46 
     47     private final MenuLayoutManager mLayoutManager;
     48 
     49     public MenuView(Context context) {
     50         this(context, null, 0);
     51     }
     52 
     53     public MenuView(Context context, AttributeSet attrs) {
     54         this(context, attrs, 0);
     55     }
     56 
     57     public MenuView(Context context, AttributeSet attrs, int defStyle) {
     58         super(context, attrs, defStyle);
     59         mLayoutInflater = LayoutInflater.from(context);
     60         getViewTreeObserver().addOnGlobalFocusChangeListener(new OnGlobalFocusChangeListener() {
     61             @Override
     62             public void onGlobalFocusChanged(View oldFocus, View newFocus) {
     63                 MenuRowView newParent = getParentMenuRowView(newFocus);
     64                 if (newParent != null) {
     65                     if (DEBUG) Log.d(TAG, "Focus changed to " + newParent);
     66                     // When the row is selected, the row view itself has the focus because the row
     67                     // is collapsed. To make the child of the row have the focus, requestFocus()
     68                     // should be called again after the row is expanded. It's done in
     69                     // setSelectedPosition().
     70                     setSelectedPositionSmooth(mMenuRowViews.indexOf(newParent));
     71                 }
     72             }
     73         });
     74         mLayoutManager = new MenuLayoutManager(context, this);
     75     }
     76 
     77     @Override
     78     public void setMenuRows(List<MenuRow> menuRows) {
     79         mMenuRows.clear();
     80         mMenuRows.addAll(menuRows);
     81         for (MenuRow row : menuRows) {
     82             MenuRowView view = createMenuRowView(row);
     83             mMenuRowViews.add(view);
     84             addView(view);
     85         }
     86         mLayoutManager.setMenuRowsAndViews(mMenuRows, mMenuRowViews);
     87     }
     88 
     89     private MenuRowView createMenuRowView(MenuRow row) {
     90         MenuRowView view = (MenuRowView) mLayoutInflater.inflate(row.getLayoutResId(), this, false);
     91         view.onBind(row);
     92         return view;
     93     }
     94 
     95     @Override
     96     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
     97         mLayoutManager.layout(left, top, right, bottom);
     98     }
     99 
    100     @Override
    101     public void onShow(@MenuShowReason int reason, String rowIdToSelect,
    102             final Runnable runnableAfterShow) {
    103         if (DEBUG) {
    104             Log.d(TAG, "onShow(reason=" + reason + ", rowIdToSelect=" + rowIdToSelect + ")");
    105         }
    106         mShowReason = reason;
    107         if (getVisibility() == VISIBLE) {
    108             if (rowIdToSelect != null) {
    109                 int position = getItemPosition(rowIdToSelect);
    110                 if (position >= 0) {
    111                     MenuRowView rowView = mMenuRowViews.get(position);
    112                     rowView.initialize(reason);
    113                     setSelectedPosition(position);
    114                 }
    115             }
    116             return;
    117         }
    118         initializeChildren();
    119         update(true);
    120         int position = getItemPosition(rowIdToSelect);
    121         if (position == -1 || !mMenuRows.get(position).isVisible()) {
    122             // Channels row is always visible.
    123             position = getItemPosition(ChannelsRow.ID);
    124         }
    125         setSelectedPosition(position);
    126         // Change the visibility as late as possible to avoid the unnecessary animation.
    127         setVisibility(VISIBLE);
    128         // Make the selected row have the focus.
    129         requestFocus();
    130         if (runnableAfterShow != null) {
    131             runnableAfterShow.run();
    132         }
    133         mLayoutManager.onMenuShow();
    134     }
    135 
    136     @Override
    137     public void onHide() {
    138         if (getVisibility() == GONE) {
    139             return;
    140         }
    141         mLayoutManager.onMenuHide();
    142         setVisibility(GONE);
    143     }
    144 
    145     @Override
    146     public boolean isVisible() {
    147         return getVisibility() == VISIBLE;
    148     }
    149 
    150     @Override
    151     public boolean update(boolean menuActive) {
    152         if (menuActive) {
    153             for (MenuRow row : mMenuRows) {
    154                 row.update();
    155             }
    156             mLayoutManager.onMenuRowUpdated();
    157             return true;
    158         }
    159         return false;
    160     }
    161 
    162     @Override
    163     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
    164         int selectedPosition = mLayoutManager.getSelectedPosition();
    165         // When the menu shows up, the selected row should have focus.
    166         if (selectedPosition >= 0 && selectedPosition < mMenuRowViews.size()) {
    167             return mMenuRowViews.get(selectedPosition).requestFocus();
    168         }
    169         return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
    170     }
    171 
    172     private void setSelectedPosition(int position) {
    173         mLayoutManager.setSelectedPosition(position);
    174     }
    175 
    176     private void setSelectedPositionSmooth(int position) {
    177         mLayoutManager.setSelectedPositionSmooth(position);
    178     }
    179 
    180     private void initializeChildren() {
    181         for (MenuRowView view : mMenuRowViews) {
    182             view.initialize(mShowReason);
    183         }
    184     }
    185 
    186     private int getItemPosition(String rowIdToSelect) {
    187         if (rowIdToSelect == null) {
    188             return -1;
    189         }
    190         int position = 0;
    191         for (MenuRow item : mMenuRows) {
    192             if (rowIdToSelect.equals(item.getId())) {
    193                 return position;
    194             }
    195             ++position;
    196         }
    197         return -1;
    198     }
    199 
    200     @Override
    201     public View focusSearch(View focused, int direction) {
    202         // The bounds of the views move and overlap with each other during the animation. In this
    203         // situation, the framework can't perform the correct focus navigation. So the menu view
    204         // should search by itself.
    205         if (direction == View.FOCUS_UP) {
    206             View newView = super.focusSearch(focused, direction);
    207             MenuRowView oldfocusedParent = getParentMenuRowView(focused);
    208             MenuRowView newFocusedParent = getParentMenuRowView(newView);
    209             int selectedPosition = mLayoutManager.getSelectedPosition();
    210             if (newFocusedParent != oldfocusedParent) {
    211                 // The focus leaves from the current menu row view.
    212                 for (int i = selectedPosition - 1; i >= 0; --i) {
    213                     MenuRowView view = mMenuRowViews.get(i);
    214                     if (view.getVisibility() == View.VISIBLE) {
    215                         return view;
    216                     }
    217                 }
    218             }
    219             return newView;
    220         } else if (direction == View.FOCUS_DOWN) {
    221             View newView = super.focusSearch(focused, direction);
    222             MenuRowView oldfocusedParent = getParentMenuRowView(focused);
    223             MenuRowView newFocusedParent = getParentMenuRowView(newView);
    224             int selectedPosition = mLayoutManager.getSelectedPosition();
    225             if (newFocusedParent != oldfocusedParent) {
    226                 // The focus leaves from the current menu row view.
    227                 int count = mMenuRowViews.size();
    228                 for (int i = selectedPosition + 1; i < count; ++i) {
    229                     MenuRowView view = mMenuRowViews.get(i);
    230                     if (view.getVisibility() == View.VISIBLE) {
    231                         return view;
    232                     }
    233                 }
    234             }
    235             return newView;
    236         }
    237         return super.focusSearch(focused, direction);
    238     }
    239 
    240     private MenuRowView getParentMenuRowView(View view) {
    241         if (view == null) {
    242             return null;
    243         }
    244         ViewParent parent = view.getParent();
    245         if (parent == MenuView.this) {
    246             return (MenuRowView) view;
    247         }
    248         if (parent instanceof View) {
    249             return getParentMenuRowView((View) parent);
    250         }
    251         return null;
    252     }
    253 }
    254