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