public final class

ActionListItem

extends ListItem<ActionListItem.ViewHolder>

 java.lang.Object

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

↳androidx.car.widget.ActionListItem

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 that has up to two actions.

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 - Text offsets start space as if there was an icon.
  • Text: supports any combination of the following text views.
    • Title
    • Body
  • Supplemental Action: Up to two actions.

ActionListItem binds data to ActionListItem.ViewHolder based on components selected.

When conflicting setter methods are called (e.g. setting primary action to both primary icon and no icon), the last called method wins.

Summary

Fields
public static final intPRIMARY_ACTION_ICON_SIZE_LARGE

Large sized icon is as tall as a list item with only title text.

public static final intPRIMARY_ACTION_ICON_SIZE_MEDIUM

Medium sized icon is slightly bigger than SMALL ones.

public static final intPRIMARY_ACTION_ICON_SIZE_SMALL

Small sized icon is the mostly commonly used size.

Constructors
publicActionListItem(Context context)

Methods
public static ActionListItem.ViewHoldercreateViewHolder(View itemView)

Creates a ActionListItem.ViewHolder.

protected ContextgetContext()

public intgetViewType()

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

public voidonBind(ActionListItem.ViewHolder viewHolder)

Resets all views in ActionListItem.ViewHolder then applies ViewBinders to adjust view layout params.

protected voidresolveDirtyState()

Calculates the layout params for views in ActionListItem.ViewHolder.

public voidsetAction(java.lang.String text, boolean showDivider, View.OnClickListener listener)

Sets the primary action of this ListItem.

public voidsetActionBorderless(boolean isActionBorderless)

Sets whether or not the actions should be styled as borderless.

public voidsetActions(java.lang.String primaryActionText, boolean showPrimaryActionDivider, View.OnClickListener primaryActionOnClickListener, java.lang.String secondaryActionText, boolean showSecondaryActionDivider, View.OnClickListener secondaryActionOnClickListener)

Sets the primary and secondary actions for this ListItem.

public voidsetBody(java.lang.CharSequence body)

Sets the body text of item.

public abstract voidsetEnabled(boolean enabled)

Sets the enabled state of the bound ListItem.ViewHolder.

public voidsetOnClickListener(View.OnClickListener listener)

Sets of ActionListItem.

public voidsetPrimaryAction(java.lang.String primaryActionText, boolean showPrimaryActionDivider, View.OnClickListener primaryActionOnClickListener)

Sets the primary action of this ListItem.

public voidsetPrimaryActionEmptyIcon()

Sets Primary Action to be empty icon.

public voidsetPrimaryActionIcon(Drawable drawable, int size)

Sets Primary Action to be represented by an icon.

public voidsetPrimaryActionIcon(int iconResId, int size)

Sets Primary Action to be represented by an icon.

public voidsetPrimaryActionNoIcon()

Sets Primary Action to have no icon.

public voidsetSecondaryAction(java.lang.String secondaryActionText, boolean showSecondaryActionDivider, View.OnClickListener secondaryActionOnClickListener)

Sets the secondary action of this ListItem.

public voidsetTitle(java.lang.CharSequence title)

Sets the title of item.

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

Fields

public static final int PRIMARY_ACTION_ICON_SIZE_SMALL

Small sized icon is the mostly commonly used size. It's the same as supplemental action icon.

public static final int PRIMARY_ACTION_ICON_SIZE_MEDIUM

Medium sized icon is slightly bigger than SMALL ones. It is intended for profile pictures (avatar), in which case caller is responsible for passing in a circular image.

public static final int PRIMARY_ACTION_ICON_SIZE_LARGE

Large sized icon is as tall as a list item with only title text. It is intended for album art.

Constructors

public ActionListItem(Context context)

Methods

public static ActionListItem.ViewHolder createViewHolder(View itemView)

Creates a ActionListItem.ViewHolder.

public int getViewType()

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

public void onBind(ActionListItem.ViewHolder viewHolder)

Resets all views in ActionListItem.ViewHolder then applies ViewBinders to adjust view layout params.

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 void resolveDirtyState()

Calculates the layout params for views in ActionListItem.ViewHolder.

protected Context getContext()

public void setOnClickListener(View.OnClickListener listener)

Sets of ActionListItem.

public void setPrimaryActionIcon(int iconResId, int size)

Sets Primary Action to be represented by an icon.

Parameters:

iconResId: the resource identifier of the drawable.
size: The size of the icon. Must be one of ActionListItem.PRIMARY_ACTION_ICON_SIZE_SMALL, ActionListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM, or ActionListItem.PRIMARY_ACTION_ICON_SIZE_LARGE.

public void setPrimaryActionIcon(Drawable drawable, int size)

Sets Primary Action to be represented by an icon.

Parameters:

drawable: the Drawable to set, or null to clear the content.
size: The size of the icon. Must be one of ActionListItem.PRIMARY_ACTION_ICON_SIZE_SMALL, ActionListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM, or ActionListItem.PRIMARY_ACTION_ICON_SIZE_LARGE.

public void setPrimaryActionEmptyIcon()

Sets Primary Action to be empty icon.

Text would have a start margin as if Primary Action were set to primary icon.

public void setPrimaryActionNoIcon()

Sets Primary Action to have no icon. Text would align to the start of item.

public void setTitle(java.lang.CharSequence title)

Sets the title of item.

Title text is limited to one line, and ellipsizes at the end.

Parameters:

title: text to display as title.

public void setBody(java.lang.CharSequence body)

Sets the body text of item.

Text beyond length required by regulation will be truncated. Defaults Title text as the primary.

Parameters:

body: text to be displayed.

public void setAction(java.lang.String text, boolean showDivider, View.OnClickListener listener)

Deprecated: Use ActionListItem.setPrimaryAction(String, boolean, View.OnClickListener) or ActionListItem.setSecondaryAction(String, boolean, View.OnClickListener) instead to individually set the actions.

Sets the primary action of this ListItem.

Parameters:

text: button text to display.
showDivider: whether to display a vertical bar that separates Text and Action Button.
listener: the callback that will run when action button is clicked.

public void setActions(java.lang.String primaryActionText, boolean showPrimaryActionDivider, View.OnClickListener primaryActionOnClickListener, java.lang.String secondaryActionText, boolean showSecondaryActionDivider, View.OnClickListener secondaryActionOnClickListener)

Deprecated: Use ActionListItem.setPrimaryAction(String, boolean, View.OnClickListener) and ActionListItem.setSecondaryAction(String, boolean, View.OnClickListener) to set both actions.

Sets the primary and secondary actions for this ListItem.

Parameters:

primaryActionText: The primary action text.
showPrimaryActionDivider: Whether or not to show a divider before the primary action.
primaryActionOnClickListener: The listener to be invoked when the primary action is triggered.
secondaryActionText: The secondary action text.
showSecondaryActionDivider: Whether or not to show a divider before the secondary action.
secondaryActionOnClickListener: The listener to be invoked when the secondary action is triggered.

public void setPrimaryAction(java.lang.String primaryActionText, boolean showPrimaryActionDivider, View.OnClickListener primaryActionOnClickListener)

Sets the primary action of this ListItem.

Parameters:

primaryActionText: Action text to display.
showPrimaryActionDivider: Whether or not to display a vertical bar before the primary action.
primaryActionOnClickListener: The callback that will run when the action is clicked.

public void setSecondaryAction(java.lang.String secondaryActionText, boolean showSecondaryActionDivider, View.OnClickListener secondaryActionOnClickListener)

Sets the secondary action of this ListItem.

The secondary action will appear before the primary action if both are set.

Parameters:

secondaryActionText: Action text to display.
showSecondaryActionDivider: Whether or not to display a vertical bar before the secondary action.
secondaryActionOnClickListener: The callback that will run when the action is clicked.

public void setActionBorderless(boolean isActionBorderless)

Sets whether or not the actions should be styled as borderless.

By default, this value is true.

Parameters:

isActionBorderless: true if the actions should be borderless. false otherwise.

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.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.DimenRes;
import androidx.annotation.Dimension;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.car.R;
import androidx.car.util.CarUxRestrictionsUtils;
import androidx.car.uxrestrictions.CarUxRestrictions;
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 that has up to two actions.
 *
 * <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 Text} offsets start space as if there was an icon.
 *     </ul>
 *     <li>{@code Text}: supports any combination of the following text views.
 *     <ul>
 *         <li>Title
 *         <li>Body
 *     </ul>
 *     <li>{@code Supplemental Action}: Up to two actions.
 * </ul>
 *
 * <p>{@code ActionListItem} binds data to {@link ViewHolder} based on components selected.
 *
 * <p>When conflicting setter methods are called (e.g. setting primary action to both primary icon
 * and no icon), the last called method wins.
 */
public final class ActionListItem extends ListItem<ActionListItem.ViewHolder> {
    @Retention(SOURCE)
    @IntDef({PRIMARY_ACTION_ICON_SIZE_SMALL, PRIMARY_ACTION_ICON_SIZE_MEDIUM,
            PRIMARY_ACTION_ICON_SIZE_LARGE})
    private @interface PrimaryActionIconSize {}

    /**
     * Small sized icon is the mostly commonly used size. It's the same as supplemental action icon.
     */
    public static final int PRIMARY_ACTION_ICON_SIZE_SMALL = 0;
    /**
     * Medium sized icon is slightly bigger than {@code SMALL} ones. It is intended for profile
     * pictures (avatar), in which case caller is responsible for passing in a circular image.
     */
    public static final int PRIMARY_ACTION_ICON_SIZE_MEDIUM = 1;
    /**
     * Large sized icon is as tall as a list item with only {@code title} text. It is intended for
     * album art.
     */
    public static final int PRIMARY_ACTION_ICON_SIZE_LARGE = 2;

    @Retention(SOURCE)
    @IntDef({
            PRIMARY_ACTION_TYPE_NO_ICON, PRIMARY_ACTION_TYPE_EMPTY_ICON,
            PRIMARY_ACTION_TYPE_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_ICON = 2;

    @PrimaryActionType private int mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
    private Drawable mPrimaryActionIconDrawable;
    @PrimaryActionIconSize private int mPrimaryActionIconSize = PRIMARY_ACTION_ICON_SIZE_SMALL;

    private final Context mContext;
    private boolean mIsEnabled = true;
    private final List<ViewBinder<ViewHolder>> mBinders = new ArrayList<>();

    private CharSequence mTitle;
    private CharSequence mBody;

    @Dimension
    private final int mSupplementalGuidelineBegin;

    private boolean mIsActionBorderless = true;
    private String mPrimaryActionText;
    private View.OnClickListener mPrimaryActionOnClickListener;
    private boolean mShowPrimaryActionDivider;

    private String mSecondaryActionText;
    private View.OnClickListener mSecondaryActionOnClickListener;
    private boolean mShowSecondaryActionDivider;

    private View.OnClickListener mOnClickListener;

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

    public ActionListItem(@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_ACTION;
    }

    /**
     * Resets all views in {@link ActionListItem.ViewHolder} then applies ViewBinders to
     * adjust view layout params.
     */
    @Override
    public void onBind(ActionListItem.ViewHolder viewHolder) {
        for (View v : viewHolder.getWidgetViews()) {
            v.setEnabled(mIsEnabled);
            v.setVisibility(View.GONE);
        }

        // ActionListItem supports clicking on the item so we also update the entire itemView.
        viewHolder.itemView.setEnabled(mIsEnabled);

        for (ViewBinder<ViewHolder> binder : mBinders) {
            binder.bind(viewHolder);
        }
    }

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

    /**
     * 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();
        setText();
        setSupplementalActions();
        setOnClickListener();
    }

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

    private void setPrimaryAction() {
        setPrimaryIconContent();
        setPrimaryIconLayout();
    }

    private void setText() {
        setTextContent();
        setTextVerticalMargin();
        setTextStartMargin();
    }

    private void setOnClickListener() {
        mBinders.add(vh -> {
            vh.itemView.setOnClickListener(mOnClickListener);
            vh.itemView.setClickable(mOnClickListener != null);
        });
    }

    private void setPrimaryIconContent() {
        switch (mPrimaryActionType) {
            case PRIMARY_ACTION_TYPE_ICON:
                mBinders.add(vh -> {
                    vh.getPrimaryIcon().setVisibility(View.VISIBLE);
                    vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
                });
                break;
            case PRIMARY_ACTION_TYPE_EMPTY_ICON:
            case PRIMARY_ACTION_TYPE_NO_ICON:
                // Do nothing.
                break;
            default:
                throw new IllegalStateException("Unknown primary action type.");
        }
    }

    /**
     * Sets the size, start margin, and vertical position of primary icon.
     *
     * <p>Large icon will have no start margin, and always align center vertically.
     *
     * <p>Small/medium icon will have start margin, and uses a top margin such that it is "pinned"
     * at the same position in list item regardless of item height.
     */
    private void setPrimaryIconLayout() {
        if (mPrimaryActionType == PRIMARY_ACTION_TYPE_EMPTY_ICON
                || mPrimaryActionType == PRIMARY_ACTION_TYPE_NO_ICON) {
            return;
        }

        // Size of icon.
        @DimenRes int sizeResId;
        switch (mPrimaryActionIconSize) {
            case PRIMARY_ACTION_ICON_SIZE_SMALL:
                sizeResId = R.dimen.car_primary_icon_size;
                break;
            case PRIMARY_ACTION_ICON_SIZE_MEDIUM:
                sizeResId = R.dimen.car_avatar_icon_size;
                break;
            case PRIMARY_ACTION_ICON_SIZE_LARGE:
                sizeResId = R.dimen.car_single_line_list_item_height;
                break;
            default:
                throw new IllegalStateException("Unknown primary action icon size.");
        }

        int iconSize = mContext.getResources().getDimensionPixelSize(sizeResId);

        // Start margin of icon.
        int startMargin;
        switch (mPrimaryActionIconSize) {
            case PRIMARY_ACTION_ICON_SIZE_SMALL:
            case PRIMARY_ACTION_ICON_SIZE_MEDIUM:
                startMargin = mContext.getResources().getDimensionPixelSize(R.dimen.car_keyline_1);
                break;
            case PRIMARY_ACTION_ICON_SIZE_LARGE:
                startMargin = 0;
                break;
            default:
                throw new IllegalStateException("Unknown primary action icon size.");
        }

        mBinders.add(vh -> {
            ConstraintLayout.LayoutParams layoutParams =
                    (ConstraintLayout.LayoutParams) vh.getPrimaryIcon().getLayoutParams();
            layoutParams.height = layoutParams.width = iconSize;
            layoutParams.setMarginStart(startMargin);

            if (mPrimaryActionIconSize == PRIMARY_ACTION_ICON_SIZE_LARGE) {
                // A large icon is always vertically centered.
                layoutParams.verticalBias = 0.5f;
                layoutParams.topMargin = 0;
            } else {
                // Align the icon to the top of the parent. This allows the topMargin to shift it
                // down relative to the top.
                layoutParams.verticalBias = 0f;

                // For all other icon sizes, the icon should be centered within the height of
                // car_double_line_list_item_height. Note: the actual height of the item can be
                // larger than this.
                int itemHeight = mContext.getResources().getDimensionPixelSize(
                        R.dimen.car_double_line_list_item_height);
                layoutParams.topMargin = (itemHeight - iconSize) / 2;
            }

            vh.getPrimaryIcon().requestLayout();
        });
    }

    private void setTextContent() {
        boolean hasTitle = !TextUtils.isEmpty(mTitle);
        boolean hasBody = !TextUtils.isEmpty(mBody);

        if (!hasTitle && !hasBody) {
            return;
        }

        mBinders.add(vh -> {
            if (hasTitle) {
                vh.getTitle().setVisibility(View.VISIBLE);
                vh.getTitle().setText(mTitle);
            }

            if (hasBody) {
                vh.getBody().setVisibility(View.VISIBLE);
                vh.getBody().setText(mBody);
            }

            if (hasTitle && !hasBody) {
                // If only title, then center the supplemental actions.
                vh.getSupplementalGuideline().setGuidelineBegin(
                        ConstraintLayout.LayoutParams.UNSET);
                vh.getSupplementalGuideline().setGuidelinePercent(0.5f);
            } else {
                // Otherwise, position it a fixed distance from the top.
                vh.getSupplementalGuideline().setGuidelinePercent(
                        ConstraintLayout.LayoutParams.UNSET);
                vh.getSupplementalGuideline().setGuidelineBegin(
                        mSupplementalGuidelineBegin);
            }
        });
    }

    /**
     * Sets start margin of text view depending on icon type.
     */
    private void setTextStartMargin() {
        @DimenRes int startMarginResId;
        switch (mPrimaryActionType) {
            case PRIMARY_ACTION_TYPE_NO_ICON:
                startMarginResId = R.dimen.car_keyline_1;
                break;
            case PRIMARY_ACTION_TYPE_EMPTY_ICON:
                startMarginResId = R.dimen.car_keyline_3;
                break;
            case PRIMARY_ACTION_TYPE_ICON:
                startMarginResId = mPrimaryActionIconSize == PRIMARY_ACTION_ICON_SIZE_LARGE
                        ? R.dimen.car_keyline_4
                        : R.dimen.car_keyline_3;  // Small and medium sized icon.
                break;
            default:
                throw new IllegalStateException("Unknown primary action type.");
        }
        int startMargin = mContext.getResources().getDimensionPixelSize(startMarginResId);
        mBinders.add(vh -> {
            ViewGroup.MarginLayoutParams titleLayoutParams =
                    (ViewGroup.MarginLayoutParams) vh.getTitle().getLayoutParams();
            titleLayoutParams.setMarginStart(startMargin);
            vh.getTitle().requestLayout();

            ViewGroup.MarginLayoutParams bodyLayoutParams =
                    (ViewGroup.MarginLayoutParams) vh.getBody().getLayoutParams();
            bodyLayoutParams.setMarginStart(startMargin);
            vh.getBody().requestLayout();
        });
    }

    /**
     * Sets top/bottom margins of {@code Title} and {@code Body}.
     */
    private void setTextVerticalMargin() {
        // Set all relevant fields in layout params to avoid carried over params when the item
        // gets bound to a recycled view holder.
        if (!TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mBody)) {
            // Title only - view is aligned center vertically by itself.
            mBinders.add(vh -> {
                ViewGroup.MarginLayoutParams layoutParams =
                        (ViewGroup.MarginLayoutParams) vh.getTitle().getLayoutParams();
                layoutParams.topMargin = 0;
                vh.getTitle().requestLayout();
            });
        } else if (TextUtils.isEmpty(mTitle) && !TextUtils.isEmpty(mBody)) {
            mBinders.add(vh -> {
                // Body uses top and bottom margin.
                int margin = mContext.getResources().getDimensionPixelSize(
                        R.dimen.car_padding_3);
                ViewGroup.MarginLayoutParams layoutParams =
                        (ViewGroup.MarginLayoutParams) vh.getBody().getLayoutParams();
                layoutParams.topMargin = margin;
                layoutParams.bottomMargin = margin;
                vh.getBody().requestLayout();
            });
        } else {
            mBinders.add(vh -> {
                Resources resources = mContext.getResources();
                int padding2 = resources.getDimensionPixelSize(R.dimen.car_padding_2);

                // Title has a top margin
                ViewGroup.MarginLayoutParams titleLayoutParams =
                        (ViewGroup.MarginLayoutParams) vh.getTitle().getLayoutParams();
                titleLayoutParams.topMargin = padding2;
                vh.getTitle().requestLayout();

                // Body is below title with no margin and has bottom margin.
                ViewGroup.MarginLayoutParams bodyLayoutParams =
                        (ViewGroup.MarginLayoutParams) vh.getBody().getLayoutParams();
                bodyLayoutParams.topMargin = 0;
                bodyLayoutParams.bottomMargin = padding2;
                vh.getBody().requestLayout();
            });
        }
    }

    /**
     * Sets up view(s) for supplemental action.
     */
    private void setSupplementalActions() {
        boolean hasPrimaryAction = !TextUtils.isEmpty(mPrimaryActionText);
        boolean hasSecondaryAction = !TextUtils.isEmpty(mSecondaryActionText);

        if (!hasPrimaryAction && !hasSecondaryAction) {
            return;
        }

        mBinders.add(vh -> {
            vh.setActionBorderless(mIsActionBorderless);

            if (hasSecondaryAction) {
                Button secondaryAction = vh.getSecondaryAction();

                secondaryAction.setVisibility(View.VISIBLE);
                if (mShowSecondaryActionDivider) {
                    vh.getSecondaryActionDivider().setVisibility(View.VISIBLE);
                }

                secondaryAction.setText(mSecondaryActionText);
                secondaryAction.setOnClickListener(mSecondaryActionOnClickListener);

                // Add spacing between the buttons if there is a primary action.
                int endMargin = hasPrimaryAction
                        ? mContext.getResources().getDimensionPixelSize(R.dimen.car_padding_4)
                        : 0;

                ViewGroup.MarginLayoutParams layoutParams =
                        (ViewGroup.MarginLayoutParams) secondaryAction.getLayoutParams();
                layoutParams.setMarginEnd(endMargin);
                secondaryAction.requestLayout();
            }

            if (hasPrimaryAction) {
                Button primaryAction = vh.getPrimaryAction();

                primaryAction.setVisibility(View.VISIBLE);
                if (mShowPrimaryActionDivider) {
                    vh.getPrimaryActionDivider().setVisibility(View.VISIBLE);
                }

                primaryAction.setText(mPrimaryActionText);
                primaryAction.setOnClickListener(mPrimaryActionOnClickListener);
            }
        });
    }

    /**
     * Sets {@link View.OnClickListener} of {@code ActionListItem}.
     */
    public void setOnClickListener(View.OnClickListener listener) {
        mOnClickListener = listener;
        markDirty();
    }

    /**
     * Sets {@code Primary Action} to be represented by an icon.
     *
     * @param iconResId the resource identifier of the drawable.
     * @param size The size of the icon. Must be one of {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
     *             {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM}, or
     *             {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
     */
    public void setPrimaryActionIcon(@DrawableRes int iconResId, @PrimaryActionIconSize int size) {
        setPrimaryActionIcon(mContext.getDrawable(iconResId), size);
    }

    /**
     * Sets {@code Primary Action} to be represented by an icon.
     *
     * @param drawable the Drawable to set, or null to clear the content.
     * @param size The size of the icon. Must be one of {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
     *             {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM}, or
     *             {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
     */
    public void setPrimaryActionIcon(@Nullable Drawable drawable, @PrimaryActionIconSize int size) {
        mPrimaryActionType = PRIMARY_ACTION_TYPE_ICON;
        mPrimaryActionIconDrawable = drawable;
        mPrimaryActionIconSize = size;
        markDirty();
    }

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

    /**
     * Sets {@code Primary Action} to have no icon. Text would align to the start of item.
     */
    public void setPrimaryActionNoIcon() {
        mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
        markDirty();
    }

    /**
     * Sets the title of item.
     *
     * <p>{@code Title} text is limited to one line, and ellipsizes at the end.
     *
     * @param title text to display as title.
     */
    public void setTitle(@NonNull CharSequence title) {
        mTitle = title;
        markDirty();
    }

    /**
     * Sets the body text of item.
     *
     * <p>Text beyond length required by regulation will be truncated. Defaults {@code Title}
     * text as the primary.
     * @param body text to be displayed.
     */
    public void setBody(@NonNull CharSequence body) {
        mBody = body;
        markDirty();
    }

    /**
     * Sets the primary action of this {@code ListItem}.
     *
     * @param text button text to display.
     * @param showDivider whether to display a vertical bar that separates {@code Text} and
     *                    {@code Action Button}.
     * @param listener the callback that will run when action button is clicked.
     * @deprecated Use {@link #setPrimaryAction(String, boolean, View.OnClickListener)} or
     * {@link #setSecondaryAction(String, boolean, View.OnClickListener)} instead to individually
     * set the actions.
     */
    @Deprecated
    public void setAction(@NonNull String text, boolean showDivider,
            @NonNull View.OnClickListener listener) {
        setPrimaryAction(text, showDivider, listener);
    }

    /**
     * Sets the primary and secondary actions for this {@code ListItem}.
     *
     * @param primaryActionText The primary action text.
     * @param showPrimaryActionDivider Whether or not to show a divider before the primary action.
     * @param primaryActionOnClickListener The listener to be invoked when the primary action is
     *                                     triggered.
     * @param secondaryActionText The secondary action text.
     * @param showSecondaryActionDivider Whether or not to show a divider before the secondary
     *                                   action.
     * @param secondaryActionOnClickListener The listener to be invoked when the secondary action is
     *                                       triggered.
     * @deprecated Use {@link #setPrimaryAction(String, boolean, View.OnClickListener)} and
     * {@link #setSecondaryAction(String, boolean, View.OnClickListener)} to set both actions.
     */
    @Deprecated
    public void setActions(@NonNull String primaryActionText, boolean showPrimaryActionDivider,
            @NonNull View.OnClickListener primaryActionOnClickListener,
            @NonNull String secondaryActionText, boolean showSecondaryActionDivider,
            @NonNull View.OnClickListener secondaryActionOnClickListener) {
        setPrimaryAction(primaryActionText, showPrimaryActionDivider, primaryActionOnClickListener);
        setSecondaryAction(secondaryActionText, showSecondaryActionDivider,
                secondaryActionOnClickListener);
    }

    /**
     * Sets the primary action of this {@code ListItem}.
     *
     * @param primaryActionText Action text to display.
     * @param showPrimaryActionDivider Whether or not to display a vertical bar before the primary
     *                                 action.
     * @param primaryActionOnClickListener The callback that will run when the action is clicked.
     *
     * @throws IllegalArgumentException If {@code primaryActionText} is {@code null} or empty.
     * @throws IllegalArgumentException If {@code primaryActionOnClickListener} is {@code null}.
     */
    public void setPrimaryAction(@NonNull String primaryActionText,
            boolean showPrimaryActionDivider,
            @NonNull View.OnClickListener primaryActionOnClickListener) {
        if (TextUtils.isEmpty(primaryActionText)) {
            throw new IllegalArgumentException("Action text cannot be empty.");
        }
        if (primaryActionOnClickListener == null) {
            throw new IllegalArgumentException("Action OnClickListener cannot be null.");
        }

        mPrimaryActionText = primaryActionText;
        mPrimaryActionOnClickListener = primaryActionOnClickListener;
        mShowPrimaryActionDivider = showPrimaryActionDivider;

        markDirty();
    }

    /**
     * Sets the secondary action of this {@code ListItem}.
     *
     * <p>The secondary action will appear before the primary action if both are set.
     *
     * @param secondaryActionText Action text to display.
     * @param showSecondaryActionDivider Whether or not to display a vertical bar before the
     *                                   secondary action.
     * @param secondaryActionOnClickListener The callback that will run when the action is clicked.
     *
     * @throws IllegalArgumentException If {@code secondaryActionText} is {@code null} or empty.
     * @throws IllegalArgumentException If {@code secondaryActionOnClickListener} is {@code null}.
     */
    public void setSecondaryAction(@NonNull String secondaryActionText,
            boolean showSecondaryActionDivider,
            @NonNull View.OnClickListener secondaryActionOnClickListener) {
        if (TextUtils.isEmpty(secondaryActionText)) {
            throw new IllegalArgumentException("Action text cannot be empty.");
        }
        if (secondaryActionOnClickListener == null) {
            throw new IllegalArgumentException("Action OnClickListener cannot be null.");
        }

        mSecondaryActionText = secondaryActionText;
        mSecondaryActionOnClickListener = secondaryActionOnClickListener;
        mShowSecondaryActionDivider = showSecondaryActionDivider;

        markDirty();
    }

    /**
     * Sets whether or not the actions should be styled as borderless.
     *
     * <p>By default, this value is {@code true}.
     *
     * @param isActionBorderless {@code true} if the actions should be borderless. {@code false}
     *                           otherwise.
     */
    public void setActionBorderless(boolean isActionBorderless) {
        mIsActionBorderless = isActionBorderless;
    }

    /**
     * Holds the children views of {@link ActionListItem}.
     */
    public static final class ViewHolder extends ListItem.ViewHolder {
        private final View[] mWidgetViews;

        private ImageView mPrimaryIcon;

        private TextView mTitle;
        private TextView mBody;

        private boolean mIsActionBorderless = true;

        private Guideline mSupplementalGuideline;

        private Button mPrimaryActionBorderless;
        private Button mPrimaryAction;
        private View mPrimaryActionDivider;

        private Button mSecondaryActionBorderless;
        private Button mSecondaryAction;
        private View mSecondaryActionDivider;

        private View mClickInterceptor;

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

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

            mTitle = itemView.findViewById(R.id.title);
            mBody = itemView.findViewById(R.id.body);

            mSupplementalGuideline = itemView.findViewById(R.id.actions_guideline);

            mPrimaryAction = itemView.findViewById(R.id.primary_action);
            mPrimaryActionBorderless = itemView.findViewById(R.id.primary_action_borderless);
            mPrimaryActionDivider = itemView.findViewById(R.id.primary_action_divider);
            mSecondaryAction = itemView.findViewById(R.id.secondary_action);
            mSecondaryActionBorderless = itemView.findViewById(R.id.secondary_action_borderless);
            mSecondaryActionDivider = itemView.findViewById(R.id.secondary_action_divider);

            mClickInterceptor = itemView.findViewById(R.id.click_interceptor);

            // 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[] {
                    // Primary action.
                    mPrimaryIcon,
                    // Text.
                    mTitle, mBody,
                    // Supplemental actions
                    mPrimaryAction,
                    mPrimaryActionBorderless,
                    mPrimaryActionDivider,
                    mSecondaryAction,
                    mSecondaryActionBorderless,
                    mSecondaryActionDivider
            };
        }

        @Override
        public void onUxRestrictionsChanged(@NonNull CarUxRestrictions restrictions) {
            CarUxRestrictionsUtils.apply(itemView.getContext(), restrictions, getBody());
        }

        /**
         * Sets if the action returned is styled as borderless or non-borderless.
         *
         * <p>By default, this value is {@code true}.
         *
         * @param isBorderless Whether or not the action is borderless.
         */
        public void setActionBorderless(boolean isBorderless) {
            mIsActionBorderless = isBorderless;
        }

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

        @NonNull
        public TextView getTitle() {
            return mTitle;
        }

        @NonNull
        public TextView getBody() {
            return mBody;
        }

        @NonNull
        public Button getPrimaryAction() {
            return mIsActionBorderless ? mPrimaryActionBorderless : mPrimaryAction;
        }

        @NonNull
        @VisibleForTesting
        Button getBorderlessPrimaryAction() {
            return mPrimaryActionBorderless;
        }

        @NonNull
        @VisibleForTesting
        Button getBorderedPrimaryAction() {
            return mPrimaryAction;
        }

        @NonNull
        public View getPrimaryActionDivider() {
            return mPrimaryActionDivider;
        }

        @NonNull
        public Button getSecondaryAction() {
            return mIsActionBorderless ? mSecondaryActionBorderless : mSecondaryAction;
        }

        @NonNull
        @VisibleForTesting
        Button getBorderlessSecondaryAction() {
            return mSecondaryActionBorderless;
        }

        @NonNull
        @VisibleForTesting
        Button getBorderedSecondaryAction() {
            return mSecondaryAction;
        }

        @NonNull
        public View getSecondaryActionDivider() {
            return mSecondaryActionDivider;
        }

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

        /** Returns the Guideline that the actions should be centered upon. */
        @NonNull
        Guideline getSupplementalGuideline() {
            return mSupplementalGuideline;
        }

        /**
         * Returns the view that will intercept clicks beneath the supplemental icon and action
         * views.
         */
        @NonNull
        View getClickInterceptView() {
            return mClickInterceptor;
        }
    }
}