public class

RadioButtonListItem

extends ListItem<RadioButtonListItem.ViewHolder>

 java.lang.Object

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

↳androidx.car.widget.RadioButtonListItem

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 .

A radio button list item visually composes of 3 parts.

  • optional Primary Action Icon.
  • optional Text.
  • A .

Clicking the item always checks the radio button.

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

Methods
public static RadioButtonListItem.ViewHoldercreateViewHolder(View itemView)

Creates a RadioButtonListItem.ViewHolder.

protected ContextgetContext()

public intgetViewType()

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

public booleanisChecked()

Get whether the radio button is checked.

protected voidonBind(RadioButtonListItem.ViewHolder viewHolder)

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

protected voidresolveDirtyState()

Calculates the layout params for views in RadioButtonListItem.ViewHolder.

public voidsetChecked(boolean isChecked)

Sets the state of radio button.

public abstract voidsetEnabled(boolean enabled)

Sets the enabled state of the bound ListItem.ViewHolder.

public voidsetOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener listener)

Sets of radio button.

public voidsetPrimaryActionIcon(Icon icon, int size)

Sets Primary Action to be represented by an icon.

public voidsetShowRadioButtonDivider(boolean showDivider)

Sets whether to display a vertical bar that separates text and radio button.

public voidsetText(java.lang.CharSequence text)

Sets text to be displayed next to icon.

public voidsetTextStartMargin(int dimenRes)

Sets the start margin of text.

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.

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

Methods

public static RadioButtonListItem.ViewHolder createViewHolder(View itemView)

Creates a RadioButtonListItem.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 setChecked(boolean isChecked)

Sets the state of radio button.

Parameters:

isChecked: true to check the button; false to uncheck it.

public boolean isChecked()

Get whether the radio button is checked.

The return value is in sync with UI state.

Returns:

true if the widget is checked; false otherwise.

public void setPrimaryActionIcon(Icon icon, int size)

Sets Primary Action to be represented by an icon. The size of icon automatically adjusts the start of Text.

Parameters:

icon: the icon to set as primary action. Setting null clears the icon and aligns text to the start of list item; size will be ignored.
size: constant that represents the size of icon. See RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_SMALL, RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM, and RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_LARGE. If null is passed in for icon, size will be ignored.

public void setText(java.lang.CharSequence text)

Sets text to be displayed next to icon.

Parameters:

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

public void setTextStartMargin(int dimenRes)

Sets the start margin of text.

public void setShowRadioButtonDivider(boolean showDivider)

Sets whether to display a vertical bar that separates text and radio button.

public void setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener listener)

Sets of radio button.

protected void resolveDirtyState()

Calculates the layout params for views in RadioButtonListItem.ViewHolder.

protected void onBind(RadioButtonListItem.ViewHolder viewHolder)

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

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.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.CompoundButton;
import android.widget.ImageView;
import android.widget.RadioButton;
import android.widget.TextView;

import androidx.annotation.DimenRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.car.R;
import androidx.car.util.CarUxRestrictionsUtils;

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

/**
 * Class to build a list item with {@link RadioButton}.
 *
 * <p>A radio button list item visually composes of 3 parts.
 * <ul>
 *     <li>optional {@code Primary Action Icon}.
 *     <li>optional {@code Text}.
 *     <li>A {@link RadioButton}.
 * </ul>
 *
 * <p>Clicking the item always checks the radio button.
 */
public class RadioButtonListItem extends ListItem<RadioButtonListItem.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.
     */
    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;

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

    @Nullable private Icon mPrimaryActionIcon;
    @PrimaryActionIconSize private int mPrimaryActionIconSize = PRIMARY_ACTION_ICON_SIZE_SMALL;

    private int mTextStartMargin;
    private CharSequence mText;

    private boolean mIsChecked;
    private boolean mShowRadioButtonDivider;
    private CompoundButton.OnCheckedChangeListener mRadioButtonOnCheckedChangeListener;

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

    public RadioButtonListItem(@NonNull Context context) {
        mContext = context;
        markDirty();
    }

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

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

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

    /**
     * Sets the state of radio button.
     *
     * @param isChecked {@code true} to check the button; {@code false} to uncheck it.
     */
    public void setChecked(boolean isChecked) {
        if (mIsChecked == isChecked) {
            return;
        }
        mIsChecked = isChecked;
        markDirty();
    }

    /**
     * Get whether the radio button is checked.
     *
     * <p>The return value is in sync with UI state.
     *
     * @return {@code true} if the widget is checked; {@code false} otherwise.
     */
    public boolean isChecked() {
        return mIsChecked;
    }

    /**
     * Sets {@code Primary Action} to be represented by an icon. The size of icon automatically
     * adjusts the start of {@code Text}.
     *
     * @param icon the icon to set as primary action. Setting {@code null} clears the icon and
     *             aligns text to the start of list item; {@code size} will be ignored.
     * @param size constant that represents the size of icon. See
     *             {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
     *             {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM}, and
     *             {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
     *             If {@code null} is passed in for icon, size will be ignored.
     */
    public void setPrimaryActionIcon(@NonNull Icon icon, @PrimaryActionIconSize int size) {
        mPrimaryActionIcon = icon;
        mPrimaryActionIconSize = size;
        markDirty();
    }

    /**
     * Sets text to be displayed next to icon.
     *
     * @param text Text to be displayed, or {@code null} to clear the content.
     */
    public void setText(@Nullable CharSequence text) {
        mText = text;
        markDirty();
    }

    /**
     * Sets the start margin of text.
     */
    public void setTextStartMargin(@DimenRes int dimenRes) {
        mTextStartMargin = mContext.getResources().getDimensionPixelSize(dimenRes);
        markDirty();
    }

    /**
     * Sets whether to display a vertical bar that separates {@code text} and radio button.
     */
    public void setShowRadioButtonDivider(boolean showDivider) {
        mShowRadioButtonDivider = showDivider;
        markDirty();
    }

    /**
     * Sets {@link android.widget.CompoundButton.OnCheckedChangeListener} of radio button.
     */
    public void setOnCheckedChangeListener(
            @NonNull CompoundButton.OnCheckedChangeListener listener) {
        mRadioButtonOnCheckedChangeListener = listener;
        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();
        setTextInternal();
        setRadioButton();
        setOnClickListenerToCheckRadioButton();
    }

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

    private void setTextInternal() {
        setTextContent();
        setTextStartMargin();
    }

    private void setRadioButton() {
        mBinders.add(vh -> {
            // Clear listener before setting checked to avoid listener is notified every time
            // we bind to view holder.
            vh.getRadioButton().setOnCheckedChangeListener(null);
            vh.getRadioButton().setChecked(mIsChecked);
            // Keep internal checked state in sync with UI by wrapping listener.
            vh.getRadioButton().setOnCheckedChangeListener((buttonView, isChecked) -> {
                mIsChecked = isChecked;
                if (mRadioButtonOnCheckedChangeListener != null) {
                    mRadioButtonOnCheckedChangeListener.onCheckedChanged(buttonView, isChecked);
                }
            });

            vh.getRadioButtonDivider().setVisibility(
                    mShowRadioButtonDivider ? View.VISIBLE : View.GONE);
        });
    }

    private void setPrimaryIconContent() {
        mBinders.add(vh -> {
            if (mPrimaryActionIcon == null) {
                vh.getPrimaryIcon().setVisibility(View.GONE);
            } else {
                vh.getPrimaryIcon().setVisibility(View.VISIBLE);
                mPrimaryActionIcon.loadDrawableAsync(getContext(),
                        drawable -> vh.getPrimaryIcon().setImageDrawable(drawable),
                        new Handler(Looper.getMainLooper()));
            }
        });
    }

    /**
     * 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 (mPrimaryActionIcon == null) {
            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 -> {
            ViewGroup.MarginLayoutParams layoutParams =
                    (ViewGroup.MarginLayoutParams) vh.getPrimaryIcon().getLayoutParams();
            layoutParams.height = layoutParams.width = iconSize;
            layoutParams.setMarginStart(startMargin);

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

    private void setTextContent() {
        if (!TextUtils.isEmpty(mText)) {
            mBinders.add(vh -> {
                vh.getText().setVisibility(View.VISIBLE);
                vh.getText().setText(mText);
            });
        }
    }

    /**
     * Sets start margin of text view depending on icon type.
     */
    private void setTextStartMargin() {
        int offset = 0;
        if (mPrimaryActionIcon != null) {
            // If there is an icon, offset text to accommodate it.
            @DimenRes int startMarginResId =
                    mPrimaryActionIconSize == PRIMARY_ACTION_ICON_SIZE_LARGE
                            ? R.dimen.car_keyline_4
                            : R.dimen.car_keyline_3;  // Small and medium sized icon.
            offset = mContext.getResources().getDimensionPixelSize(startMarginResId);
        }

        int startMargin = offset + mTextStartMargin;
        mBinders.add(vh -> {
            ViewGroup.MarginLayoutParams layoutParams =
                    (ViewGroup.MarginLayoutParams) vh.getText().getLayoutParams();
            layoutParams.setMarginStart(startMargin);
            vh.getText().requestLayout();
        });
    }

    // Clicking the item always checks radio button.
    private void setOnClickListenerToCheckRadioButton() {
        mBinders.add(vh -> {
            vh.itemView.setClickable(true);
            vh.itemView.setOnClickListener(v -> vh.getRadioButton().setChecked(true));
        });
    }

    /**
     * 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);
        }
        // Radio button is always visible.
        vh.getRadioButton().setVisibility(View.VISIBLE);
    }

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

        private final View[] mWidgetViews;

        private ViewGroup mContainerLayout;

        private ImageView mPrimaryIcon;
        private TextView mText;

        private View mRadioButtonDivider;
        private RadioButton mRadioButton;

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

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

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

            mRadioButton = itemView.findViewById(R.id.radio_button);
            mRadioButtonDivider = itemView.findViewById(R.id.radio_button_divider);

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

            MinTouchTargetHelper.ensureThat(mRadioButton)
                    .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, mText,
                    mRadioButton, mRadioButtonDivider};
        }

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

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

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

        @NonNull
        public RadioButton getRadioButton() {
            return mRadioButton;
        }

        @NonNull
        public View getRadioButtonDivider() {
            return mRadioButtonDivider;
        }

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

        @Override
        public void onUxRestrictionsChanged(
                androidx.car.uxrestrictions.CarUxRestrictions restrictionInfo) {
            CarUxRestrictionsUtils.apply(itemView.getContext(), restrictionInfo, getText());
        }
    }
}