CheckBoxGroup

本文讨论了在Android应用中,自定义的CheckboxGroup组件如何处理状态保存的问题。默认情况下,Android系统会自动保存可见UI的状态,如EditText的用户输入和CheckBox的选中状态。然而,对于自定义视图,需要手动分配ID并覆盖onSaveInstanceState()和onRestoreInstanceState()方法来确保状态正确保存和恢复。作者发现自己的CheckboxGroup控件中,CheckBox的状态未被正确保存,因此参考相关资源进行了改进,实现了手动分配ID和覆盖保存恢复状态的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前做个一个投票应用,投票可以多选,但是又有上限,比如5选3,跟多选题一样。当时就写了个组合控件。
关键代码如下:

    public void init(List<String> texts, int maxCheckedNum) {
        setOrientation(VERTICAL);

        this.maxCheckedNum = maxCheckedNum;

        if (maxCheckedNum == 1) {
            for (int i = 0; i < texts.size(); i++) {
                RadioButton radioButton = new RadioButton(getContext());
                radioButton.setText(texts.get(i));
                radioButton.setTag(i);
                radioButton.setOnCheckedChangeListener(this);
                addView(radioButton, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            }
        } else {
            for (int i = 0; i < texts.size(); i++) {
                CheckBox checkBox = new CheckBox(getContext());
                checkBox.setText(texts.get(i));
                checkBox.setTag(i);
                checkBox.setOnCheckedChangeListener(this);
                addView(checkBox, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            }
        }
    }

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if (maxCheckedNum == 1) {
            if (isChecked) {
                if (currentCheckedRadioButton != null) {
                    currentCheckedRadioButton.setChecked(false);
                }
                currentCheckedRadioButton = buttonView;
                checkedPositions.clear();
                checkedPositions.add((Integer) buttonView.getTag());
            } else {
                currentCheckedRadioButton = null;
                checkedPositions.clear();
            }
        } else {
            if (isChecked) {
                if (checkedPositions.size() < maxCheckedNum) {
                    checkedPositions.add((Integer) buttonView.getTag());
                } else {
                    Toast.makeText(getContext(), "You cann't select more!", Toast.LENGTH_SHORT).show();
                    buttonView.setChecked(false);
                }
            } else {
                checkedPositions.remove(((Integer) buttonView.getTag()));
            }
        }
    }

    public List<Integer> getCheckedPositions() {
        return checkedPositions;
    }

最近看文档,觉得这里面存在状态保存的问题。摘要如下:

However, even if you do nothing and do not implement onSaveInstanceState(), some of the activity state is restored by the Activity class’s default implementation of onSaveInstanceState(). Specifically, the default implementation calls the corresponding onSaveInstanceState() method for every View in the layout, which allows each view to provide information about itself that should be saved. Almost every widget in the Android framework implements this method as appropriate, such that any visible changes to the UI are automatically saved and restored when your activity is recreated. For example, the EditText widget saves any text entered by the user and the CheckBox widget saves whether it’s checked or not. The only work required by you is to provide a unique ID (with the android:id attribute) for each widget you want to save its state. If a widget does not have an ID, then the system cannot save its state.

You can also explicitly stop a view in your layout from saving its state by setting the android:saveEnabled attribute to “false” or by calling the setSaveEnabled() method. Usually, you should not disable this, but you might if you want to restore the state of the activity UI differently.
Although the default implementation of onSaveInstanceState() saves useful information about your activity’s UI, you still might need to override it to save additional information. For example, you might need to save member values that changed during the activity’s life (which might correlate to values restored in the UI, but the members that hold those UI values are not restored, by default).

Because the default implementation of onSaveInstanceState() helps save the state of the UI, if you override the method in order to save additional state information, you should always call the superclass implementation of onSaveInstanceState() before doing any work. Likewise, you should also call the superclass implementation of onRestoreInstanceState() if you override it, so the default implementation can restore view states.

注意If a widget does not have an ID, then the system cannot save its state.,我的组合控件中checkbox就属于这种情况。此外,根据the members that hold those UI values are not restored, by default,类中的checkedPositions也不会被保存。
所以我参考链接1链接2进行了完善:

  • 手动为 checkbox 分配 id
  • override onSavedInstanceState 和 onRestoreInstanceState 方法

关键代码如下:

public class CheckBoxGroup extends LinearLayout implements CompoundButton.OnCheckedChangeListener {

    private List<String> texts;
    private int maxCheckedNum;
    private ArrayList<Integer> checkedPositions = new ArrayList<Integer>();
    private CompoundButton currentCheckedRadioButton;

    public CheckBoxGroup(Context context) {
        super(context);
    }

    public CheckBoxGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CheckBoxGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void init(List<String> texts, int maxCheckedNum) {
        setOrientation(VERTICAL);

        this.maxCheckedNum = maxCheckedNum;

        if (maxCheckedNum == 1) {
            for (int i = 0; i < texts.size(); i++) {
                RadioButton radioButton = new RadioButton(getContext());
                radioButton.setText(texts.get(i));
                radioButton.setTag(i);
                radioButton.setOnCheckedChangeListener(this);
                radioButton.setId(generateViewId());
                addView(radioButton, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            }
        } else {
            for (int i = 0; i < texts.size(); i++) {
                CheckBox checkBox = new CheckBox(getContext());
                checkBox.setText(texts.get(i));
                checkBox.setTag(i);
                checkBox.setOnCheckedChangeListener(this);
                checkBox.setId(generateViewId());
                addView(checkBox, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            }
        }
    }

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if (maxCheckedNum == 1) {
            if (isChecked) {
                if (currentCheckedRadioButton != null) {
                    currentCheckedRadioButton.setChecked(false);
                }
                currentCheckedRadioButton = buttonView;
                checkedPositions.clear();
                checkedPositions.add((Integer) buttonView.getTag());
            } else {
                currentCheckedRadioButton = null;
                checkedPositions.clear();
            }
        } else {
            if (isChecked) {
                if (checkedPositions.size() < maxCheckedNum) {
                    checkedPositions.add((Integer) buttonView.getTag());
                } else {
                    Toast.makeText(getContext(), "You cann't select more!", Toast.LENGTH_SHORT).show();
                    buttonView.setChecked(false);
                }
            } else {
                checkedPositions.remove(((Integer) buttonView.getTag()));
            }
        }
    }

    public List<Integer> getCheckedPositions() {
        return checkedPositions;
    }

    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);

    /**
     * Generate a value suitable for use in {@link #setId(int)}.
     * This value will not collide with ID values generated at build time by aapt for R.id.
     *
     * @return a generated ID value
     */
    public static int generateViewId() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            return  View.generateViewId();
        }

        for (;;) {
            final int result = sNextGeneratedId.get();
            // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
            int newValue = result + 1;
            if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
            if (sNextGeneratedId.compareAndSet(result, newValue)) {
                return result;
            }
        }
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        super.onRestoreInstanceState(state);
        if (state instanceof SavedState){
            SavedState savedState = (SavedState) state;
            super.onRestoreInstanceState(savedState.getSuperState());
            this.checkedPositions = savedState.checkedPositions;
        } else {
            super.onRestoreInstanceState(state);
        }
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState savedState = new SavedState(superState);
        savedState.checkedPositions = this.checkedPositions;
        return savedState;
    }

    static class SavedState extends BaseSavedState implements Parcelable {
        private ArrayList<Integer> checkedPositions;

        public SavedState(Parcelable superState) {
            super(superState);
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeSerializable(this.checkedPositions);
        }

        private SavedState(Parcel in) {
            super(in);
            this.checkedPositions = (ArrayList<Integer>) in.readSerializable();
        }

        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
            public SavedState createFromParcel(Parcel source) {
                return new SavedState(source);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
}

ps, Android Studio 有个好用的插件可以帮你给你的类实现 parcelable 接口,就叫 Android Parcelable code generator

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值