public class

SeekbarListItem

extends ListItem<SeekbarListItem.ViewHolder>

 java.lang.Object

androidx.car.widget.ListItem<SeekbarListItem.ViewHolder>

↳androidx.car.widget.SeekbarListItem

Gradle dependencies

compile group: 'androidx.car', name: 'car', version: '1.0.0-alpha7'

  • groupId: androidx.car
  • artifactId: car
  • version: 1.0.0-alpha7

Artifact androidx.car:car:1.0.0-alpha7 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.car:car com.android.support:car

Overview

Class to build a list item with SeekBar.

An item supports primary action and supplemental action(s).

An item visually composes of 3 parts; each part may contain multiple views.

  • Primary Action: represented by an icon of following types.
    • Primary Icon - icon size could be large or small.
    • No Icon - no icon is shown.
    • Empty Icon - Seekbar offsets start space as if there was an icon.
  • Seekbar: with optional Text.
  • Supplemental Action: presented by an icon of following types; aligned to the end of item.
    • Supplemental Icon.
    • Supplemental Empty Icon - Seekbar offsets end space as if there was an icon.
SeekbarListItem binds data to SeekbarListItem.ViewHolder based on components selected.

When conflicting methods are called (e.g. setting primary action to both primary icon and no icon), the last called method wins. minimum value is set to 0.

Summary

Constructors
publicSeekbarListItem(Context context)

Methods
public static SeekbarListItem.ViewHoldercreateViewHolder(View itemView)

Creates a SeekbarListItem.ViewHolder.

protected ContextgetContext()

public intgetViewType()

Used by ListItemAdapter to choose layout to inflate for view holder.

protected voidonBind(SeekbarListItem.ViewHolder viewHolder)

Hides all views in SeekbarListItem.ViewHolder then applies ViewBinders to adjust view layout params.

protected voidresolveDirtyState()

Calculates the layout params for views in SeekbarListItem.ViewHolder.

public abstract voidsetEnabled(boolean enabled)

Sets the enabled state of the bound ListItem.ViewHolder.

public voidsetMax(int max)

Sets max value of seekbar.

public voidsetOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener listener)

Sets .

public voidsetPrimaryActionEmptyIcon()

Sets Primary Action to be empty icon.

public voidsetPrimaryActionIcon(Drawable drawable)

Sets Primary Action to be represented by an icon.

public voidsetPrimaryActionIcon(Icon icon)

Sets Primary Action to be represented by an icon.

public voidsetPrimaryActionIconListener(View.OnClickListener onClickListener)

Sets an OnClickListener for the icon representing the Primary Action.

public voidsetProgress(int progress)

Sets progress of seekbar.

public voidsetSecondaryProgress(int secondaryProgress)

Sets secondary progress of seekbar.

public voidsetSupplementalEmptyIcon(boolean seekbarOffsetDividerWidth)

Sets Supplemental Action to be empty icon.

public voidsetSupplementalIcon(Drawable drawable, boolean showSupplementalIconDivider)

Sets Supplemental Action to be represented by an Supplemental Icon.

public voidsetSupplementalIcon(Icon icon, boolean showSupplementalIconDivider)

Sets Supplemental Action to be represented by an Supplemental Icon.

public voidsetSupplementalIconListener(View.OnClickListener listener)

Sets OnClickListener for a Supplemental Icon.

public voidsetText(java.lang.CharSequence text)

Sets text that sits on top of seekbar.

from ListItem<VH>addViewBinder, addViewBinder, getShowDivider, isDirty, markClean, markDirty, onBind, removeViewBinder, setShowDivider
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public SeekbarListItem(Context context)

Methods

public static SeekbarListItem.ViewHolder createViewHolder(View itemView)

Creates a SeekbarListItem.ViewHolder.

public int getViewType()

Used by ListItemAdapter to choose layout to inflate for view holder.

public abstract void setEnabled(boolean enabled)

Sets the enabled state of the bound ListItem.ViewHolder.

All visible children views of ViewHolder should be set to enabled. Caller is responsible for notifying ListItemAdapter about data change.

Disabled items are usually styled at 50% opacity. Consider similar styling for consistency.

protected Context getContext()

public void setMax(int max)

Sets max value of seekbar.

public void setProgress(int progress)

Sets progress of seekbar.

public void setSecondaryProgress(int secondaryProgress)

Sets secondary progress of seekbar.

public void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener listener)

Sets .

public void setText(java.lang.CharSequence text)

Sets text that sits on top of seekbar.

Parameters:

text: text to be displayed, or null to clear the content.

protected void resolveDirtyState()

Calculates the layout params for views in SeekbarListItem.ViewHolder.

protected void onBind(SeekbarListItem.ViewHolder viewHolder)

Hides all views in SeekbarListItem.ViewHolder then applies ViewBinders to adjust view layout params.

public void setPrimaryActionIcon(Icon icon)

Sets Primary Action to be represented by an icon.

Parameters:

icon: An icon to set as primary action.

public void setPrimaryActionIcon(Drawable drawable)

Deprecated: Use SeekbarListItem.setPrimaryActionIcon(Icon).

Sets Primary Action to be represented by an icon.

Parameters:

drawable: the Drawable to set, or null to clear the content.

public void setPrimaryActionIconListener(View.OnClickListener onClickListener)

Sets an OnClickListener for the icon representing the Primary Action.

Parameters:

onClickListener: the listener to be set for the primary action icon.

public void setPrimaryActionEmptyIcon()

Sets Primary Action to be empty icon. Seekbar would have a start margin as if Primary Action were set as icon.

public void setSupplementalIcon(Icon icon, boolean showSupplementalIconDivider)

Sets Supplemental Action to be represented by an Supplemental Icon.

public void setSupplementalIcon(Drawable drawable, boolean showSupplementalIconDivider)

Deprecated: Use SeekbarListItem.setSupplementalIcon(Icon, boolean).

Sets Supplemental Action to be represented by an Supplemental Icon.

public void setSupplementalIconListener(View.OnClickListener listener)

Sets OnClickListener for a Supplemental Icon.

public void setSupplementalEmptyIcon(boolean seekbarOffsetDividerWidth)

Sets Supplemental Action to be empty icon. Seekbar would have an end margin as if Supplemental Action were set.

Source

/*
 * Copyright 2018 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.car.widget;

import static java.lang.annotation.RetentionPolicy.SOURCE;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;

import androidx.annotation.Dimension;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.car.R;
import androidx.car.util.CarUxRestrictionsUtils;
import androidx.car.uxrestrictions.CarUxRestrictions;
import androidx.car.uxrestrictions.OnUxRestrictionsChangedListener;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Guideline;

import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.List;

/**
 * Class to build a list item with {@link SeekBar}.
 *
 * <p>An item supports primary action and supplemental action(s).
 *
 * <p>An item visually composes of 3 parts; each part may contain multiple views.
 * <ul>
 *     <li>{@code Primary Action}: represented by an icon of following types.
 *     <ul>
 *         <li>Primary Icon - icon size could be large or small.
 *         <li>No Icon - no icon is shown.
 *         <li>Empty Icon - {@code Seekbar} offsets start space as if there was an icon.
 *     </ul>
 *     <li>{@code Seekbar}: with optional {@code Text}.
 *     <li>{@code Supplemental Action}: presented by an icon of following types; aligned to
 *     the end of item.
 *     <ul>
 *         <li>Supplemental Icon.
 *         <li>Supplemental Empty Icon - {@code Seekbar} offsets end space as if there was an icon.
 *     </ul>
 * </ul>
 *
 * {@code SeekbarListItem} binds data to {@link ViewHolder} based on components selected.
 *
 * <p>When conflicting methods are called (e.g. setting primary action to both primary icon and
 * no icon), the last called method wins.
 *
 * {@code minimum value} is set to 0.
 */
public class SeekbarListItem extends ListItem<SeekbarListItem.ViewHolder> {

    @Retention(SOURCE)
    @IntDef({
            PRIMARY_ACTION_TYPE_NO_ICON, PRIMARY_ACTION_TYPE_EMPTY_ICON,
            PRIMARY_ACTION_TYPE_SMALL_ICON})
    private @interface PrimaryActionType {}

    private static final int PRIMARY_ACTION_TYPE_NO_ICON = 0;
    private static final int PRIMARY_ACTION_TYPE_EMPTY_ICON = 1;
    private static final int PRIMARY_ACTION_TYPE_SMALL_ICON = 2;

    @Retention(SOURCE)
    @IntDef({SUPPLEMENTAL_ACTION_NO_ACTION, SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON,
            SUPPLEMENTAL_ACTION_SUPPLEMENTAL_EMPTY_ICON,
            SUPPLEMENTAL_ACTION_SUPPLEMENTAL_EMPTY_ICON_WITH_DIVIDER})
    private @interface SupplementalActionType {}

    private static final int SUPPLEMENTAL_ACTION_NO_ACTION = 0;
    private static final int SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON = 1;
    private static final int SUPPLEMENTAL_ACTION_SUPPLEMENTAL_EMPTY_ICON = 2;
    private static final int SUPPLEMENTAL_ACTION_SUPPLEMENTAL_EMPTY_ICON_WITH_DIVIDER = 3;

    private final Context mContext;
    private boolean mIsEnabled = true;

    private final List<ViewBinder<ViewHolder>> mBinders = new ArrayList<>();

    @PrimaryActionType private int mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
    private Icon mPrimaryActionIcon;
    private Drawable mPrimaryActionIconDrawable;
    private View.OnClickListener mPrimaryActionIconOnClickListener;

    @Nullable private CharSequence mText;

    private int mMax;
    private int mProgress;
    private int mSecondaryProgress;
    private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener;

    @Dimension
    private final int mSupplementalGuidelineBegin;

    @SupplementalActionType private int mSupplementalActionType = SUPPLEMENTAL_ACTION_NO_ACTION;
    private Icon mSupplementalIcon;
    private Drawable mSupplementalIconDrawable;
    private View.OnClickListener mSupplementalIconOnClickListener;
    private boolean mShowSupplementalIconDivider;

    /**
     * Creates a {@link SeekbarListItem.ViewHolder}.
     */
    @NonNull
    public static ViewHolder createViewHolder(View itemView) {
        return new ViewHolder(itemView);
    }

    public SeekbarListItem(@NonNull Context context) {
        mContext = context;
        mSupplementalGuidelineBegin = mContext.getResources().getDimensionPixelSize(
                R.dimen.car_list_item_supplemental_guideline_top);
        markDirty();
    }

    /**
     * Used by {@link ListItemAdapter} to choose layout to inflate for view holder.
     */
    @Override
    public int getViewType() {
        return ListItemAdapter.LIST_ITEM_TYPE_SEEKBAR;
    }

    @Override
    public void setEnabled(boolean enabled) {
        mIsEnabled = enabled;
    }

    @NonNull
    protected Context getContext() {
        return mContext;
    }

    /**
     * Sets max value of seekbar.
     */
    public void setMax(int max) {
        mMax = max;
        markDirty();
    }

    /**
     * Sets progress of seekbar.
     */
    public void setProgress(int progress) {
        mProgress = progress;
        markDirty();
    }

    /**
     * Sets secondary progress of seekbar.
     */
    public void setSecondaryProgress(int secondaryProgress) {
        mSecondaryProgress = secondaryProgress;
        markDirty();
    }

    /**
     * Sets {@link SeekBar.OnSeekBarChangeListener}.
     */
    public void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener listener) {
        mOnSeekBarChangeListener = listener;
        markDirty();
    }

    /**
     * Sets text that sits on top of seekbar.
     *
     * @param text text to be displayed, or {@code null} to clear the content.
     */
    public void setText(@Nullable CharSequence text) {
        mText = text;
        markDirty();
    }

    /**
     * Calculates the layout params for views in {@link ViewHolder}.
     */
    @Override
    protected void resolveDirtyState() {
        mBinders.clear();

        // Create binders that adjust layout params of each view.
        setPrimaryAction();
        setSeekBarAndText();
        setSupplementalAction();
    }

    /**
     * Hides all views in {@link ViewHolder} then applies ViewBinders to adjust view layout params.
     */
    @Override
    protected void onBind(ViewHolder viewHolder) {
        // Hide all subviews then apply view binders to adjust subviews.
        hideSubViews(viewHolder);
        for (ViewBinder binder : mBinders) {
            binder.bind(viewHolder);
        }

        for (View v : viewHolder.getWidgetViews()) {
            v.setEnabled(mIsEnabled);
        }
    }

    private void hideSubViews(ViewHolder vh) {
        for (View v : vh.getWidgetViews()) {
            v.setVisibility(View.GONE);
        }
        // SeekBar is always visible.
        vh.getSeekBar().setVisibility(View.VISIBLE);
    }

    private void setPrimaryAction() {
        setPrimaryActionLayout();
        setPrimaryActionContent();
    }

    private void setSeekBarAndText() {
        setSeekBarAndTextContent();
        setSeekBarAndTextLayout();
    }

    private void setPrimaryActionLayout() {
        switch (mPrimaryActionType) {
            case PRIMARY_ACTION_TYPE_NO_ICON:
            case PRIMARY_ACTION_TYPE_EMPTY_ICON:
                // Do nothing.
                break;
            case PRIMARY_ACTION_TYPE_SMALL_ICON:
                mBinders.add(vh -> {
                    ConstraintLayout.LayoutParams layoutParams =
                            (ConstraintLayout.LayoutParams) vh.getPrimaryIcon().getLayoutParams();

                    if (TextUtils.isEmpty(mText)) {
                        // If there is no text, then the icon should be vertically centered.
                        layoutParams.verticalBias = 0.5f;
                        layoutParams.topMargin = 0;
                    } else {
                        // Align the icon to  the opt of the parent. This allows the topMargin to
                        // shift it down relative to the top.
                        layoutParams.verticalBias = 0f;

                        Resources res = mContext.getResources();

                        // Set icon top margin so that the icon remains in the same position it
                        // would've been in for non-long-text item, namely so that the center
                        // line of icon matches that of line item.
                        int itemHeight = res.getDimensionPixelSize(
                                R.dimen.car_double_line_list_item_height);
                        int iconSize = res.getDimensionPixelSize(R.dimen.car_primary_icon_size);
                        layoutParams.topMargin = (itemHeight - iconSize) / 2;
                    }

                    vh.getPrimaryIcon().requestLayout();
                });
                break;
            default:
                throw new IllegalStateException("Unknown primary action type.");
        }
    }

    private void setPrimaryActionContent() {
        switch (mPrimaryActionType) {
            case PRIMARY_ACTION_TYPE_NO_ICON:
            case PRIMARY_ACTION_TYPE_EMPTY_ICON:
                // Do nothing.
                break;
            case PRIMARY_ACTION_TYPE_SMALL_ICON:
                mBinders.add(vh -> {
                    vh.getPrimaryIcon().setVisibility(View.VISIBLE);

                    if (mPrimaryActionIcon != null) {
                        mPrimaryActionIcon.loadDrawableAsync(getContext(),
                                drawable -> vh.getPrimaryIcon().setImageDrawable(drawable),
                                new Handler(Looper.getMainLooper()));
                    } else {
                        vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
                    }
                    vh.getPrimaryIcon().setOnClickListener(
                            mPrimaryActionIconOnClickListener);
                    vh.getPrimaryIcon().setClickable(
                            mPrimaryActionIconOnClickListener != null);
                });
                break;
            default:
                throw new IllegalStateException("Unknown primary action type.");
        }
    }

    private void setSeekBarAndTextContent() {
        mBinders.add(vh -> {
            vh.getSeekBar().setMax(mMax);
            vh.getSeekBar().setProgress(mProgress);
            vh.getSeekBar().setSecondaryProgress(mSecondaryProgress);
            vh.getSeekBar().setOnSeekBarChangeListener(mOnSeekBarChangeListener);

            if (!TextUtils.isEmpty(mText)) {
                vh.getText().setVisibility(View.VISIBLE);
                vh.getText().setText(mText);

                // If there is a title, the ensure the guideline is a fixed
                vh.getSupplementalGuideline().setGuidelineBegin(
                        mSupplementalGuidelineBegin);
                vh.getSupplementalGuideline().setGuidelinePercent(
                        ConstraintLayout.LayoutParams.UNSET);
            } else {
                // Otherwise, the guideline should center the supplemental icon.
                vh.getSupplementalGuideline().setGuidelineBegin(
                        ConstraintLayout.LayoutParams.UNSET);
                vh.getSupplementalGuideline().setGuidelinePercent(0.5f);
            }
        });
    }

    private void setSeekBarAndTextLayout() {
        mBinders.add(vh -> {
            // Set start and end margin of text and seek bar.
            int marginStart = getSeekBarAndTextMarginStart();
            int marginEnd = getSeekBarAndTextMarginEnd();

            ViewGroup.MarginLayoutParams textLayoutParams =
                    (ViewGroup.MarginLayoutParams) vh.getText().getLayoutParams();
            textLayoutParams.setMarginStart(marginStart);
            textLayoutParams.setMarginEnd(marginEnd);
            vh.getText().requestLayout();

            ConstraintLayout.LayoutParams seekBarLayoutParams =
                    (ConstraintLayout.LayoutParams) vh.getSeekBar().getLayoutParams();
            seekBarLayoutParams.setMarginStart(marginStart);
            seekBarLayoutParams.setMarginEnd(marginEnd);

            if (TextUtils.isEmpty(mText)) {
                // If there is no text, set the vertical bias to 0.5 so that the seekbar is
                // vertically centered.
                seekBarLayoutParams.verticalBias = 0.5f;
                seekBarLayoutParams.bottomMargin = 0;
            } else {
                // If there is text, set the vertical bias to 1 so that it is aligned to the bottom
                // of the parent view, allowing the bottom margin to take effect.
                seekBarLayoutParams.verticalBias = 1f;
                seekBarLayoutParams.bottomMargin =
                        mContext.getResources().getDimensionPixelSize(R.dimen.car_padding_1);
            }

            vh.getSeekBar().requestLayout();
        });
    }

    /**
     * Returns the starting margin that should be used for the text and seekbar views. This value
     * will depend on whether or not a primary icon is currently visible.
     */
    private int getSeekBarAndTextMarginStart() {
        switch (mPrimaryActionType) {
            case PRIMARY_ACTION_TYPE_NO_ICON:
                return mContext.getResources().getDimensionPixelSize(R.dimen.car_keyline_1);
            case PRIMARY_ACTION_TYPE_EMPTY_ICON:
            case PRIMARY_ACTION_TYPE_SMALL_ICON:
                return mContext.getResources().getDimensionPixelSize(R.dimen.car_keyline_3);
            default:
                throw new IllegalStateException("Unknown primary action type.");
        }
    }

    /**
     * Returns the end margin that should be used for the text and seekbar views. This value
     * will depend on whether or not supplemental icons are visible.
     */
    private int getSeekBarAndTextMarginEnd() {
        int endMargin = 0;
        switch (mSupplementalActionType) {
            case SUPPLEMENTAL_ACTION_NO_ACTION:
                // Aligned to parent end.
                return 0;
            case SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON:
                // Add padding to account for the supplemental icon.
                return mContext.getResources().getDimensionPixelSize(R.dimen.car_padding_4);
            case SUPPLEMENTAL_ACTION_SUPPLEMENTAL_EMPTY_ICON_WITH_DIVIDER:
                // Align to parent end with a margin as if the icon and an optional divider were
                // present. We do this by adding the divider padding to icon, and width of divider.
                endMargin += mContext.getResources().getDimensionPixelSize(
                         R.dimen.car_padding_4);
                endMargin += mContext.getResources().getDimensionPixelSize(
                         R.dimen.car_vertical_line_divider_width);
                // Fall through.
            case SUPPLEMENTAL_ACTION_SUPPLEMENTAL_EMPTY_ICON:
                // Add view padding, width of icon, and icon end margin.
                endMargin += mContext.getResources().getDimensionPixelSize(
                         R.dimen.car_padding_4);
                endMargin += mContext.getResources().getDimensionPixelSize(
                         R.dimen.car_primary_icon_size);

                return endMargin;
            default:
                throw new IllegalStateException("Unknown supplemental action type.");
        }
    }

    private void setSupplementalAction() {
        switch (mSupplementalActionType) {
            case SUPPLEMENTAL_ACTION_NO_ACTION:
            case SUPPLEMENTAL_ACTION_SUPPLEMENTAL_EMPTY_ICON_WITH_DIVIDER:
            case SUPPLEMENTAL_ACTION_SUPPLEMENTAL_EMPTY_ICON:
                break;
            case SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON:
                mBinders.add(vh -> {
                    vh.getSupplementalIcon().setVisibility(View.VISIBLE);
                    if (mShowSupplementalIconDivider) {
                        vh.getSupplementalIconDivider().setVisibility(View.VISIBLE);
                    }

                    if (mSupplementalIcon != null) {
                        mSupplementalIcon.loadDrawableAsync(getContext(),
                                drawable -> vh.getSupplementalIcon().setImageDrawable(drawable),
                                new Handler(Looper.getMainLooper()));
                    } else {
                        vh.getSupplementalIcon().setImageDrawable(mSupplementalIconDrawable);
                    }

                    vh.getSupplementalIcon().setOnClickListener(
                            mSupplementalIconOnClickListener);
                    vh.getSupplementalIcon().setClickable(
                            mSupplementalIconOnClickListener != null);
                });
                break;
            default:
                throw new IllegalStateException("Unknown supplemental action type.");
        }
    }

    /**
     * Sets {@code Primary Action} to be represented by an icon.
     *
     * @param icon An icon to set as primary action.
     */
    public void setPrimaryActionIcon(@NonNull Icon icon) {
        mPrimaryActionType = PRIMARY_ACTION_TYPE_SMALL_ICON;
        mPrimaryActionIcon = icon;
        markDirty();
    }

    /**
     * Sets {@code Primary Action} to be represented by an icon.
     *
     * @param iconResId the resource identifier of the drawable.
     *
     * @deprecated Use {@link #setPrimaryActionIcon(Icon)}.
     */
    @Deprecated
    public void setPrimaryActionIcon(@DrawableRes int iconResId) {
        setPrimaryActionIcon(Icon.createWithResource(getContext(), iconResId));
    }

    /**
     * Sets {@code Primary Action} to be represented by an icon.
     *
     * @param drawable the Drawable to set, or null to clear the content.
     *
     * @deprecated Use {@link #setPrimaryActionIcon(Icon)}.
     */
    @Deprecated
    public void setPrimaryActionIcon(Drawable drawable) {
        mPrimaryActionType = PRIMARY_ACTION_TYPE_SMALL_ICON;
        mPrimaryActionIconDrawable = drawable;
        markDirty();
    }

    /**
     * Sets an {@code OnClickListener} for the icon representing the {@code Primary Action}.
     *
     * @param onClickListener the listener to be set for the primary action icon.
     */
    public void setPrimaryActionIconListener(View.OnClickListener onClickListener) {
        mPrimaryActionIconOnClickListener = onClickListener;
        markDirty();
    }

    /**
     * Sets {@code Primary Action} to be empty icon.
     *
     * {@code Seekbar} would have a start margin as if {@code Primary Action} were set as icon.
     */
    public void setPrimaryActionEmptyIcon() {
        mPrimaryActionType = PRIMARY_ACTION_TYPE_EMPTY_ICON;

        markDirty();
    }

    /**
     * Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}.
     */
    public void setSupplementalIcon(@NonNull Icon icon, boolean showSupplementalIconDivider) {
        mSupplementalActionType = SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON;
        mSupplementalIcon = icon;
        mShowSupplementalIconDivider = showSupplementalIconDivider;
        markDirty();
    }

    /**
     * Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}.
     *
     * @deprecated Use {@link #setSupplementalIcon(Icon, boolean)}.
     */
    @Deprecated
    public void setSupplementalIcon(@DrawableRes int iconResId,
            boolean showSupplementalIconDivider) {
        setSupplementalIcon(Icon.createWithResource(getContext(), iconResId),
                showSupplementalIconDivider);
    }

    /**
     * Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}.
     *
     * @deprecated Use {@link #setSupplementalIcon(Icon, boolean)}.
     */
    @Deprecated
    public void setSupplementalIcon(@NonNull Drawable drawable,
            boolean showSupplementalIconDivider) {
        mSupplementalActionType = SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON;
        mSupplementalIconDrawable = drawable;
        mShowSupplementalIconDivider = showSupplementalIconDivider;
        markDirty();
    }

    /**
     * Sets {@code OnClickListener} for a {@code Supplemental Icon}.
     */
    public void setSupplementalIconListener(View.OnClickListener listener) {
        mSupplementalIconOnClickListener = listener;

        markDirty();
    }

    /**
     * Sets {@code Supplemental Action} to be empty icon.
     *
     * {@code Seekbar} would have an end margin as if {@code Supplemental Action} were set.
     */
    public void setSupplementalEmptyIcon(boolean seekbarOffsetDividerWidth) {
        mSupplementalActionType = seekbarOffsetDividerWidth
                ? SUPPLEMENTAL_ACTION_SUPPLEMENTAL_EMPTY_ICON_WITH_DIVIDER
                : SUPPLEMENTAL_ACTION_SUPPLEMENTAL_EMPTY_ICON;
        markDirty();
    }

    /**
     * Holds views of SeekbarListItem.
     */
    public static final class ViewHolder extends ListItem.ViewHolder implements
            OnUxRestrictionsChangedListener {

        private final View[] mWidgetViews;

        private ViewGroup mContainerLayout;

        private ImageView mPrimaryIcon;

        private TextView mText;
        private SeekBar mSeekBar;

        private Guideline mSupplementalGuideline;
        private View mSupplementalIconDivider;
        private ImageView mSupplementalIcon;

        public ViewHolder(View itemView) {
            super(itemView);

            mContainerLayout = itemView.findViewById(R.id.container);

            mPrimaryIcon = itemView.findViewById(R.id.primary_icon);

            mText = itemView.findViewById(R.id.seek_bar_text);
            mSeekBar = itemView.findViewById(R.id.seek_bar);

            mSupplementalGuideline = itemView.findViewById(R.id.supplemental_icon_guideline);
            mSupplementalIcon = itemView.findViewById(R.id.supplemental_icon);
            mSupplementalIconDivider = itemView.findViewById(R.id.supplemental_icon_divider);

            int minTouchSize = itemView.getContext().getResources()
                    .getDimensionPixelSize(R.dimen.car_touch_target_size);

            MinTouchTargetHelper.ensureThat(mSupplementalIcon)
                    .hasMinTouchSize(minTouchSize);

            // Each line groups relevant child views in an effort to help keep this view array
            // updated with actual child views in the ViewHolder.
            mWidgetViews = new View[]{
                    mPrimaryIcon,
                    mSeekBar, mText,
                    mSupplementalIcon, mSupplementalIconDivider};
        }

        /**
         * Updates child views with current car UX restrictions.
         *
         * <p>{@code Text} might be truncated to meet length limit required by regulation.
         *
         * @param restrictionsInfo current car UX restrictions.
         */
        @Override
        public void onUxRestrictionsChanged(CarUxRestrictions restrictionsInfo) {
            CarUxRestrictionsUtils.apply(itemView.getContext(), restrictionsInfo, getText());
        }

        @NonNull
        public ViewGroup getContainerLayout() {
            return mContainerLayout;
        }

        @NonNull
        public ImageView getPrimaryIcon() {
            return mPrimaryIcon;
        }

        @NonNull
        public TextView getText() {
            return mText;
        }

        @NonNull
        public SeekBar getSeekBar() {
            return mSeekBar;
        }

        @NonNull
        public ImageView getSupplementalIcon() {
            return mSupplementalIcon;
        }

        @NonNull
        public View getSupplementalIconDivider() {
            return mSupplementalIconDivider;
        }

        /** Returns the guideline that the supplemental icon is centered upon. */
        @NonNull
        Guideline getSupplementalGuideline() {
            return mSupplementalGuideline;
        }

        @NonNull
        View[] getWidgetViews() {
            return mWidgetViews;
        }
    }
}