先上图和视频看效果:
屏幕录制2021-01-25 08.51.42
(原创作品,转载请声明出处:https://blog.csdn.net/hegan2010/article/details/113103183)
谷歌已经提供了 MaterialButtonToggleGroup, 但是 MaterialButtonToggleGroup 是继承 LinearLayout 的,并且没有增加子View排列的的属性定义,说明只支持线性排列,不能满足需要多行排列的需求。
无奈自己仿写了 MaterialButtonToggleGroup 来实现支持各种布局 ButtonGroupRecyclerView。
ButtonGroupRecyclerView 继承自 RecyclerView。内部通过 mDefaultCheckedPos (单选),mCheckedItemIdSet (复选) 来记录已选的 Button 的 Item Id,所以这里要求 Adapter 要 setHasStableIds(true), 并且要每个 Item View 返回独特的 Item Id,即要求实现 public long getItemId(int position) 方法,因此ButtonGroupRecyclerView 已提供 ButtonCheckedAdapter 作为 Adapter 的 Base 类。ButtonCheckedAdapter 在 ViewHolder 被回收的时候(onViewRecycled) 对已选 Button 进行 check 状态重置,以避免 Button 复用的时候保留了之前的 check 状态。实际是否能避免这个问题还有待进一步测试。所以,可能还存在 bug。
提供了 OnButtonCheckedListener 监听器,注意监听器回掉方法的参数 checkedPos 指的是 Item Position 而不是 Item Id。
好了,上代码:
import static com.google.android.material.theme.overlay.MaterialThemeOverlay.wrap;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.BoolRes;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.customview.view.AbsSavedState;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.button.MaterialButton;
import com.hym.tuluowan.R;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.WeakHashMap;
import java.util.function.Predicate;
public class ButtonGroupRecyclerView extends RecyclerView {
/**
* Interface definition for a callback to be invoked when a {@link MaterialButton} is checked or
* unchecked in this group.
*/
public interface OnButtonCheckedListener {
/**
* Called when a {@link MaterialButton} in this group is checked or unchecked.
*
* @param group The group in which the MaterialButton's checked state was changed
* @param checkedPos The position of the MaterialButton whose check state changed
* @param isChecked Whether the MaterialButton is currently checked
*/
void onButtonChecked(ButtonGroupRecyclerView group, int checkedPos, boolean isChecked);
}
private static final String LOG_TAG = ButtonGroupRecyclerView.class.getSimpleName();
private static final int DEF_STYLE_RES = R.style.Widget_ButtonGroupRecyclerView;
private final CheckedStateTracker mCheckedStateTracker = new CheckedStateTracker();
private final LinkedHashSet<OnButtonCheckedListener> mOnButtonCheckedListeners =
new LinkedHashSet<>();
private boolean mSkipCheckedStateTracker = false;
private boolean mSingleSelection;
private boolean mSelectionRequired;
private long mCheckedItemId = NO_ID;
private final int mDefaultCheckedPos;
private final SortedSet<Long> mCheckedItemIdSet = new TreeSet<>();
public ButtonGroupRecyclerView(@NonNull Context context) {
this(context, null);
}
public ButtonGroupRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.buttonGroupRecyclerViewStyle);
}
public ButtonGroupRecyclerView(
@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(wrap(context, attrs, defStyleAttr, DEF_STYLE_RES), attrs, defStyleAttr);
// Ensure we are using the correctly themed context rather than the context that was
// passed in.
context = getContext();
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.ButtonGroupRecyclerView, defStyleAttr, DEF_STYLE_RES);
boolean single = a.getBoolean(R.styleable.ButtonGroupRecyclerView_singleSelection, false);
setSingleSelection(single);
mDefaultCheckedPos =
a.getInteger(R.styleable.ButtonGroupRecyclerView_checkedButtonPos, NO_POSITION);
recordCheckedItemId(mCheckedItemId, true);
mSelectionRequired = a.getBoolean(R.styleable.ButtonGroupRecyclerView_selectionRequired,
false);
setChildrenDrawingOrderEnabled(true);
a.recycle();
ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// Checks the appropriate button as requested via XML
if (mCheckedItemId != NO_ID) {
checkForced(mCheckedItemId);
}
}
/**
* This override prohibits Views other than {@link MaterialButton} to be added.
*/
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (!(child instanceof MaterialButton)) {
Log.e(LOG_TAG, "Child views must be of type MaterialButton.");
return;
}
super.addView(child, index, params);
MaterialButton buttonChild = (MaterialButton) child;
// Sets sensible default values and an internal checked change listener for this child
setupButtonChild(buttonChild);
// Reorders children if a checked child was added to this layout
if (buttonChild.isChecked()) {
long childItemId = getChildItemId(buttonChild);
updateCheckedStates(childItemId, true);
setCheckedItemId(childItemId);
} else if (isChildChecked(buttonChild) || (mCheckedItemIdSet.isEmpty()
&& mDefaultCheckedPos == getChildLayoutPosition(buttonChild))) {
MaterialButtonHelper.setButtonCheckedWithoutNotifyListeners(buttonChild, true);
long childItemId = getChildItemId(buttonChild);
updateCheckedStates(childItemId, true);
setCheckedItemId(childItemId);
}
}
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
((MaterialButton) child).removeOnCheckedChangeListener(mCheckedStateTracker);
}
@Override
protected Parcelable onSaveInstanceState() {
SavedState state = new SavedState(super.onSaveInstanceState());
state.checkedItemId = mCheckedItemId;
state.checkedItemIdList = new ArrayList<>(mCheckedItemIdSet);
return state;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
resetChecked();
setCheckedItemId(savedState.checkedItemId, false);
for (long checkedItemId : savedState.checkedItemIdList) {
setCheckedItemId(checkedItemId, false);
}
}
private static class SavedState extends AbsSavedState {
private long checkedItemId;
private List<Long> checkedItemIdList;
/**
* Constructor called from {@link ButtonGroupRecyclerView#onSaveInstanceState()}
*/
private SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(Parcel in, ClassLoader loader) {
super(in, loader);
checkedItemId = in.readLong();
checkedItemIdList = in.readArrayList(null);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeLong(checkedItemId);
dest.writeList(checkedItemIdList);
}
public static final Parcelable.Creator<SavedState> CREATOR
= new ClassLoaderCreator<SavedState>() {
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in, loader);
}
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in, null);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
@NonNull
@Override
public CharSequence getAccessibilityClassName() {
return ButtonGroupRecyclerView.class.getName();
}
/**
* Sets the {@link MaterialButton} whose id is passed in to the checked state. If this
* RadioGroupView is in {@link #isSingleSelection() single selection mode}, then all
* other MaterialButtons in this group will be unchecked. Otherwise, other MaterialButtons will
* retain their checked state.
*
* @param itemId View item id of {@link MaterialButton} to set checked
* @see #uncheck(long)
* @see #clearChecked()
* @see #getCheckedButtonItemIds()
* @see #getCheckedButtonItemId()
*/
public void check(long itemId) {
if (itemId == mCheckedItemId) {
return;
}
checkForced(itemId);
}
/**
* Sets the {@link MaterialButton} whose id is passed in to the unchecked state.
*
* @param itemId View item id of {@link MaterialButton} to set unchecked
* @see #check(long)
* @see #clearChecked()
* @see #getCheckedButtonItemIds()
* @see #getCheckedButtonItemId()
*/
public void uncheck(long itemId) {
setCheckedStateForView(itemId, false);
updateCheckedStates(itemId, false);
mCheckedItemId = NO_ID;
// recordCheckedItemId(mCheckedItemId, true);
dispatchOnButtonChecked(itemId, false);
}
/**
* Clears the selections. When the selections are cleared, no {@link MaterialButton} in this
* group is checked and {@link #getCheckedButtonItemIds()} returns an empty list.
*
* @see #check(long)
* @see #uncheck(long)
* @see #getCheckedButtonItemIds()
* @see #getCheckedButtonItemId()
*/
public void clearChecked() {
mSkipCheckedStateTracker = true;
for (int i = 0; i < getChildCount(); i++) {
MaterialButton child = getChildButton(i);
child.setChecked(false);
dispatchOnButtonChecked(getChildLayoutPosition(child), false);
}
mSkipCheckedStateTracker = false;
mCheckedItemIdSet.clear();
setCheckedItemId(NO_ID);
}
/**
* When in {@link #isSingleSelection() single selection mode}, returns the identifier of the
* selected button in this group. Upon empty selection, the returned value is {@link
* RecyclerView#NO_ID}.
* If not in single selection mode, the return value is {@link RecyclerView#NO_ID}.
*
* @return The item id of the selected {@link MaterialButton} in this group in {@link
* #isSingleSelection() single selection mode}. When not in {@link #isSingleSelection() single
* selection mode}, returns {@link RecyclerView#NO_ID}.
* @attr ref R.styleable#RadioGroupRecyclerView_checkedButton
* @see #check(long)
* @see #uncheck(long)
* @see #clearChecked()
* @see #getCheckedButtonItemIds()
*/
public long getCheckedButtonItemId() {
return mSingleSelection ? mCheckedItemId : NO_ID;
}
/**
* Returns the identifiers of the selected {@link MaterialButton}s in this group. Upon empty
* selection, the returned value is an empty list.
*
* @return The item ids of the selected {@link MaterialButton}s in this group. When in {@link
* #isSingleSelection() single selection mode}, returns a list with a single item id. When no
* {@link MaterialButton}s are selected, returns an empty list.
* @see #check(long)
* @see #uncheck(long)
* @see #clearChecked()
* @see #getCheckedButtonItemId()
*/
@NonNull
public List<Long> getCheckedButtonItemIds() {
return new ArrayList<>(mCheckedItemIdSet);
}
/**
* Add a listener that will be invoked when the check state of a {@link MaterialButton} in this
* group changes. See {@link OnButtonCheckedListener}.
*
* <p>Components that add a listener should take care to remove it when finished via {@link
* #removeOnButtonCheckedListener(OnButtonCheckedListener)}.
*
* @param listener listener to add
*/
public void addOnButtonCheckedListener(@NonNull OnButtonCheckedListener listener) {
mOnButtonCheckedListeners.add(listener);
}
/**
* Remove a listener that was previously added via {@link
* #addOnButtonCheckedListener(OnButtonCheckedListener)}.
*
* @param listener listener to remove
*/
public void removeOnButtonCheckedListener(@NonNull OnButtonCheckedListener listener) {
mOnButtonCheckedListeners.remove(listener);
}
/** Remove all previously added {@link OnButtonCheckedListener}s. */
public void clearOnButtonCheckedListeners() {
mOnButtonCheckedListeners.clear();
}
/**
* Returns whether this group only allows a single button to be checked.
*
* @return whether this group only allows a single button to be checked
* @attr ref R.styleable#RadioGroupRecyclerView_singleSelection
*/
public boolean isSingleSelection() {
return mSingleSelection;
}
/**
* Sets whether this group only allows a single button to be checked.
*
* <p>Calling this method results in all the buttons in this group to become unchecked.
*
* @param singleSelection whether this group only allows a single button to be checked
* @attr ref R.styleable#RadioGroupRecyclerView_singleSelection
*/
public void setSingleSelection(boolean singleSelection) {
if (mSingleSelection != singleSelection) {
mSingleSelection = singleSelection;
clearChecked();
}
}
/**
* Sets whether we prevent all child buttons from being deselected.
*
* @attr ref R.styleable#RadioGroupRecyclerView_selectionRequired
*/
public void setSelectionRequired(boolean selectionRequired) {
mSelectionRequired = selectionRequired;
}
/**
* Returns whether we prevent all child buttons from being deselected.
*
* @attr ref R.styleable#RadioGroupRecyclerView_selectionRequired
*/
public boolean isSelectionRequired() {
return mSelectionRequired;
}
/**
* Sets whether this group only allows a single button to be checked.
*
* <p>Calling this method results in all the buttons in this group to become unchecked.
*
* @param id boolean resource ID of whether this group only allows a single button to be checked
* @attr ref R.styleable#RadioGroupRecyclerView_singleSelection
*/
public void setSingleSelection(@BoolRes int id) {
setSingleSelection(getResources().getBoolean(id));
}
private void setCheckedStateForView(long itemId, boolean checked) {
ViewHolder holder = findViewHolderForItemId(itemId);
if (holder == null) {
return;
}
View checkedView = holder.itemView;
mSkipCheckedStateTracker = true;
((MaterialButton) checkedView).setChecked(checked);
recordCheckedItemId(itemId, checked);
mSkipCheckedStateTracker = false;
}
private void setCheckedItemId(long checkedItemId) {
setCheckedItemId(checkedItemId, true);
}
private void setCheckedItemId(long checkedItemId, boolean dispatch) {
mCheckedItemId = checkedItemId;
recordCheckedItemId(checkedItemId, true);
if (dispatch) {
dispatchOnButtonChecked(checkedItemId, true);
}
}
private void recordCheckedItemId(long checkedItemId, boolean checked) {
if (checkedItemId == NO_ID) {
return;
}
if (checked) {
mCheckedItemIdSet.add(checkedItemId);
} else {
mCheckedItemIdSet.remove(checkedItemId);
}
}
private void resetChecked() {
mCheckedItemId = NO_ID;
mCheckedItemIdSet.clear();
}
private boolean isChildChecked(View child) {
ViewHolder holder = getChildViewHolder(child);
if (holder != null) {
return mCheckedItemIdSet.contains(holder.getItemId());
}
return false;
}
private MaterialButton getChildButton(int index) {
return (MaterialButton) getChildAt(index);
}
private int getFirstVisibleChildIndex() {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
if (isChildVisible(i)) {
return i;
}
}
return -1;
}
private int getLastVisibleChildIndex() {
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
if (isChildVisible(i)) {
return i;
}
}
return -1;
}
private boolean isChildVisible(int i) {
View child = getChildAt(i);
return child.getVisibility() != View.GONE;
}
private int getVisibleButtonCount() {
int count = 0;
for (int i = 0; i < getChildCount(); i++) {
if (isChildVisible(i)) {
count++;
}
}
return count;
}
private int getIndexWithinVisibleButtons(@Nullable View child) {
int index = 0;
for (int i = 0; i < getChildCount(); i++) {
if (getChildAt(i) == child) {
return index;
}
if (isChildVisible(i)) {
index++;
}
}
return -1;
}
/**
* When a checked child is added, or a child is clicked, updates checked state and draw order of
* children to draw all checked children on top of all unchecked children.
*
* <p>If {@code singleSelection} is true, this will unselect any other children as well.
*
* <p>If {@code selectionRequired} is true, and the last child is unchecked it will undo the
* deselection.
*
* @param childItemId item id of child whose checked state may have changed
* @param childIsChecked Whether the child is checked
* @return Whether the checked state for childId has changed.
*/
private boolean updateCheckedStates(long childItemId, boolean childIsChecked) {
List<Long> checkedButtonPositions = getCheckedButtonItemIds();
if (mSelectionRequired && checkedButtonPositions.isEmpty()) {
// undo deselection
setCheckedStateForView(childItemId, true);
mCheckedItemId = childItemId;
recordCheckedItemId(childItemId, true);
return false;
}
// un select previous selection
if (childIsChecked && mSingleSelection) {
checkedButtonPositions.remove(childItemId);
for (long buttonItemId : checkedButtonPositions) {
setCheckedStateForView(buttonItemId, false);
dispatchOnButtonChecked(buttonItemId, false);
}
}
return true;
}
private void dispatchOnButtonChecked(long buttonItemId, boolean checked) {
ViewHolder holder = findViewHolderForItemId(buttonItemId);
if (holder != null) {
dispatchOnButtonChecked(holder.getLayoutPosition(), checked);
}
}
private void dispatchOnButtonChecked(int buttonPos, boolean checked) {
for (OnButtonCheckedListener listener : mOnButtonCheckedListeners) {
listener.onButtonChecked(this, buttonPos, checked);
}
}
private void checkForced(long checkedItemId) {
setCheckedStateForView(checkedItemId, true);
updateCheckedStates(checkedItemId, true);
setCheckedItemId(checkedItemId);
}
/**
* Sets sensible default values for {@link MaterialButton} child of this group, set child to
* {@code checkable}, and set internal checked change listener for this child.
*
* @param buttonChild {@link MaterialButton} child to set up to be added to this {@link
* ButtonGroupRecyclerView}
*/
private void setupButtonChild(@NonNull MaterialButton buttonChild) {
buttonChild.setMaxLines(1);
buttonChild.setEllipsize(TextUtils.TruncateAt.END);
buttonChild.setCheckable(true);
buttonChild.addOnCheckedChangeListener(mCheckedStateTracker);
// Enables surface layer drawing for semi-opaque strokes
// buttonChild.setShouldDrawSurfaceColorStroke(true);
MaterialButtonHelper.setShouldDrawSurfaceColorStroke(buttonChild, true);
}
private class CheckedStateTracker implements MaterialButton.OnCheckedChangeListener {
@Override
public void onCheckedChanged(@NonNull MaterialButton button, boolean isChecked) {
// Prevents infinite recursion
if (mSkipCheckedStateTracker) {
return;
}
long childItemId = getChildItemId(button);
if (mSingleSelection) {
mCheckedItemId = isChecked ? childItemId : NO_ID;
if (!isChecked) {
recordCheckedItemId(childItemId, false);
}
recordCheckedItemId(mCheckedItemId, true);
} else {
recordCheckedItemId(childItemId, isChecked);
}
boolean buttonCheckedStateChanged = updateCheckedStates(childItemId, isChecked);
if (buttonCheckedStateChanged) {
// Dispatch button.isChecked instead of isChecked in case its checked state was
// updated internally.
dispatchOnButtonChecked(childItemId, button.isChecked());
}
invalidate();
}
}
private final AdapterDataObserver mAdapterDataObserver = new AdapterDataObserver();
private class AdapterDataObserver extends RecyclerView.AdapterDataObserver {
@Override
public void onChanged() {
long checkedItemId = NO_ID;
List<Long> checkedItemIds = new LinkedList<>();
Adapter adapter = getAdapter();
if (adapter != null) {
int count = adapter.getItemCount();
for (int pos = 0; pos < count; pos++) {
ViewHolder holder = findViewHolderForAdapterPosition(pos);
if (holder == null) {
continue;
}
long itemId = holder.getItemId();
if (mCheckedItemId == itemId) {
checkedItemId = itemId;
}
if (mCheckedItemIdSet.contains(itemId)) {
checkedItemIds.add(itemId);
}
}
setItemViewCacheSize(count);
} else {
setItemViewCacheSize(0);
}
resetChecked();
if (checkedItemId != NO_ID) {
mCheckedItemId = checkedItemId;
}
mCheckedItemIdSet.addAll(checkedItemIds);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
onChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
onItemRangeChanged(positionStart, itemCount);
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
onChanged();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
onChanged();
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
onChanged();
}
}
@Override
public void swapAdapter(@Nullable Adapter adapter, boolean removeAndRecycleExistingViews) {
Adapter oldAdapter = getAdapter();
if (oldAdapter != null) {
oldAdapter.unregisterAdapterDataObserver(mAdapterDataObserver);
}
super.swapAdapter(adapter, removeAndRecycleExistingViews);
if (adapter != null) {
setItemViewCacheSize(adapter.getItemCount());
adapter.registerAdapterDataObserver(mAdapterDataObserver);
}
}
@Override
public void setAdapter(@Nullable Adapter adapter) {
Adapter oldAdapter = getAdapter();
if (oldAdapter != null) {
oldAdapter.unregisterAdapterDataObserver(mAdapterDataObserver);
}
super.setAdapter(adapter);
if (adapter != null) {
setItemViewCacheSize(adapter.getItemCount());
adapter.registerAdapterDataObserver(mAdapterDataObserver);
}
}
public abstract static class ButtonCheckedAdapter<VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
private final Map<VH, MaterialButton.OnCheckedChangeListener> mButtonListenerMap
= new WeakHashMap<>();
public ButtonCheckedAdapter() {
setHasStableIds(true);
}
@Override
public abstract long getItemId(int position);
public abstract MaterialButton.OnCheckedChangeListener getOnCheckedChangeListener(
@NonNull VH holder, int position);
@CallSuper
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
MaterialButton.OnCheckedChangeListener oldListener = mButtonListenerMap.remove(holder);
MaterialButton button = (MaterialButton) holder.itemView;
if (oldListener != null) {
button.removeOnCheckedChangeListener(oldListener);
}
MaterialButton.OnCheckedChangeListener newListener = getOnCheckedChangeListener(holder,
position);
if (newListener != null) {
mButtonListenerMap.put(holder, newListener);
button.addOnCheckedChangeListener(newListener);
}
}
@CallSuper
@Override
public void onViewRecycled(@NonNull VH holder) {
mButtonListenerMap.remove(holder);
MaterialButton button = (MaterialButton) holder.itemView;
button.clearOnCheckedChangeListeners();
if (button.isChecked()) {
button.toggle();
}
}
}
private static class MaterialButtonHelper {
private static final Method setShouldDrawSurfaceColorStroke;
private static final Field onCheckedChangeListeners;
static {
Method method = null;
try {
method = MaterialButton.class.getDeclaredMethod(
"setShouldDrawSurfaceColorStroke", boolean.class);
method.setAccessible(true);
} catch (ReflectiveOperationException e) {
Log.w(LOG_TAG, "get MaterialButton.setShouldDrawSurfaceColorStroke failed", e);
}
setShouldDrawSurfaceColorStroke = method;
Field field = null;
try {
field = MaterialButton.class.getDeclaredField("onCheckedChangeListeners");
field.setAccessible(true);
} catch (ReflectiveOperationException e) {
Log.w(LOG_TAG, "get MaterialButton.onCheckedChangeListeners failed", e);
}
onCheckedChangeListeners = field;
}
public static void setShouldDrawSurfaceColorStroke(MaterialButton button,
boolean shouldDrawSurfaceColorStroke) {
if (setShouldDrawSurfaceColorStroke == null) {
Log.w(LOG_TAG, "setShouldDrawSurfaceColorStroke failed: method is null");
return;
}
try {
setShouldDrawSurfaceColorStroke.invoke(button, shouldDrawSurfaceColorStroke);
} catch (ReflectiveOperationException e) {
Log.w(LOG_TAG, "setShouldDrawSurfaceColorStroke failed", e);
}
}
public static Set<MaterialButton.OnCheckedChangeListener>
removeAllOnCheckedChangeListeners(MaterialButton button) {
if (onCheckedChangeListeners == null) {
Log.w(LOG_TAG, "removeAllOnCheckedChangeListeners failed: field is null");
return Collections.emptySet();
}
try {
Object obj = onCheckedChangeListeners.get(button);
Set<MaterialButton.OnCheckedChangeListener> oriSet = (Set) obj;
final Set<MaterialButton.OnCheckedChangeListener> retSet = new LinkedHashSet<>();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
oriSet.removeIf(new Predicate<MaterialButton.OnCheckedChangeListener>() {
@Override
public boolean test(
MaterialButton.OnCheckedChangeListener onCheckedChangeListener) {
retSet.add(onCheckedChangeListener); // add to retSet
return true; // remove all
}
});
} else {
retSet.addAll(oriSet);
oriSet.clear();
}
return retSet;
} catch (ReflectiveOperationException e) {
Log.w(LOG_TAG, "removeAllOnCheckedChangeListeners failed", e);
return Collections.emptySet();
}
}
public static void restoreOnCheckedChangeListeners(MaterialButton button,
Set<MaterialButton.OnCheckedChangeListener> set) {
if (onCheckedChangeListeners == null) {
Log.w(LOG_TAG, "restoreOnCheckedChangeListeners failed: field is null");
}
try {
Object obj = onCheckedChangeListeners.get(button);
((Set) obj).addAll(set);
} catch (ReflectiveOperationException e) {
Log.w(LOG_TAG, "restoreOnCheckedChangeListeners failed", e);
}
}
public static void setButtonCheckedWithoutNotifyListeners(MaterialButton button,
boolean checked) {
Set<MaterialButton.OnCheckedChangeListener> set = removeAllOnCheckedChangeListeners(
button);
button.setChecked(checked);
restoreOnCheckedChangeListeners(button, set);
}
}
}
还需要添加属性定义:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="buttonGroupRecyclerViewStyle" format="reference" />
<declare-styleable name="ButtonGroupRecyclerView">
<attr name="singleSelection" format="boolean" />
<attr name="checkedButtonPos" format="integer" />
<attr name="selectionRequired" format="boolean" />
</declare-styleable>
</resources>
singleSelection 表示是单选还是复选。
checkedButtonPos 设置默认选中 Button 的 position (注意这里不是 Item Id),默认是NO_POSITION。
selectionRequired 表示是否至少选中一个。