blob: e09a4ef0ee24ce68e8a2f6ff27298165418fe325 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tv.menu;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.tv.R;
import com.android.tv.menu.Menu.MenuShowReason;
public abstract class MenuRowView extends LinearLayout {
private static final String TAG = "MenuRowView";
private static final boolean DEBUG = false;
private TextView mTitleView;
private View mContentsView;
private final float mTitleViewAlphaDeselected;
private final float mTitleViewScaleSelected;
/**
* The lastly focused view. It is used to keep the focus while navigating the menu rows and
* reset when the menu is popped up.
*/
private View mLastFocusView;
private MenuRow mRow;
private final OnFocusChangeListener mOnFocusChangeListener =
new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
onChildFocusChange(v, hasFocus);
}
};
/** Returns the alpha value of the title view when it's deselected. */
public float getTitleViewAlphaDeselected() {
return mTitleViewAlphaDeselected;
}
/** Returns the scale value of the title view when it's selected. */
public float getTitleViewScaleSelected() {
return mTitleViewScaleSelected;
}
public MenuRowView(Context context) {
this(context, null);
}
public MenuRowView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MenuRowView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public MenuRowView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
Resources res = context.getResources();
TypedValue outValue = new TypedValue();
res.getValue(R.dimen.menu_row_title_alpha_deselected, outValue, true);
mTitleViewAlphaDeselected = outValue.getFloat();
float textSizeSelected =
res.getDimensionPixelSize(R.dimen.menu_row_title_text_size_selected);
float textSizeDeselected =
res.getDimensionPixelSize(R.dimen.menu_row_title_text_size_deselected);
mTitleViewScaleSelected = textSizeSelected / textSizeDeselected;
this.setAccessibilityDelegate(
new AccessibilityDelegate() {
@Override
public void sendAccessibilityEvent(View host, int eventType) {
super.sendAccessibilityEvent(host, eventType);
if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED &&
!mRow.isReselected()) {
requestChildFocus();
}
}
}
);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTitleView = (TextView) findViewById(R.id.title);
mContentsView = findViewById(getContentsViewId());
if (mContentsView.isFocusable()) {
mContentsView.setOnFocusChangeListener(mOnFocusChangeListener);
}
if (mContentsView instanceof ViewGroup) {
setOnFocusChangeListenerToChildren((ViewGroup) mContentsView);
}
// Make contents view invisible in order that the view participates in the initial layout.
// The visibility is set to GONE after the first layout finishes.
// If not, we can't see the contents view animation for the first time it is shown.
// TODO: Find a better way to resolve this issue.
mContentsView.setVisibility(INVISIBLE);
}
private void setOnFocusChangeListenerToChildren(ViewGroup parent) {
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; ++i) {
View child = parent.getChildAt(i);
if (child.isFocusable()) {
child.setOnFocusChangeListener(mOnFocusChangeListener);
}
if (child instanceof ViewGroup) {
setOnFocusChangeListenerToChildren((ViewGroup) child);
}
}
}
protected abstract int getContentsViewId();
/** Returns the title view. */
public final TextView getTitleView() {
return mTitleView;
}
/** Returns the contents view. */
public final View getContentsView() {
return mContentsView;
}
/**
* Initialize this view. e.g. Set the initial selection. This method is called when the main
* menu is visible. Subclass of {@link MenuRowView} should override this to set correct
* mLastFocusView.
*
* @param reason A reason why this is initialized. See {@link MenuShowReason}
*/
public void initialize(@MenuShowReason int reason) {
mLastFocusView = null;
}
protected Menu getMenu() {
return mRow == null ? null : mRow.getMenu();
}
public void onBind(MenuRow row) {
if (DEBUG) Log.d(TAG, "onBind: row=" + row);
mRow = row;
mTitleView.setText(row.getTitle());
}
@Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
// Expand view here so initial focused item can be shown.
return getInitialFocusView().requestFocus();
}
@NonNull
private View getInitialFocusView() {
if (mLastFocusView == null) {
return mContentsView;
}
return mLastFocusView;
}
/**
* Sets the view which needs to have focus when this row appears. Subclasses should call this in
* {@link #initialize} if needed.
*/
protected void setInitialFocusView(@NonNull View v) {
mLastFocusView = v;
}
/** Subclasses should implement this to request focus on child. */
protected abstract void requestChildFocus();
/**
* Called when the focus of a child view is changed. The inherited class should override this
* method instead of calling {@link
* android.view.View#setOnFocusChangeListener(android.view.View.OnFocusChangeListener)}.
*/
protected void onChildFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
mLastFocusView = v;
}
}
/** Returns the ID of row object bound to this view. */
public String getRowId() {
return mRow == null ? null : mRow.getId();
}
/**
* Called when this row is selected.
*
* @param showTitle If {@code true}, the title is not hidden immediately after the row is
* selected even though hideTitleWhenSelected() is {@code true}.
*/
public void onSelected(boolean showTitle) {
if (mRow.hideTitleWhenSelected() && !showTitle) {
// Title view should participate in the layout even though it is not visible.
mTitleView.setVisibility(INVISIBLE);
} else {
mTitleView.setVisibility(VISIBLE);
mTitleView.setAlpha(1.0f);
mTitleView.setScaleX(mTitleViewScaleSelected);
mTitleView.setScaleY(mTitleViewScaleSelected);
}
// Making the content view visible will cause it to set a focus item
// So we store mLastFocusView and reset it
View lastFocusView = mLastFocusView;
mContentsView.setVisibility(VISIBLE);
mLastFocusView = lastFocusView;
}
/** Called when this row is deselected. */
public void onDeselected() {
mTitleView.setVisibility(VISIBLE);
mTitleView.setAlpha(mTitleViewAlphaDeselected);
mTitleView.setScaleX(1.0f);
mTitleView.setScaleY(1.0f);
mContentsView.setVisibility(GONE);
}
/** Returns the preferred height of the contents view. The top/bottom padding is excluded. */
public int getPreferredContentsHeight() {
return mRow.getHeight();
}
}