public class

SwitchListItem

extends ListItem<SwitchListItem.ViewHolder>

 java.lang.Object

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

↳androidx.car.widget.SwitchListItem

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 .

An item supports primary action and a switch as supplemental action.

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: represented by .

SwitchListItem binds data to SwitchListItem.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
publicSwitchListItem(Context context)

Methods
public static SwitchListItem.ViewHoldercreateViewHolder(View itemView)

Creates a SwitchListItem.ViewHolder.

protected final ContextgetContext()

public intgetViewType()

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

public voidonBind(SwitchListItem.ViewHolder viewHolder)

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

protected voidresolveDirtyState()

Calculates the layout params for views in SwitchListItem.ViewHolder.

public voidsetBody(java.lang.CharSequence body)

Sets the body text of item.

public voidsetClickable(boolean isClickable)

Sets whether the item is clickable.

public abstract voidsetEnabled(boolean enabled)

Sets the enabled state of the bound ListItem.ViewHolder.

public voidsetPrimaryActionEmptyIcon()

Sets Primary Action to be empty icon.

public voidsetPrimaryActionIcon(Icon icon, int size)

Sets Primary Action to be represented by an icon.

public voidsetPrimaryActionNoIcon()

Sets Primary Action to have no icon.

public voidsetShowSwitchDivider(boolean showSwitchDivider)

Sets whether to display a vertical bar between switch and text.

public voidsetSwitchOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener listener)

Registers a callback to be invoked when the checked state of switch changes.

public voidsetSwitchState(boolean isChecked)

Sets the state of Switch.

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 SwitchListItem(Context context)

Methods

public static SwitchListItem.ViewHolder createViewHolder(View itemView)

Creates a SwitchListItem.ViewHolder.

public int getViewType()

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

protected void resolveDirtyState()

Calculates the layout params for views in SwitchListItem.ViewHolder.

public void onBind(SwitchListItem.ViewHolder viewHolder)

Hides all views in SwitchListItem.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.

public void setClickable(boolean isClickable)

Sets whether the item is clickable. If true, clicking item toggles the switch.

public void setPrimaryActionIcon(Icon icon, int size)

Sets Primary Action to be represented by an icon.

Parameters:

icon: An icon to set as primary action.
size: small/medium/large. Available as SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL, SwitchListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM, SwitchListItem.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.

Primary text is Title by default. It can be set by SwitchListItem.setBody(CharSequence)

Title text is limited to one line, and ellipses 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.

Parameters:

body: text to be displayed.

public void setSwitchState(boolean isChecked)

Sets the state of Switch.

Parameters:

isChecked: sets the "checked/unchecked, namely on/off" state of switch.

public void setSwitchOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener listener)

Registers a callback to be invoked when the checked state of switch changes.

Parameters:

listener: callback to be invoked when the checked state shown in the UI changes.

public void setShowSwitchDivider(boolean showSwitchDivider)

Sets whether to display a vertical bar between switch and text.

protected final Context getContext()

Source

/*
 * Copyright 2019 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.Icon;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.Switch;
import android.widget.TextView;

import androidx.annotation.CallSuper;
import androidx.annotation.DimenRes;
import androidx.annotation.Dimension;
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.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 Switch}.
 *
 * <p>An item supports primary action and a switch as supplemental action.
 *
 * <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}: represented by {@link Switch}.
 * </ul>
 *
 * <p>{@code SwitchListItem} 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 class SwitchListItem extends ListItem<SwitchListItem.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;

    private final Context mContext;
    private boolean mIsEnabled = true;
    private boolean mIsClickable;

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

    @PrimaryActionType private int mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
    private Icon mPrimaryActionIcon;
    @PrimaryActionIconSize private int mPrimaryActionIconSize = PRIMARY_ACTION_ICON_SIZE_SMALL;

    private CharSequence mTitle;
    private CharSequence mBody;

    @Dimension
    private final int mSupplementalGuidelineBegin;

    private boolean mSwitchChecked;
    /**
     * {@code true} if the checked state of the switch has changed programmatically and
     * {@link #mSwitchOnCheckedChangeListener} needs to be notified.
     */
    private boolean mShouldNotifySwitchChecked;
    private boolean mShowSwitchDivider;
    private CompoundButton.OnCheckedChangeListener mSwitchOnCheckedChangeListener;

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

    public SwitchListItem(@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_SWITCH;
    }

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

        // Create binders that adjust layout params of each view.
        setPrimaryAction();
        setText();
        setSwitch();
        setItemClickable();
    }

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

        for (View v : viewHolder.getWidgetViews()) {
            v.setEnabled(mIsEnabled);
        }
        // SwitchListItem supports clicking on the item so we also update the entire itemView.
        viewHolder.itemView.setEnabled(mIsEnabled);
    }

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

    /**
     * Sets whether the item is clickable. If {@code true}, clicking item toggles the switch.
     */
    public void setClickable(boolean isClickable) {
        mIsClickable = isClickable;
        markDirty();
    }

    /**
     * Sets {@code Primary Action} to be represented by an icon.
     *
     * @param icon An icon to set as primary action.
     * @param size small/medium/large. Available as {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
     *             {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM},
     *             {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
     */
    public void setPrimaryActionIcon(@NonNull Icon icon, @PrimaryActionIconSize int size) {
        mPrimaryActionType = PRIMARY_ACTION_TYPE_ICON;
        mPrimaryActionIcon = icon;
        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>Primary text is {@code Title} by default. It can be set by
     * {@link #setBody(CharSequence)}
     *
     * <p>{@code Title} text is limited to one line, and ellipses at the end.
     *
     * @param title text to display as title.
     */
    public void setTitle(@Nullable CharSequence title) {
        mTitle = title;
        markDirty();
    }

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

    /**
     * Sets the state of {@code Switch}.
     *
     * @param isChecked sets the "checked/unchecked, namely on/off" state of switch.
     */
    public void setSwitchState(boolean isChecked) {
        if (mSwitchChecked == isChecked) {
            return;
        }
        mSwitchChecked = isChecked;
        mShouldNotifySwitchChecked = true;
        markDirty();
    }

    /**
     * Registers a callback to be invoked when the checked state of switch changes.
     *
     * @param listener callback to be invoked when the checked state shown in the UI changes.
     */
    public void setSwitchOnCheckedChangeListener(
            @Nullable CompoundButton.OnCheckedChangeListener listener) {
        mSwitchOnCheckedChangeListener = listener;
        // This method invalidates previous listener. Reset so that we *only*
        // notify when the checked state changes and not on the initial bind.
        mShouldNotifySwitchChecked = false;
        markDirty();
    }

    /**
     * Sets whether to display a vertical bar between switch and text.
     */
    public void setShowSwitchDivider(boolean showSwitchDivider) {
        mShowSwitchDivider = showSwitchDivider;
        markDirty();
    }

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

    private void hideSubViews(ViewHolder vh) {
        for (View v : vh.getWidgetViews()) {
            v.setVisibility(View.GONE);
        }
    }

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

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

    private void setPrimaryIconContent() {
        switch (mPrimaryActionType) {
            case PRIMARY_ACTION_TYPE_ICON:
                mBinders.add(vh -> {
                    vh.getPrimaryIcon().setVisibility(View.VISIBLE);
                    mPrimaryActionIcon.loadDrawableAsync(getContext(),
                            drawable -> vh.getPrimaryIcon().setImageDrawable(drawable),
                            new Handler(Looper.getMainLooper()));
                });
                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 -> {
            MarginLayoutParams titleLayoutParams =
                    (MarginLayoutParams) vh.getTitle().getLayoutParams();
            titleLayoutParams.setMarginStart(startMargin);
            vh.getTitle().requestLayout();

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

    private void setTextEndMargin() {
        int endMargin = mContext.getResources().getDimensionPixelSize(R.dimen.car_padding_4);

        mBinders.add(vh -> {
            MarginLayoutParams titleLayoutParams =
                    (MarginLayoutParams) vh.getTitle().getLayoutParams();
            titleLayoutParams.setMarginEnd(endMargin);

            MarginLayoutParams bodyLayoutParams =
                    (MarginLayoutParams) vh.getBody().getLayoutParams();
            bodyLayoutParams.setMarginEnd(endMargin);
        });
    }

    /**
     * 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 -> {
                MarginLayoutParams layoutParams =
                        (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);
                MarginLayoutParams layoutParams =
                        (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
                MarginLayoutParams titleLayoutParams =
                        (MarginLayoutParams) vh.getTitle().getLayoutParams();
                titleLayoutParams.topMargin = padding2;
                vh.getTitle().requestLayout();

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

    /**
     * Sets up view(s) for supplemental action.
     */
    private void setSwitch() {
        mBinders.add(vh -> {
            vh.getSwitch().setVisibility(View.VISIBLE);
            vh.getSwitch().setOnCheckedChangeListener(null);
            vh.getSwitch().setChecked(mSwitchChecked);
            vh.getSwitch().setOnCheckedChangeListener((buttonView, isChecked) -> {
                if (mSwitchOnCheckedChangeListener != null) {
                    // The checked state changed via user interaction with the switch.
                    mSwitchOnCheckedChangeListener.onCheckedChanged(buttonView, isChecked);
                }
                mSwitchChecked = isChecked;
            });
            if (mShouldNotifySwitchChecked && mSwitchOnCheckedChangeListener != null) {
                // The checked state was changed programmatically.
                mSwitchOnCheckedChangeListener.onCheckedChanged(vh.getSwitch(),
                        mSwitchChecked);
                mShouldNotifySwitchChecked = false;
            }

            if (mShowSwitchDivider) {
                vh.getSwitchDivider().setVisibility(View.VISIBLE);
            }
        });
    }

    private void setItemClickable() {
        mBinders.add(vh -> {
            // If applicable (namely item is clickable), clicking item always toggles the switch.
            vh.itemView.setOnClickListener(v -> vh.getSwitch().toggle());
            vh.itemView.setClickable(mIsClickable);
        });
    }

    /**
     * Holds views of SwitchListItem.
     */
    public static final class ViewHolder extends ListItem.ViewHolder {

        private final View[] mWidgetViews;

        private ImageView mPrimaryIcon;

        private TextView mTitle;
        private TextView mBody;

        private Guideline mSupplementalGuideline;

        private Switch mSwitch;
        private View mSwitchDivider;

        /**
         * ViewHolder that contains necessary widgets for {@link SwitchListItem}.
         */
        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.supplemental_actions_guideline);

            mSwitch = itemView.findViewById(R.id.switch_widget);
            mSwitchDivider = itemView.findViewById(R.id.switch_divider);

            int minTouchSize = itemView.getContext().getResources()
                    .getDimensionPixelSize(R.dimen.car_touch_target_size);
            MinTouchTargetHelper.ensureThat(mSwitch).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,
                    mTitle, mBody,
                    mSwitch, mSwitchDivider,
            };
        }

        /**
         * 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, getBody());
        }

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

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

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

        @NonNull
        public View getSwitchDivider() {
            return mSwitchDivider;
        }

        @NonNull
        public Switch getSwitch() {
            return mSwitch;
        }

        @NonNull
        Guideline getSupplementalGuideline() {
            return mSupplementalGuideline;
        }

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