Создание Android RadioGroup с несколькими RadioButton (Custom RadioGroup)

Я хочу получить Single RadioGroup, как показано на изображении ниже, для выбора группы крови. Как это может быть сделано?

введите здесь описание изображения


comment
Привет, не могли бы вы рассказать мне, как вам удалось создать этот стиль для вашего RadioButton? Я пытаюсь сделать что-то подобное, но не могу найти хороший учебник.   -  person Kashmir    schedule 17.07.2017
comment
github.com/jeffreyliu8/FlexBoxRadioGroup . @Кашмир   -  person Jeffrey Liu    schedule 21.07.2018


Ответы (3)


Я создал свой собственный RadioGridLayout, который включает код RadioGroup и расширяет GridLayout. Вы можете скопировать этот код. Для меня работает хорошо. После того, как вы можете использовать этот макет в своем xml и настроить его как макет сетки.

Для R.styleable.RadioGridLayout_checked я использовал такой код:

<resources>
    <declare-styleable name="RadioGridLayout">
        <attr name="checked" format="integer" />
    </declare-styleable>
</resources>
public class RadioGridLayout extends GridLayout {

    private int mCheckedId = -1;
    private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;
    private boolean mProtectFromCheckedChange = false;
    private OnCheckedChangeListener mOnCheckedChangeListener;
    private PassThroughHierarchyChangeListener mPassThroughListener;

    private void setCheckedId(@IdRes int id) {
        mCheckedId = id;
        if (mOnCheckedChangeListener != null) {
            mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
        }
        AutofillManager afm = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            afm = getContext().getSystemService(AutofillManager.class);
        }
        if (afm != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                afm.notifyValueChanged(this);
            }
        }
    }

    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
        mOnCheckedChangeListener = listener;
    }

    public interface OnCheckedChangeListener {
        void onCheckedChanged(RadioGridLayout group, @IdRes int checkedId);
    }

    private int mInitialCheckedId = View.NO_ID;

    public RadioGridLayout(Context context) {
        super(context);
        setOrientation(VERTICAL);
        init();
    }

    public RadioGridLayout(Context context, AttributeSet attrs) {
        super(context, attrs);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
                setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
            }
        }

        TypedArray attributes = context.obtainStyledAttributes(
                attrs,
                R.styleable.RadioGridLayout,
                R.attr.radioButtonStyle, 0);

        int value = attributes.getResourceId(R.styleable.RadioGridLayout_checked, View.NO_ID);
        if (value != View.NO_ID) {
            mCheckedId = value;
            mInitialCheckedId = value;
        }

        attributes.recycle();
        init();
    }

    private void init() {
        mChildOnCheckedChangeListener = new CheckedStateTracker();
        mPassThroughListener = new PassThroughHierarchyChangeListener();
        super.setOnHierarchyChangeListener(mPassThroughListener);
    }

    @Override
    public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
        mPassThroughListener.mOnHierarchyChangeListener = listener;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (mCheckedId != -1) {
            mProtectFromCheckedChange = true;
            setCheckedStateForView(mCheckedId, true);
            mProtectFromCheckedChange = false;
            setCheckedId(mCheckedId);
        }
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (child instanceof RadioButton) {
            final RadioButton button = (RadioButton) child;
            if (button.isChecked()) {
                mProtectFromCheckedChange = true;
                if (mCheckedId != -1) {
                    setCheckedStateForView(mCheckedId, false);
                }
                mProtectFromCheckedChange = false;
                setCheckedId(button.getId());
            }
        }

        super.addView(child, index, params);
    }

    public void check(@IdRes int id) {
        if (id != -1 && (id == mCheckedId)) {
            return;
        }

        if (mCheckedId != -1) {
            setCheckedStateForView(mCheckedId, false);
        }

        if (id != -1) {
            setCheckedStateForView(id, true);
        }

        setCheckedId(id);
    }

    private void setCheckedStateForView(int viewId, boolean checked) {
        View checkedView = findViewById(viewId);
        if (checkedView != null && checkedView instanceof RadioButton) {
            ((RadioButton) checkedView).setChecked(checked);
        }
    }

    @IdRes
    public int getCheckedRadioButtonId() {
        return mCheckedId;
    }

    public void clearCheck() {
        check(-1);
    }

    @Override
    public GridLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new GridLayout.LayoutParams(getContext(), attrs);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof RadioGroup.LayoutParams;
    }

    @Override
    protected GridLayout.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams();
    }

    @Override
    public CharSequence getAccessibilityClassName() {
        return RadioGroup.class.getName();
    }

    public static class LayoutParams extends GridLayout.LayoutParams {

        public LayoutParams(Spec rowSpec, Spec columnSpec) {
            super(rowSpec, columnSpec);
        }

        public LayoutParams() {
            super();
        }

        public LayoutParams(ViewGroup.LayoutParams params) {
            super(params);
        }

        public LayoutParams(MarginLayoutParams params) {
            super(params);
        }

        public LayoutParams(GridLayout.LayoutParams source) {
            super(source);
        }

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

        @Override
        protected void setBaseAttributes(TypedArray a,
                                         int widthAttr, int heightAttr) {

            if (a.hasValue(widthAttr)) {
                width = a.getLayoutDimension(widthAttr, "layout_width");
            } else {
                width = WRAP_CONTENT;
            }

            if (a.hasValue(heightAttr)) {
                height = a.getLayoutDimension(heightAttr, "layout_height");
            } else {
                height = WRAP_CONTENT;
            }
        }
    }

    private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (mProtectFromCheckedChange) {
                return;
            }

            mProtectFromCheckedChange = true;
            if (mCheckedId != -1) {
                setCheckedStateForView(mCheckedId, false);
            }
            mProtectFromCheckedChange = false;

            int id = buttonView.getId();
            setCheckedId(id);
        }
    }

    private class PassThroughHierarchyChangeListener implements
            ViewGroup.OnHierarchyChangeListener {
        private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;

        @Override
        public void onChildViewAdded(View parent, View child) {
            if (parent == RadioGridLayout.this && child instanceof RadioButton) {
                int id = child.getId();
                if (id == View.NO_ID) {
                    id = View.generateViewId();
                    child.setId(id);
                }
                ((RadioButton) child).setOnCheckedChangeListener(
                        mChildOnCheckedChangeListener);
            }

            if (mOnHierarchyChangeListener != null) {
                mOnHierarchyChangeListener.onChildViewAdded(parent, child);
            }
        }

        @Override
        public void onChildViewRemoved(View parent, View child) {
            if (parent == RadioGridLayout.this && child instanceof RadioButton) {
                ((RadioButton) child).setOnCheckedChangeListener(null);
            }

            if (mOnHierarchyChangeListener != null) {
                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
            }
        }
    }

    @Override
    public void onProvideAutofillStructure(ViewStructure structure, int flags) {
        super.onProvideAutofillStructure(structure, flags);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            structure.setDataIsSensitive(mCheckedId != mInitialCheckedId);
        }
    }

    @Override
    public void autofill(AutofillValue value) {
        if (!isEnabled()) return;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (!value.isList()) {
                Timber.w(value + " could not be autofilled into " + this);
                return;
            }
        }

        int index = 0;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            index = value.getListValue();
        }
        final View child = getChildAt(index);
        if (child == null) {
            Timber.w("RadioGroup.autoFill(): no child with index %s", index);
            return;
        }

        check(child.getId());
    }

    @Override
    public int getAutofillType() {
        return isEnabled() ? AUTOFILL_TYPE_LIST : AUTOFILL_TYPE_NONE;
    }

    @Override
    public AutofillValue getAutofillValue() {
        if (!isEnabled()) return null;

        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getId() == mCheckedId) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    return AutofillValue.forList(i);
                }
            }
        }
        return null;
    }
}
person Роман Телухин    schedule 09.07.2019

Одно из возможных решений описано @Suhail k k.
Также посмотрите здесь (просто подробнее, чтобы ответить @Suhail k k.).

Я могу предложить другой вариант:
1) создайте собственные представления (например, ImageView) и расположите их по своему усмотрению в макете; поставить на каждый из них android:tag (серийный номер, например, от 0 до 7);
2) сделать селектор для каждого из них (состояние normal/selected);
3) во время выполнения поставить onClickListener на эти такие элементы:

    @Override
    public void onClick(View v) {
        images.get(currentSelected).setSelected(false);
        currentSelected = (int) v.getTag(); 
        images.get(currentSelected).setSelected(true);
    }

Имхо, вам было бы намного проще реализовать.
Надеюсь, у вас есть идея в целом, ваша реализация может быть другой :)

person Goltsev Eugene    schedule 08.04.2016

создайте две радиогруппы. Одна для первой строки, а другая для второй строки. Затем добавьте следующий код в свой код Java

 mFirstGroup = (RadioGroup) findViewById(R.id.first_group);
 mSecondGroup = (RadioGroup) findViewById(R.id.second_group);
 mFirstGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
     @Override
     public void onCheckedChanged(RadioGroup group, int checkedId) {
         if (checkedId != -1 && isChecking) {
             isChecking = false;
             mSecondGroup.clearCheck();
             mCheckedId = checkedId;
         }
         isChecking = true;
     }
 });

 mSecondGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
     @Override
     public void onCheckedChanged(RadioGroup group, int checkedId) {
         if (checkedId != -1 && isChecking) {
             isChecking = false;
             mFirstGroup.clearCheck();
             mCheckedId = checkedId;
         }
         isChecking = true;
     }
 });
person Suhail k k    schedule 08.04.2016
comment
Спасибо. Но, как я упоминал в своем вопросе, я хочу добиться этого с помощью Single RadioGroup. - person pRaNaY; 08.04.2016
comment
да. Но вы можете заархивировать функциональность отдельной радиогруппы. - person Suhail k k; 08.04.2016
comment
Я предполагаю, что пока вы не сделаете свою собственную реализацию представления RadioGroup, будет невозможно достичь желаемого дизайна с помощью одной RadioGroup. Проверьте stackoverflow.com/questions/11168538/ - person Goltsev Eugene; 08.04.2016
comment
@ГольцевЕвгений. Хм! точно понял мою мысль. Да, я хочу реализовать пользовательскую RadioGroup для достижения этой цели. Я пытаюсь реализовать пользовательскую RadioGroup. - person pRaNaY; 08.04.2016
comment
Почему ты не хочешь попробовать мое решение? Это действительно намного проще, и это также позволит только один выбор. - person Goltsev Eugene; 08.04.2016
comment
Если вам нужно иметь дело со всем макетом, поместите все эти представления в свой макет и действуйте с ним, как хотите. - person Goltsev Eugene; 08.04.2016
comment
@ГольцевЕвгений . Ваше решение приятно. Но если мы найдем решение с одной пользовательской RadioGroup, это еще лучше. - person pRaNaY; 08.04.2016
comment
Хорошо, может быть, stackoverflow.com/ questions/20456322/ помогут вам. Также там упоминается третья часть библиотеки, может быть, это поможет. - person Goltsev Eugene; 08.04.2016