1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package androidx.leanback.widget;
15 
16 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
17 
18 import android.graphics.Paint;
19 import android.text.TextUtils;
20 import android.view.LayoutInflater;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.widget.TextView;
24 
25 import androidx.annotation.RestrictTo;
26 import androidx.leanback.R;
27 
28 /**
29  * RowHeaderPresenter provides a default presentation for {@link HeaderItem} using a
30  * {@link RowHeaderView} and optionally a TextView for description. If a subclass creates its own
31  * view, the subclass must also override {@link #onCreateViewHolder(ViewGroup)},
32  * {@link #onSelectLevelChanged(ViewHolder)}.
33  */
34 public class RowHeaderPresenter extends Presenter {
35 
36     private final int mLayoutResourceId;
37     private final Paint mFontMeasurePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
38     private boolean mNullItemVisibilityGone;
39     private final boolean mAnimateSelect;
40 
41     /**
42      * Creates default RowHeaderPresenter using a title view and a description view.
43      * @see ViewHolder#ViewHolder(View)
44      */
RowHeaderPresenter()45     public RowHeaderPresenter() {
46         this(R.layout.lb_row_header);
47     }
48 
49     /**
50      * @hide
51      */
52     @RestrictTo(LIBRARY_GROUP)
RowHeaderPresenter(int layoutResourceId)53     public RowHeaderPresenter(int layoutResourceId) {
54         this(layoutResourceId, true);
55     }
56 
57     /**
58      * @hide
59      */
60     @RestrictTo(LIBRARY_GROUP)
RowHeaderPresenter(int layoutResourceId, boolean animateSelect)61     public RowHeaderPresenter(int layoutResourceId, boolean animateSelect) {
62         mLayoutResourceId = layoutResourceId;
63         mAnimateSelect = animateSelect;
64     }
65 
66     /**
67      * Optionally sets the view visibility to {@link View#GONE} when bound to null.
68      */
setNullItemVisibilityGone(boolean nullItemVisibilityGone)69     public void setNullItemVisibilityGone(boolean nullItemVisibilityGone) {
70         mNullItemVisibilityGone = nullItemVisibilityGone;
71     }
72 
73     /**
74      * Returns true if the view visibility is set to {@link View#GONE} when bound to null.
75      */
isNullItemVisibilityGone()76     public boolean isNullItemVisibilityGone() {
77         return mNullItemVisibilityGone;
78     }
79 
80     /**
81      * A ViewHolder for the RowHeaderPresenter.
82      */
83     public static class ViewHolder extends Presenter.ViewHolder {
84         float mSelectLevel;
85         int mOriginalTextColor;
86         float mUnselectAlpha;
87         RowHeaderView mTitleView;
88         TextView mDescriptionView;
89 
90         /**
91          * Creating a new ViewHolder that supports title and description.
92          * @param view Root of Views.
93          */
ViewHolder(View view)94         public ViewHolder(View view) {
95             super(view);
96             mTitleView = (RowHeaderView)view.findViewById(R.id.row_header);
97             mDescriptionView = (TextView)view.findViewById(R.id.row_header_description);
98             initColors();
99         }
100 
101         /**
102          * Uses a single {@link RowHeaderView} for creating a new ViewHolder.
103          * @param view The single RowHeaderView.
104          * @hide
105          */
106         @RestrictTo(LIBRARY_GROUP)
ViewHolder(RowHeaderView view)107         public ViewHolder(RowHeaderView view) {
108             super(view);
109             mTitleView = view;
110             initColors();
111         }
112 
initColors()113         void initColors() {
114             if (mTitleView != null) {
115                 mOriginalTextColor = mTitleView.getCurrentTextColor();
116             }
117 
118             mUnselectAlpha = view.getResources().getFraction(
119                     R.fraction.lb_browse_header_unselect_alpha, 1, 1);
120         }
121 
getSelectLevel()122         public final float getSelectLevel() {
123             return mSelectLevel;
124         }
125     }
126 
127     @Override
onCreateViewHolder(ViewGroup parent)128     public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
129         View root = LayoutInflater.from(parent.getContext())
130                 .inflate(mLayoutResourceId, parent, false);
131 
132         ViewHolder viewHolder = new ViewHolder(root);
133         if (mAnimateSelect) {
134             setSelectLevel(viewHolder, 0);
135         }
136         return viewHolder;
137     }
138 
139     @Override
onBindViewHolder(Presenter.ViewHolder viewHolder, Object item)140     public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
141         HeaderItem headerItem = item == null ? null : ((Row) item).getHeaderItem();
142         RowHeaderPresenter.ViewHolder vh = (RowHeaderPresenter.ViewHolder)viewHolder;
143         if (headerItem == null) {
144             if (vh.mTitleView != null) {
145                 vh.mTitleView.setText(null);
146             }
147             if (vh.mDescriptionView != null) {
148                 vh.mDescriptionView.setText(null);
149             }
150 
151             viewHolder.view.setContentDescription(null);
152             if (mNullItemVisibilityGone) {
153                 viewHolder.view.setVisibility(View.GONE);
154             }
155         } else {
156             if (vh.mTitleView != null) {
157                 vh.mTitleView.setText(headerItem.getName());
158             }
159             if (vh.mDescriptionView != null) {
160                 if (TextUtils.isEmpty(headerItem.getDescription())) {
161                     vh.mDescriptionView.setVisibility(View.GONE);
162                 } else {
163                     vh.mDescriptionView.setVisibility(View.VISIBLE);
164                 }
165                 vh.mDescriptionView.setText(headerItem.getDescription());
166             }
167             viewHolder.view.setContentDescription(headerItem.getContentDescription());
168             viewHolder.view.setVisibility(View.VISIBLE);
169         }
170     }
171 
172     @Override
onUnbindViewHolder(Presenter.ViewHolder viewHolder)173     public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
174         RowHeaderPresenter.ViewHolder vh = (ViewHolder)viewHolder;
175         if (vh.mTitleView != null) {
176             vh.mTitleView.setText(null);
177         }
178         if (vh.mDescriptionView != null) {
179             vh.mDescriptionView.setText(null);
180         }
181 
182         if (mAnimateSelect) {
183             setSelectLevel((ViewHolder) viewHolder, 0);
184         }
185     }
186 
187     /**
188      * Sets the select level.
189      */
setSelectLevel(ViewHolder holder, float selectLevel)190     public final void setSelectLevel(ViewHolder holder, float selectLevel) {
191         holder.mSelectLevel = selectLevel;
192         onSelectLevelChanged(holder);
193     }
194 
195     /**
196      * Called when the select level changes.  The default implementation sets the alpha on the view.
197      */
onSelectLevelChanged(ViewHolder holder)198     protected void onSelectLevelChanged(ViewHolder holder) {
199         if (mAnimateSelect) {
200             holder.view.setAlpha(holder.mUnselectAlpha + holder.mSelectLevel
201                     * (1f - holder.mUnselectAlpha));
202         }
203     }
204 
205     /**
206      * Returns the space (distance in pixels) below the baseline of the
207      * text view, if one exists; otherwise, returns 0.
208      */
getSpaceUnderBaseline(ViewHolder holder)209     public int getSpaceUnderBaseline(ViewHolder holder) {
210         int space = holder.view.getPaddingBottom();
211         if (holder.view instanceof TextView) {
212             space += (int) getFontDescent((TextView) holder.view, mFontMeasurePaint);
213         }
214         return space;
215     }
216 
217     @SuppressWarnings("ReferenceEquality")
getFontDescent(TextView textView, Paint fontMeasurePaint)218     protected static float getFontDescent(TextView textView, Paint fontMeasurePaint) {
219         if (fontMeasurePaint.getTextSize() != textView.getTextSize()) {
220             fontMeasurePaint.setTextSize(textView.getTextSize());
221         }
222         if (fontMeasurePaint.getTypeface() != textView.getTypeface()) {
223             fontMeasurePaint.setTypeface(textView.getTypeface());
224         }
225         return fontMeasurePaint.descent();
226     }
227 }
228