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 */
14package androidx.leanback.widget;
15
16import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
17
18import android.graphics.Paint;
19import android.text.TextUtils;
20import android.view.LayoutInflater;
21import android.view.View;
22import android.view.ViewGroup;
23import android.widget.TextView;
24
25import androidx.annotation.RestrictTo;
26import 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 */
34public 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     */
45    public RowHeaderPresenter() {
46        this(R.layout.lb_row_header);
47    }
48
49    /**
50     * @hide
51     */
52    @RestrictTo(LIBRARY_GROUP)
53    public RowHeaderPresenter(int layoutResourceId) {
54        this(layoutResourceId, true);
55    }
56
57    /**
58     * @hide
59     */
60    @RestrictTo(LIBRARY_GROUP)
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     */
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     */
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         */
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)
107        public ViewHolder(RowHeaderView view) {
108            super(view);
109            mTitleView = view;
110            initColors();
111        }
112
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
122        public final float getSelectLevel() {
123            return mSelectLevel;
124        }
125    }
126
127    @Override
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
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
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     */
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     */
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     */
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")
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