public class

GridRowView

extends SliceChildView

 java.lang.Object

↳FrameLayout

androidx.slice.widget.SliceChildView

↳androidx.slice.widget.GridRowView

Gradle dependencies

compile group: 'androidx.slice', name: 'slice-view', version: '1.1.0-alpha02'

  • groupId: androidx.slice
  • artifactId: slice-view
  • version: 1.1.0-alpha02

Artifact androidx.slice:slice-view:1.1.0-alpha02 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.slice:slice-view com.android.support:slices-view

Summary

Fields
from SliceChildViewmInsetBottom, mInsetEnd, mInsetStart, mInsetTop, mLastUpdated, mLoadingListener, mMode, mObserver, mRowStyle, mShowLastUpdated, mSliceStyle, mTintColor, mViewPolicy
Constructors
publicGridRowView(Context context)

publicGridRowView(Context context, AttributeSet attrs)

Methods
public intgetHiddenItemCount()

public voidonClick(View view)

protected voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)

public booleanonTouch(View view, MotionEvent event)

public abstract voidresetView()

Called when the view should be reset.

public voidsetInsets(int l, int t, int r, int b)

Sets the insets (padding) for the slice.

public voidsetSliceItem(SliceContent slice, boolean isHeader, int rowIndex, int rowCount, SliceView.OnSliceActionListener observer)

This is called when GridView is being used as a component in a larger template.

public voidsetTint(int tintColor)

Sets a custom color to use for tinting elements like icons for this view.

from SliceChildViewgetLoadingActions, getMode, setActionLoading, setAllowTwoLines, setLastUpdated, setLoadingActions, setPolicy, setShowLastUpdated, setSliceActionListener, setSliceActionLoadingListener, setSliceActions, setSliceContent, setStyle
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public GridRowView(Context context)

public GridRowView(Context context, AttributeSet attrs)

Methods

public void setInsets(int l, int t, int r, int b)

Sets the insets (padding) for the slice.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

public void setTint(int tintColor)

Sets a custom color to use for tinting elements like icons for this view.

public void setSliceItem(SliceContent slice, boolean isHeader, int rowIndex, int rowCount, SliceView.OnSliceActionListener observer)

This is called when GridView is being used as a component in a larger template.

public void onClick(View view)

public boolean onTouch(View view, MotionEvent event)

public abstract void resetView()

Called when the view should be reset.

public int getHiddenItemCount()

Source

/*
 * Copyright 2017 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 androidx.slice.widget;

import static android.app.slice.Slice.EXTRA_RANGE_VALUE;
import static android.app.slice.Slice.HINT_LARGE;
import static android.app.slice.Slice.HINT_NO_TINT;
import static android.app.slice.Slice.HINT_TITLE;
import static android.app.slice.Slice.SUBTYPE_MILLIS;
import static android.app.slice.SliceItem.FORMAT_ACTION;
import static android.app.slice.SliceItem.FORMAT_IMAGE;
import static android.app.slice.SliceItem.FORMAT_LONG;
import static android.app.slice.SliceItem.FORMAT_SLICE;
import static android.app.slice.SliceItem.FORMAT_TEXT;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

import static androidx.slice.core.SliceHints.LARGE_IMAGE;
import static androidx.slice.core.SliceHints.RAW_IMAGE_LARGE;
import static androidx.slice.core.SliceHints.SUBTYPE_DATE_PICKER;
import static androidx.slice.core.SliceHints.SUBTYPE_TIME_PICKER;
import static androidx.slice.widget.EventInfo.ACTION_TYPE_DATE_PICK;
import static androidx.slice.widget.EventInfo.ACTION_TYPE_TIME_PICK;
import static androidx.slice.widget.EventInfo.ACTION_TYPE_TOGGLE;
import static androidx.slice.widget.EventInfo.ROW_TYPE_DATE_PICK;
import static androidx.slice.widget.EventInfo.ROW_TYPE_TIME_PICK;
import static androidx.slice.widget.EventInfo.ROW_TYPE_TOGGLE;
import static androidx.slice.widget.SliceView.MODE_SMALL;

import android.app.DatePickerDialog;
import android.app.PendingIntent;
import android.app.TimePickerDialog;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.DatePicker;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.TimePicker;

import androidx.annotation.ColorInt;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.content.ContextCompat;
import androidx.slice.CornerDrawable;
import androidx.slice.SliceItem;
import androidx.slice.core.SliceActionImpl;
import androidx.slice.core.SliceHints;
import androidx.slice.core.SliceQuery;
import androidx.slice.view.R;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

/**
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
public class GridRowView extends SliceChildView implements View.OnClickListener,
        View.OnTouchListener {

    private static final String TAG = "GridRowView";

    private static final int TITLE_TEXT_LAYOUT = R.layout.abc_slice_title;
    private static final int TEXT_LAYOUT = R.layout.abc_slice_secondary_text;

    // Max number of text items that can show in a cell
    private static final int MAX_CELL_TEXT = 2;
    // Max number of text items that can show in a cell if the mode is small
    private static final int MAX_CELL_TEXT_SMALL = 1;
    // Max number of images that can show in a cell
    private static final int MAX_CELL_IMAGES = 1;

    private int mRowIndex;
    private int mRowCount;

    private final int mLargeImageHeight;
    private final int mSmallImageSize;
    private final int mSmallImageMinWidth;
    private final int mIconSize;
    private final int mGutter;
    private final int mTextPadding;

    private GridContent mGridContent;
    private final LinearLayout mViewContainer;
    private final View mForeground;
    int mMaxCells = -1;
    private final int[] mLoc = new int[2];

    boolean mMaxCellUpdateScheduled;

    private int mHiddenItemCount;

    public GridRowView(Context context) {
        this(context, null);
    }

    public GridRowView(Context context, AttributeSet attrs) {
        super(context, attrs);
        final Resources res = getContext().getResources();
        mViewContainer = new LinearLayout(getContext());
        mViewContainer.setOrientation(LinearLayout.HORIZONTAL);
        addView(mViewContainer, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mViewContainer.setGravity(Gravity.CENTER_VERTICAL);
        mIconSize = res.getDimensionPixelSize(R.dimen.abc_slice_icon_size);
        mSmallImageSize = res.getDimensionPixelSize(R.dimen.abc_slice_small_image_size);
        mLargeImageHeight = res.getDimensionPixelSize(R.dimen.abc_slice_grid_image_only_height);
        mSmallImageMinWidth = res.getDimensionPixelSize(R.dimen.abc_slice_grid_image_min_width);
        mGutter = res.getDimensionPixelSize(R.dimen.abc_slice_grid_gutter);
        mTextPadding = res.getDimensionPixelSize(R.dimen.abc_slice_grid_text_padding);

        mForeground = new View(getContext());
        addView(mForeground, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setInsets(int l, int t, int r, int b) {
        super.setInsets(l, t, r, b);
        mViewContainer.setPadding(l, t + getExtraTopPadding(), r, b + getExtraBottomPadding());
    }

    private int getExtraTopPadding() {
        if (mGridContent != null && mGridContent.isAllImages()) {
            // Might need to add padding if in first or last position
            if (mRowIndex == 0) {
                return mSliceStyle != null ? mSliceStyle.getGridTopPadding() : 0;
            }
        }
        return 0;
    }

    private int getExtraBottomPadding() {
        if (mGridContent != null && mGridContent.isAllImages()) {
            if (mRowIndex == mRowCount - 1 || getMode() == MODE_SMALL) {
                return mSliceStyle != null ? mSliceStyle.getGridBottomPadding() : 0;
            }
        }
        return 0;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = mGridContent.getHeight(mSliceStyle, mViewPolicy)
                + mInsetTop + mInsetBottom;
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
        mViewContainer.getLayoutParams().height = height;
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    public void setTint(@ColorInt int tintColor) {
        super.setTint(tintColor);
        if (mGridContent != null) {
            // TODO -- could be smarter about this
            resetView();
            populateViews();
        }
    }

    /**
     * This is called when GridView is being used as a component in a larger template.
     */
    @Override
    public void setSliceItem(SliceContent slice, boolean isHeader, int rowIndex,
            int rowCount, SliceView.OnSliceActionListener observer) {
        resetView();
        setSliceActionListener(observer);
        mRowIndex = rowIndex;
        mRowCount = rowCount;
        mGridContent = (GridContent) slice;

        if (!scheduleMaxCellsUpdate()) {
            populateViews();
        }
        mViewContainer.setPadding(mInsetStart, mInsetTop + getExtraTopPadding(), mInsetEnd,
                mInsetBottom + getExtraBottomPadding());
    }

    /**
     * Schedules update to determine the max number of cells that can be shown in this grid.
     *
     * @return true if update was scheduled, false if it wasn't needed
     */
    private boolean scheduleMaxCellsUpdate() {
        if (mGridContent == null || !mGridContent.isValid()) {
            return true;
        }
        if (getWidth() == 0) {
            // Need to wait for layout pass so we know how much width we have for the grid items.
            mMaxCellUpdateScheduled = true;
            getViewTreeObserver().addOnPreDrawListener(mMaxCellsUpdater);
            return true;
        } else {
            mMaxCells = getMaxCells();
            return false;
        }
    }

    int getMaxCells() {
        if (mGridContent == null || !mGridContent.isValid() || getWidth() == 0) {
            return -1;
        }
        ArrayList<GridContent.CellContent> cells = mGridContent.getGridContent();
        if (cells.size() > 1) {
            int desiredCellWidth;
            switch (mGridContent.getLargestImageMode()) {
                case LARGE_IMAGE:
                    desiredCellWidth = mLargeImageHeight;
                    break;
                case RAW_IMAGE_LARGE:
                    desiredCellWidth = mGridContent.getFirstImageSize(getContext()).x;
                    break;
                default:
                    desiredCellWidth = mSmallImageMinWidth;
            }
            return getWidth() / (desiredCellWidth + mGutter);
        } else {
            return 1;
        }
    }

    void populateViews() {
        if (mGridContent == null || !mGridContent.isValid()) {
            resetView();
            return;
        }
        if (scheduleMaxCellsUpdate()) {
            return;
        }
        if (mGridContent.getLayoutDir() != -1) {
            setLayoutDirection(mGridContent.getLayoutDir());
        }
        if (mGridContent.getContentIntent() != null) {
            EventInfo info = new EventInfo(getMode(), EventInfo.ACTION_TYPE_CONTENT,
                    EventInfo.ROW_TYPE_GRID, mRowIndex);
            Pair<SliceItem, EventInfo> tagItem = new Pair<>(mGridContent.getContentIntent(), info);
            mViewContainer.setTag(tagItem);
            makeEntireGridClickable(true);
        }
        CharSequence contentDescr = mGridContent.getContentDescription();
        if (contentDescr != null) {
            mViewContainer.setContentDescription(contentDescr);
        }
        ArrayList<GridContent.CellContent> cells = mGridContent.getGridContent();
        if (mGridContent.getLargestImageMode() == LARGE_IMAGE
                || mGridContent.getLargestImageMode() == RAW_IMAGE_LARGE) {
            mViewContainer.setGravity(Gravity.TOP);
        } else {
            mViewContainer.setGravity(Gravity.CENTER_VERTICAL);
        }
        int maxCells = mMaxCells;
        boolean hasSeeMore = mGridContent.getSeeMoreItem() != null;
        mHiddenItemCount = 0;
        for (int i = 0; i < cells.size(); i++) {
            if (mViewContainer.getChildCount() >= maxCells) {
                mHiddenItemCount = cells.size() - maxCells;
                if (hasSeeMore) {
                    addSeeMoreCount(mHiddenItemCount);
                }
                break;
            }
            addCell(cells.get(i), i, Math.min(cells.size(), maxCells));
        }
    }

    private void addSeeMoreCount(int numExtra) {
        // Remove last element
        View last = mViewContainer.getChildAt(mViewContainer.getChildCount() - 1);
        mViewContainer.removeView(last);

        SliceItem seeMoreItem = mGridContent.getSeeMoreItem();
        int index = mViewContainer.getChildCount();
        int total = mMaxCells;
        if ((FORMAT_SLICE.equals(seeMoreItem.getFormat())
                || FORMAT_ACTION.equals(seeMoreItem.getFormat()))
                && seeMoreItem.getSlice().getItems().size() > 0) {
            // It's a custom see more cell, add it
            addCell(new GridContent.CellContent(seeMoreItem), index, total);
            return;
        }

        // Default see more, create it
        LayoutInflater inflater = LayoutInflater.from(getContext());
        TextView extraText;
        View extraTint;
        ViewGroup seeMoreView;
        if (mGridContent.isAllImages()) {
            seeMoreView = (FrameLayout) inflater.inflate(R.layout.abc_slice_grid_see_more_overlay,
                    mViewContainer, false);
            seeMoreView.addView(last, 0, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
            extraText = seeMoreView.findViewById(R.id.text_see_more_count);
            extraTint = seeMoreView.findViewById(R.id.overlay_see_more);
            extraTint.setBackground(new CornerDrawable(SliceViewUtil.getDrawable(
                    getContext(), android.R.attr.colorForeground),
                    mSliceStyle.getImageCornerRadius()));
        } else {
            seeMoreView = (LinearLayout) inflater.inflate(
                    R.layout.abc_slice_grid_see_more, mViewContainer, false);
            extraText = seeMoreView.findViewById(R.id.text_see_more_count);

            // Update text appearance
            TextView moreText = seeMoreView.findViewById(R.id.text_see_more);
            if (mSliceStyle != null && mRowStyle != null) {
                moreText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSliceStyle.getGridTitleSize());
                moreText.setTextColor(mRowStyle.getTitleColor());
            }
        }
        mViewContainer.addView(seeMoreView, new LinearLayout.LayoutParams(0, MATCH_PARENT, 1));
        extraText.setText(getResources().getString(R.string.abc_slice_more_content, numExtra));

        // Make it clickable
        EventInfo info = new EventInfo(getMode(), EventInfo.ACTION_TYPE_SEE_MORE,
                EventInfo.ROW_TYPE_GRID, mRowIndex);
        info.setPosition(EventInfo.POSITION_CELL, index, total);
        Pair<SliceItem, EventInfo> tagItem = new Pair<>(seeMoreItem, info);
        seeMoreView.setTag(tagItem);
        makeClickable(seeMoreView, true);
    }

    /**
     * Adds a cell to the grid view based on the provided {@link SliceItem}.
     */
    private void addCell(GridContent.CellContent cell, int index, int total) {
        final int maxCellText = getMode() == MODE_SMALL && mGridContent.hasImage()
                ? MAX_CELL_TEXT_SMALL : MAX_CELL_TEXT;
        LinearLayout cellContainer = new LinearLayout(getContext());
        cellContainer.setOrientation(LinearLayout.VERTICAL);
        cellContainer.setGravity(Gravity.CENTER_HORIZONTAL);

        ArrayList<SliceItem> cellItems = cell.getCellItems();
        SliceItem contentIntentItem = cell.getContentIntent();
        SliceItem pickerItem = cell.getPicker();
        SliceItem toggleItem = cell.getToggleItem();

        int textCount = 0;
        int imageCount = 0;
        boolean added = false;
        boolean isSingleItem = cellItems.size() == 1;
        List<SliceItem> textItems = null;
        // In small format we display one text item and prefer titles
        if (!isSingleItem && getMode() == MODE_SMALL) {
            // Get all our text items
            textItems = new ArrayList<>();
            for (SliceItem cellItem : cellItems) {
                if (FORMAT_TEXT.equals(cellItem.getFormat())) {
                    textItems.add(cellItem);
                }
            }
            // If we have more than 1 remove non-titles
            Iterator<SliceItem> iterator = textItems.iterator();
            while (textItems.size() > maxCellText) {
                SliceItem item = iterator.next();
                if (!item.hasAnyHints(HINT_TITLE, HINT_LARGE)) {
                    iterator.remove();
                }
            }
        }
        SliceItem prevItem = null;
        for (int i = 0; i < cellItems.size(); i++) {
            SliceItem item = cellItems.get(i);
            final String itemFormat = item.getFormat();
            int padding = determinePadding(prevItem);
            if (textCount < maxCellText && (FORMAT_TEXT.equals(itemFormat)
                    || FORMAT_LONG.equals(itemFormat))) {
                if (textItems != null && !textItems.contains(item)) {
                    continue;
                }
                if (addTextItem(item, cellContainer, padding)) {
                    prevItem = item;
                    textCount++;
                    added = true;
                }
            } else if (imageCount < MAX_CELL_IMAGES && FORMAT_IMAGE.equals(item.getFormat())) {
                if (addImageItem(item, cell.getOverlayItem(), mTintColor, cellContainer,
                        isSingleItem)) {
                    prevItem = item;
                    imageCount++;
                    added = true;
                }
            }
        }
        if (pickerItem != null) {
            if (SUBTYPE_DATE_PICKER.equals(pickerItem.getSubType())) {
                added = addPickerItem(pickerItem, cellContainer, determinePadding(prevItem),
                        /*isDatePicker=*/ true);
            } else if (SUBTYPE_TIME_PICKER.equals(pickerItem.getSubType())) {
                added = addPickerItem(pickerItem, cellContainer, determinePadding(prevItem),
                        /*isDatePicker=*/ false);
            }
        }
        SliceActionView sav = null;
        if (toggleItem != null) {
            sav = new SliceActionView(getContext(), mSliceStyle, mRowStyle);
            cellContainer.addView(sav);
            added = true;
        }
        if (added) {
            CharSequence contentDescr = cell.getContentDescription();
            if (contentDescr != null) {
                cellContainer.setContentDescription(contentDescr);
            }
            mViewContainer.addView(cellContainer,
                    new LinearLayout.LayoutParams(0, WRAP_CONTENT, 1));
            if (index != total - 1) {
                // If we're not the last or only element add space between items
                MarginLayoutParams lp =
                        (LinearLayout.MarginLayoutParams) cellContainer.getLayoutParams();
                lp.setMarginEnd(mGutter);
                cellContainer.setLayoutParams(lp);
            }
            if (contentIntentItem != null) {
                EventInfo info = new EventInfo(getMode(), EventInfo.ACTION_TYPE_BUTTON,
                        EventInfo.ROW_TYPE_GRID, mRowIndex);
                info.setPosition(EventInfo.POSITION_CELL, index, total);
                Pair<SliceItem, EventInfo> tagItem = new Pair<>(contentIntentItem, info);
                cellContainer.setTag(tagItem);
                makeClickable(cellContainer, true);
            }
            if (toggleItem != null) {
                EventInfo info =
                        new EventInfo(getMode(), ACTION_TYPE_TOGGLE, ROW_TYPE_TOGGLE, mRowIndex);
                sav.setAction(
                        new SliceActionImpl(toggleItem),
                        info, mObserver, mTintColor, mLoadingListener);
                info.setPosition(EventInfo.POSITION_CELL, index, total);
            }
        }
    }

    /**
     * Adds simple text based items to a container. Simple text items include text and
     * timestamps.
     *
     * @param item      item to add to the container.
     * @param container the container to add to.
     * @param padding   the padding to apply to the item.
     * @return Whether an item was added.
     */
    private boolean addTextItem(SliceItem item, ViewGroup container, int padding) {
        final String format = item.getFormat();
        if (!FORMAT_TEXT.equals(format) && !FORMAT_LONG.equals(format)) {
            return false;
        }
        boolean isTitle = SliceQuery.hasAnyHints(item, HINT_LARGE, HINT_TITLE);
        TextView tv = (TextView) LayoutInflater.from(getContext()).inflate(isTitle
                ? TITLE_TEXT_LAYOUT : TEXT_LAYOUT, null);
        if (mSliceStyle != null && mRowStyle != null) {
            tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, isTitle
                    ? mSliceStyle.getGridTitleSize() : mSliceStyle.getGridSubtitleSize());
            tv.setTextColor(isTitle
                    ? mRowStyle.getTitleColor() : mRowStyle.getSubtitleColor());
        }
        CharSequence text = FORMAT_LONG.equals(format)
                ? SliceViewUtil.getTimestampString(getContext(), item.getLong())
                : item.getSanitizedText();
        tv.setText(text);
        container.addView(tv);
        tv.setPadding(0, padding, 0, 0);
        return true;
    }

    /**
     * Adds simple image based item to a container.
     *
     * @param item        item to add to the container.
     * @param overlayItem overlaid text to add to the image.
     * @param container   the container to add to.
     * @param isSingle    whether this is the only item in the cell or not.
     * @return Whether an item was added.
     */
    private boolean addImageItem(SliceItem item, SliceItem overlayItem, int color,
            ViewGroup container, boolean isSingle) {
        final String format = item.getFormat();
        final boolean hasRoundedImage =
                mSliceStyle != null && mSliceStyle.getApplyCornerRadiusToLargeImages();
        if (!FORMAT_IMAGE.equals(format) || item.getIcon() == null) {
            return false;
        }
        Drawable d = item.getIcon().loadDrawable(getContext());
        if (d == null) {
            return false;
        }
        ImageView iv = new ImageView(getContext());
        if (hasRoundedImage) {
            CornerDrawable cd = new CornerDrawable(d, mSliceStyle.getImageCornerRadius());
            iv.setImageDrawable(cd);
        } else {
            iv.setImageDrawable(d);
        }
        LinearLayout.LayoutParams lp;
        if (item.hasHint(SliceHints.HINT_RAW)) {
            iv.setScaleType(ScaleType.CENTER_INSIDE);
            lp = new LinearLayout.LayoutParams(mGridContent.getFirstImageSize(getContext()).x,
                    mGridContent.getFirstImageSize(getContext()).y);
        } else if (item.hasHint(HINT_LARGE)) {
            iv.setScaleType(hasRoundedImage ? ScaleType.FIT_XY : ScaleType.CENTER_CROP);
            int height = isSingle ? MATCH_PARENT : mLargeImageHeight;
            lp = new LinearLayout.LayoutParams(MATCH_PARENT, height);
        } else {
            boolean isIcon = !item.hasHint(HINT_NO_TINT);
            int size = !isIcon ? mSmallImageSize : mIconSize;
            iv.setScaleType(isIcon ? ScaleType.CENTER_INSIDE : ScaleType.CENTER_CROP);
            lp = new LinearLayout.LayoutParams(size, size);
        }
        if (color != -1 && !item.hasHint(HINT_NO_TINT)) {
            iv.setColorFilter(color);
        }
        // don't add an overlay on see more
        if (overlayItem == null || mViewContainer.getChildCount() == mMaxCells - 1) {
            container.addView(iv, lp);
            return true;
        }
        // add overlay on top of the ImageView
        LayoutInflater inflater = LayoutInflater.from(getContext());
        TextView overlayText;
        View overlayTint;
        ViewGroup overlayView;
        overlayView = (FrameLayout) inflater.inflate(R.layout.abc_slice_grid_text_overlay_image,
                container, false);
        overlayView.addView(iv, 0, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
        overlayText = overlayView.findViewById(R.id.text_overlay);
        overlayText.setText(overlayItem.getText());
        overlayTint = overlayView.findViewById(R.id.tint_overlay);
        overlayTint.setBackground(new CornerDrawable(ContextCompat.getDrawable(getContext(),
                R.drawable.abc_slice_gradient), mSliceStyle.getImageCornerRadius()));
        container.addView(overlayView, lp);
        return true;
    }

    /**
     * Adds date or time picker to a container.
     *
     * @param pickerItem item to add to the container.
     * @param container      the container to add to.
     * @param padding        the padding to apply to the item.
     * @param isDatePicker   if true, it is a date picker, otherwise is a time picker.
     * @return Whether an item was added.
     */
    private boolean addPickerItem(SliceItem pickerItem, ViewGroup container, int padding,
            boolean isDatePicker) {
        SliceItem dateTimeItem = SliceQuery.findSubtype(pickerItem, FORMAT_LONG,
                SUBTYPE_MILLIS);
        if (dateTimeItem == null) {
            return false;
        }
        long dateTimeMillis = dateTimeItem.getLong();

        TextView tv = (TextView) LayoutInflater.from(getContext()).inflate(TITLE_TEXT_LAYOUT, null);
        if (mSliceStyle != null) {
            tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSliceStyle.getGridTitleSize());
            tv.setTextColor(mSliceStyle.getTitleColor());
        }

        Date date = new Date(dateTimeMillis);
        SliceItem titleItem = SliceQuery.find(pickerItem, FORMAT_TEXT, HINT_TITLE,
                /*nonHints=*/null);
        if (titleItem != null) {
            tv.setText(titleItem.getText());
        }

        int rowIndex = mRowIndex;

        container.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Calendar cal = Calendar.getInstance();
                cal.setTime(date);
                if (isDatePicker) {
                    DatePickerDialog dialog = new DatePickerDialog(
                            getContext(),
                            R.style.DialogTheme,
                            new DateSetListener(pickerItem, rowIndex),
                            cal.get(Calendar.YEAR),
                            cal.get(Calendar.MONTH),
                            cal.get(Calendar.DAY_OF_MONTH));
                    dialog.show();
                } else {
                    TimePickerDialog dialog = new TimePickerDialog(
                            getContext(),
                            R.style.DialogTheme,
                            new TimeSetListener(pickerItem, rowIndex),
                            cal.get(Calendar.HOUR_OF_DAY),
                            cal.get(Calendar.MINUTE),
                            false);
                    dialog.show();
                }
            }
        });
        container.setClickable(true);

        int backgroundAttr = android.R.attr.selectableItemBackground;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            backgroundAttr = android.R.attr.selectableItemBackgroundBorderless;
        }
        container.setBackground(SliceViewUtil.getDrawable(getContext(), backgroundAttr));
        container.addView(tv);
        tv.setPadding(0, padding, 0, 0);
        return true;
    }

    private class DateSetListener implements DatePickerDialog.OnDateSetListener {
        private final SliceItem mActionItem;
        private final int mRowIndex;

        DateSetListener(SliceItem datePickerItem, int mRowIndex) {
            this.mActionItem = datePickerItem;
            this.mRowIndex = mRowIndex;
        }

        @Override
        public void onDateSet(DatePicker datePicker, int year, int month, int day) {
            Calendar c = Calendar.getInstance();
            c.set(year, month, day);
            Date date = c.getTime();


            if (mActionItem != null) {
                try {
                    mActionItem.fireAction(getContext(),
                            new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
                                    .putExtra(EXTRA_RANGE_VALUE, date.getTime()));
                    if (mObserver != null) {
                        EventInfo info = new EventInfo(getMode(), ACTION_TYPE_DATE_PICK,
                                ROW_TYPE_DATE_PICK,
                                mRowIndex);
                        mObserver.onSliceAction(info, mActionItem);
                    }
                } catch (PendingIntent.CanceledException e) {
                    Log.e(TAG, "PendingIntent for slice cannot be sent", e);
                }
            }
        }
    }

    private class TimeSetListener implements TimePickerDialog.OnTimeSetListener {
        private final SliceItem mActionItem;
        private final int mRowIndex;

        TimeSetListener(SliceItem timePickerItem, int mRowIndex) {
            this.mActionItem = timePickerItem;
            this.mRowIndex = mRowIndex;
        }

        @Override
        public void onTimeSet(TimePicker timePicker, int hour, int minute) {
            Calendar c = Calendar.getInstance();
            Date time = c.getTime();
            time.setHours(hour);
            time.setMinutes(minute);

            if (mActionItem != null) {
                try {
                    mActionItem.fireAction(getContext(),
                            new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
                                    .putExtra(EXTRA_RANGE_VALUE, time.getTime()));
                    if (mObserver != null) {
                        EventInfo info = new EventInfo(getMode(), ACTION_TYPE_TIME_PICK,
                                ROW_TYPE_TIME_PICK,
                                mRowIndex);
                        mObserver.onSliceAction(info, mActionItem);
                    }
                } catch (PendingIntent.CanceledException e) {
                    Log.e(TAG, "PendingIntent for slice cannot be sent", e);
                }
            }
        }
    }

    private int determinePadding(SliceItem prevItem) {
        if (prevItem == null) {
            // No need for top padding
            return 0;
        } else if (FORMAT_IMAGE.equals(prevItem.getFormat())) {
            return mTextPadding;
        } else if (FORMAT_TEXT.equals(prevItem.getFormat())
                || FORMAT_LONG.equals(prevItem.getFormat())) {
            return mSliceStyle != null ? mSliceStyle.getVerticalGridTextPadding() : 0;
        }
        return 0;
    }

    private void makeEntireGridClickable(boolean isClickable) {
        mViewContainer.setOnTouchListener(isClickable ? this : null);
        mViewContainer.setOnClickListener((isClickable ? this : null));
        mForeground.setBackground(isClickable
                ? SliceViewUtil.getDrawable(
                        getContext(), android.R.attr.selectableItemBackground)
                : null);
        mViewContainer.setClickable(isClickable);
    }

    private void makeClickable(View layout, boolean isClickable) {
        layout.setOnClickListener(isClickable ? this : null);
        int backgroundAttr = android.R.attr.selectableItemBackground;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            backgroundAttr = android.R.attr.selectableItemBackgroundBorderless;
        }
        layout.setBackground(isClickable
                ? SliceViewUtil.getDrawable(getContext(), backgroundAttr)
                : null);
        layout.setClickable(isClickable);
    }

    @Override
    @SuppressWarnings("unchecked")
    public void onClick(View view) {
        Pair<SliceItem, EventInfo> tagItem = (Pair<SliceItem, EventInfo>) view.getTag();
        final SliceItem sliceItem = tagItem.first;
        final EventInfo info = tagItem.second;
        if (sliceItem != null) {
            final SliceItem actionItem = SliceQuery.find(sliceItem,
                    FORMAT_ACTION, (String) null, null);
            if (actionItem != null) {
                try {
                    actionItem.fireAction(null, null);
                    if (mObserver != null) {
                        mObserver.onSliceAction(info, actionItem);
                    }
                } catch (PendingIntent.CanceledException e) {
                    Log.e(TAG, "PendingIntent for slice cannot be sent", e);
                }
            }
        }
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        onForegroundActivated(event);
        return false;
    }

    private void onForegroundActivated(MotionEvent event) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            mForeground.getLocationOnScreen(mLoc);
            final int x = (int) (event.getRawX() - mLoc[0]);
            final int y = (int) (event.getRawY() - mLoc[1]);
            mForeground.getBackground().setHotspot(x, y);
        }
        int action = event.getActionMasked();
        if (action == android.view.MotionEvent.ACTION_DOWN) {
            mForeground.setPressed(true);
        } else if (action == android.view.MotionEvent.ACTION_CANCEL
                || action == android.view.MotionEvent.ACTION_UP
                || action == android.view.MotionEvent.ACTION_MOVE) {
            mForeground.setPressed(false);
        }
    }

    @Override
    public void resetView() {
        if (mMaxCellUpdateScheduled) {
            mMaxCellUpdateScheduled = false;
            getViewTreeObserver().removeOnPreDrawListener(mMaxCellsUpdater);
        }
        mViewContainer.removeAllViews();
        setLayoutDirection(View.LAYOUT_DIRECTION_INHERIT);
        makeEntireGridClickable(false);
    }

    @Override
    public int getHiddenItemCount() {
        return mHiddenItemCount;
    }

    private final ViewTreeObserver.OnPreDrawListener mMaxCellsUpdater =
            new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
                    mMaxCells = getMaxCells();
                    populateViews();
                    getViewTreeObserver().removeOnPreDrawListener(this);
                    mMaxCellUpdateScheduled = false;
                    return true;
                }
            };
}