Программное добавление настраиваемого переключателя не работает в Android API 22

Я хотел бы использовать настраиваемые переключатели в приложении для Android, которое должно ориентироваться на API 22 и более поздние версии. Я бы хотел что-то вроде этого

Программное добавление настраиваемого переключателя не работает в Android API 22

По завершении источник текста и значений переключателя будет поступать из базы данных SQLite, и поэтому мне нужно будет иметь возможность добавлять переключатели программно, а не определять их в файле макета XML.

Я попытался это сделать, используя код, описанный в этом сообщении блога https://crosp.net/blog/software-development/mobile/android/creating-custom-radio-groups-radio-buttons-android/, и код, который он описывает, можно найти здесь https://github.com/CROSP/custom-radio-group-radio-button-android.

Я создал пример, который работает, когда я пробую его на своем эмуляторе, это API 25. Однако проблема возникает, когда я запускаю его на устройстве / эмуляторе с API 22. Используя Android Studio, я запускал его с помощью Nexus 4 API. 22, но, к сожалению, код не работает. Когда я говорю, что это не работает, все, что вы видите, - это пустой экран, а не настраиваемые переключатели, которые я вижу при запуске на API 25. Я посмотрел на logcat и, похоже, не было никаких ошибок, о которых сообщалось. .

Итак, в итоге у меня есть код, который работает для API 25, но не для API 22. И я хотел бы знать, как исправить код, чтобы он работал в API 22.

Я создал самую простую версию кода, который может воспроизвести проблему. Этот код ниже

MainActivity.java

package net.myexample.radioapp;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.LinearLayout;
import net.myexample.radioapp.CustomRadioButtons.PresetRadioGroup;
import net.myexample.radioapp.CustomRadioButtons.PresetValueButton;

 public class MainActivity extends AppCompatActivity
    {
        private PresetRadioGroup mPledgeDescriptionsContainer;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mPledgeDescriptionsContainer = 
            findViewById(R.id.pledge_descriptions_container);
            int counter =0;
            do {
                String description = "test " + counter;
                double amount = counter;
                PresetValueButton rbtn = new PresetValueButton(this);
                rbtn.setId(counter);
                rbtn.setLayoutParams(new 
    LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1.0f));
                rbtn.setUnit(description);
                rbtn.setValue("£" + amount);
                mPledgeDescriptionsContainer.addView(rbtn);
                counter++;
            } while (counter < 5);
        }
    }

Следующие файлы java очень похожи на файлы https://github.com/CROSP/custom-radio-group-radio-button-android

PresetRadioGroup.java

package net.myexample.radioapp.CustomRadioButtons;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.annotation.IdRes;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import net.myexample.radioapp.R;
import java.util.HashMap;

public class PresetRadioGroup extends LinearLayout {


    private int mCheckedId = View.NO_ID;
    private boolean mProtectFromCheckedChange = false;
    private OnCheckedChangeListener mOnCheckedChangeListener;
    private HashMap<Integer, View> mChildViewsMap = new HashMap<>();
    private PassThroughHierarchyChangeListener mPassThroughListener;
    private RadioCheckable.OnCheckedChangeListener mChildOnCheckedChangeListener;

    public PresetRadioGroup(Context context) {
        super(context);
        setupView();
    }

    public PresetRadioGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        parseAttributes(attrs);
        setupView();
    }

    @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
    public PresetRadioGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        parseAttributes(attrs);
        setupView();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public PresetRadioGroup(
            Context context,
            AttributeSet attrs,
            int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        parseAttributes(attrs);
        setupView();
    }

    private void parseAttributes(AttributeSet attrs) {
        TypedArray a = getContext().obtainStyledAttributes(attrs,
                R.styleable.PresetRadioGroup, 0, 0);
        try {
            mCheckedId =
                    a.getResourceId(R.styleable.PresetRadioGroup_presetRadioCheckedId,
                    View.NO_ID);
        } finally {
            a.recycle();
        }
    }

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

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

    @Override
    public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
        // the user listener is delegated to our pass-through listener
        mPassThroughListener.mOnHierarchyChangeListener = listener;
    }

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

    private void setCheckedId(@IdRes int id, boolean isChecked) {
        mCheckedId = id;
        if (mOnCheckedChangeListener != null) {
            mOnCheckedChangeListener.onCheckedChanged(this,
                    mChildViewsMap.get(id), isChecked, mCheckedId);
        }
    }

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

    public void clearCheck() {
        check(View.NO_ID);
    }

    public void check(@IdRes int id) {
        if (id != View.NO_ID && (id == mCheckedId)) {
            return;
        }

        if (mCheckedId != View.NO_ID) {
            setCheckedStateForView(mCheckedId, false);
        }

        if (id != View.NO_ID) {
            setCheckedStateForView(id, true);
        }
        setCheckedId(id, true);
    }

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

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

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

    public OnCheckedChangeListener getOnCheckedChangeListener() {
        return mOnCheckedChangeListener;
    }

    public static interface OnCheckedChangeListener {
        void onCheckedChanged(
                View radioGroup, View radioButton, boolean isChecked, int checkedId);
    }

    public static class LayoutParams extends LinearLayout.LayoutParams {
        /**
         * {@inheritDoc}
         */
        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        /**
         * {@inheritDoc}
         */
        public LayoutParams(int w, int h) {
            super(w, h);
        }

        /**
         * {@inheritDoc}
         */
        public LayoutParams(int w, int h, float initWeight) {
            super(w, h, initWeight);
        }

        /**
         * {@inheritDoc}
         */
        public LayoutParams(ViewGroup.LayoutParams p) {
            super(p);
        }

        /**
         * {@inheritDoc}
         */
        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }

        /**
         * <p>Fixes the child's width to
         * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the child's
         * height to  {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
         * when not specified in the XML file.</p>
         *
         * @param a          the styled attributes set
         * @param widthAttr  the width attribute to fetch
         * @param heightAttr the height attribute to fetch
         */
        @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 RadioCheckable.OnCheckedChangeListener {
        @Override
        public void onCheckedChanged(View buttonView, boolean isChecked) {
            // prevents from infinite recursion
            if (mProtectFromCheckedChange) {
                return;
            }

            mProtectFromCheckedChange = true;
            if (mCheckedId != View.NO_ID) {
                setCheckedStateForView(mCheckedId, false);
            }
            mProtectFromCheckedChange = false;

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

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

        /**
         * {@inheritDoc}
         */
        public void onChildViewAdded(View parent, View child) {
            if (parent == PresetRadioGroup.this && child instanceof RadioCheckable) {
                int id = child.getId();
                // generates an id if it's missing
                if (id == View.NO_ID) {
                    id = ViewUtils.generateViewId();
                    child.setId(id);
                }
                ((RadioCheckable) child).addOnCheckChangeListener(
                        mChildOnCheckedChangeListener);
                mChildViewsMap.put(id, child);
            }

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

        /**
         * {@inheritDoc}
         */
        public void onChildViewRemoved(View parent, View child) {
            if (parent == PresetRadioGroup.this && child instanceof RadioCheckable) {
                ((RadioCheckable) child)
                        .removeOnCheckChangeListener(mChildOnCheckedChangeListener);
            }
            mChildViewsMap.remove(child.getId());
            if (mOnHierarchyChangeListener != null) {
                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
            }
        }
    }
}

PresetValueButton.java

  package net.myexample.radioapp.CustomRadioButtons;
    import android.content.Context;
    import android.content.res.Resources;
    import android.content.res.TypedArray;
    import android.graphics.Color;
    import android.graphics.drawable.Drawable;
    import android.os.Build;
    import android.support.annotation.Nullable;
    import android.support.annotation.RequiresApi;
    import android.util.AttributeSet;
    import android.view.LayoutInflater;
    import android.view.MotionEvent;
    import android.view.View;
    import android.widget.RelativeLayout;
    import android.widget.TextView;
    import net.myexample.radioapp.R;
    import java.util.ArrayList;


    public class PresetValueButton extends RelativeLayout implements RadioCheckable {
        private TextView mValueTextView, mUnitTextView;
        public static final int DEFAULT_TEXT_COLOR = Color.WHITE;
        private String mValue;
        private String mUnit;
        private int mValueTextColor;
        private int mUnitTextColor;
        private int mPressedTextColor;
        private Drawable mInitialBackgroundDrawable;
        private OnClickListener mOnClickListener;
        private OnTouchListener mOnTouchListener;
        private boolean mChecked;
        private ArrayList<OnCheckedChangeListener> mOnCheckedChangeListeners 
                = new ArrayList<>();

        public PresetValueButton(Context context) {
            super(context, null, R.style.PresetLayoutButton);
            mValueTextColor = Color.WHITE;
            mUnitTextColor = Color.WHITE;
            mPressedTextColor =Color.WHITE;
            setupView();
        }

        public PresetValueButton(Context context, AttributeSet attrs) {
            super(context, attrs);
            parseAttributes(attrs);
            setupView();
        }

        @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
        public PresetValueButton(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            parseAttributes(attrs);
            setupView();
        }

        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        public PresetValueButton(Context context, AttributeSet attrs,
                                 int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            parseAttributes(attrs);
            setupView();
        }

        private void parseAttributes(AttributeSet attrs) {
            TypedArray a = getContext().obtainStyledAttributes(attrs,
                    R.styleable.PresetValueButton, 0, 0);
            Resources resources = getContext().getResources();
            try {
                mValue = a.getString(R.styleable.PresetValueButton_presetButtonValueText);
                mUnit = a.getString(R.styleable.PresetValueButton_presetButtonUnitText);
                mValueTextColor = a.getColor(
                        R.styleable.PresetValueButton_presetButtonValueTextColor,
                        resources.getColor(R.color.fontWhite));
                mPressedTextColor = a.getColor(
                        R.styleable.PresetValueButton_presetButtonPressedTextColor
                        , Color.WHITE);
                mUnitTextColor = a.getColor(
                        R.styleable.PresetValueButton_presetButtonUnitTextColor
                        , resources.getColor(R.color.fontWhite));
            } finally {
                a.recycle();
            }
        }

        private void setupView() {
            inflateView();
            bindView();
            setCustomTouchListener();
        }

        protected void inflateView() {
            LayoutInflater inflater = LayoutInflater.from(getContext());
            inflater.inflate(R.layout.custom_preset_button, this, true);
            mValueTextView = (TextView) findViewById(R.id.text_view_value);
            mUnitTextView = (TextView) findViewById(R.id.text_view_unit);
            mInitialBackgroundDrawable = getBackground();
        }

        protected void bindView() {
            if (mUnitTextColor != DEFAULT_TEXT_COLOR) {
                mUnitTextView.setTextColor(mUnitTextColor);
            }
            if (mValueTextColor != DEFAULT_TEXT_COLOR) {
                mValueTextView.setTextColor(mValueTextColor);
            }
            mUnitTextView.setText(mUnit);
            mValueTextView.setText(mValue);
        }

        @Override
        public void setOnClickListener(@Nullable OnClickListener l) {
            mOnClickListener = l;
        }

        protected void setCustomTouchListener() {
            super.setOnTouchListener(new TouchListener());
        }

        @Override
        public void setOnTouchListener(OnTouchListener onTouchListener) {
            mOnTouchListener = onTouchListener;
        }

        public OnTouchListener getOnTouchListener() {
            return mOnTouchListener;
        }

        private void onTouchDown(MotionEvent motionEvent) {
            setChecked(true);
        }

        private void onTouchUp(MotionEvent motionEvent) {
            // Handle user defined click listeners
            if (mOnClickListener != null) {
                mOnClickListener.onClick(this);
            }
        }

        public void setCheckedState() {
            setBackgroundResource(R.drawable.background_shape_preset_button__pressed);
            mValueTextView.setTextColor(mPressedTextColor);
            mUnitTextView.setTextColor(mPressedTextColor);
        }

        public void setNormalState() {
            setBackgroundDrawable(mInitialBackgroundDrawable);
            mValueTextView.setTextColor(mValueTextColor);
            mUnitTextView.setTextColor(mUnitTextColor);
        }

        public String getValue() {
            return mValue;
        }

        public void setValue(String value) {
            mValue = value;
            mValueTextView.setText(value);
        }

        public String getUnit() {
            return mUnit;
        }

        public void setUnit(String unit) {
            mUnit = unit;
            mUnitTextView.setText(unit);
        }

        @Override
        public void setChecked(boolean checked) {
            if (mChecked != checked) {
                mChecked = checked;
                if (!mOnCheckedChangeListeners.isEmpty()) {
                    for (int i = 0; i < mOnCheckedChangeListeners.size(); i++) {
                        mOnCheckedChangeListeners.get(i).onCheckedChanged(this, mChecked);
                    }
                }
                if (mChecked) {
                    setCheckedState();
                } else {
                    setNormalState();
                }
            }
        }

        @Override
        public boolean isChecked() {
            return mChecked;
        }

        @Override
        public void toggle() {
            setChecked(!mChecked);
        }

        @Override
        public void addOnCheckChangeListener(OnCheckedChangeListener onCheckedChangeListener) {
            mOnCheckedChangeListeners.add(onCheckedChangeListener);
        }

        @Override
        public void removeOnCheckChangeListener(
                OnCheckedChangeListener onCheckedChangeListener) {
            mOnCheckedChangeListeners.remove(onCheckedChangeListener);
        }

        private final class TouchListener implements OnTouchListener {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        onTouchDown(event);
                        break;
                    case MotionEvent.ACTION_UP:
                        onTouchUp(event);
                        break;
                }
                if (mOnTouchListener != null) {
                    mOnTouchListener.onTouch(v, event);
                }
                return true;
            }
        }
    }

RadioCheckable.java

package net.myexample.radioapp.CustomRadioButtons;
import android.view.View;
import android.widget.Checkable;

public interface RadioCheckable extends Checkable {
    void addOnCheckChangeListener(OnCheckedChangeListener onCheckedChangeListener);
    void removeOnCheckChangeListener(OnCheckedChangeListener onCheckedChangeListener);

    public static interface OnCheckedChangeListener {
        void onCheckedChanged(View buttonView, boolean isChecked);
    }
}

ViewUtils.java

package net.myexample.radioapp.CustomRadioButtons;
import android.annotation.SuppressLint;
import android.os.Build;
import android.view.View;
import java.util.concurrent.atomic.AtomicInteger;

public class ViewUtils {
    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);

    @SuppressLint("NewApi")
    public static int generateViewId() {

        if (Build.VERSION.SDK_INT < 17) {
            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;
                }
            }
        } else {
            return View.generateViewId();
        }
    }
}

папка макета activity_main.xml

 <LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:tools = "http://schemas.android.com/tools"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent"
    android:orientation = "vertical"
    tools:context = ".MainActivity">
        <net.myexample.radioapp.CustomRadioButtons.PresetRadioGroup
            android:layout_width = "match_parent"
            android:layout_height = "wrap_content"
            android:id = "@+id/pledge_descriptions_container"
            android:layout_alignParentRight = "true"
            android:orientation = "vertical">
        </net.myexample.radioapp.CustomRadioButtons.PresetRadioGroup>
    </LinearLayout>

custom_preset_button.xml

<merge xmlns:android = "http://schemas.android.com/apk/res/android">
  <TextView
      android:id = "@+id/text_view_value"
      style = "@style/PresetLayoutButton_ValueText"
      android:layout_width = "100dp"
      android:layout_height = "wrap_content"
      android:layout_alignParentLeft = "true"
      android:layout_alignParentTop = "true"
      />
  <TextView
      android:id = "@+id/text_view_unit"
      style = "@style/PresetLayoutButton_UnitText"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_toRightOf = "@+id/text_view_value"
      />
</merge>

выдвижная папка

background_selector_preset_button.xml

<?xml version = "1.0" encoding = "utf-8"?>
<selector xmlns:android = "http://schemas.android.com/apk/res/android">
    <item android:drawable = "@drawable/background_shape_preset_button__pressed" android:state_focused = "true"/>
    <item android:drawable = "@drawable/background_shape_preset_button__pressed" android:state_pressed = "true"/>
    <item android:drawable = "@drawable/background_shape_preset_time_button"/>
</selector>

background_shape_preset_button_pressed.xml

 <?xml version = "1.0" encoding = "utf-8"?>
    <shape xmlns:android = "http://schemas.android.com/apk/res/android"
           android:shape = "rectangle">
        <solid android:color = "#fe9900"/>
    </shape>

background_shape_preset_time_button.xml

   <?xml version = "1.0" encoding = "utf-8"?>
    <shape xmlns:android = "http://schemas.android.com/apk/res/android"
           android:shape = "rectangle">
        <solid android:color = "@color/fontWhite"/>
        <stroke
            android:width = "0.2dp"
            android:color = "#2f3f3f"/>
    </shape>

папка значений attrs_preset_radio_group.xml

 <?xml version = "1.0" encoding = "utf-8"?>
    <resources>
        <declare-styleable name = "PresetRadioGroup">
            <attr name = "presetRadioCheckedId" format = "reference"/>
        </declare-styleable>
    </resources>

attrs_preset_value_button.xml

<?xml version = "1.0" encoding = "utf-8"?>
<resources>
  <declare-styleable name = "PresetValueButton">
    <attr name = "presetButtonValueText" format = "integer"/>
    <attr name = "presetButtonUnitText" format = "string"/>
    <attr name = "presetButtonValueTextColor" format = "color"/>
    <attr name = "presetButtonPressedTextColor" format = "color"/>
    <attr name = "presetButtonUnitTextColor" format = "color"/>
  </declare-styleable>
</resources>

colors.xml

 <?xml version = "1.0" encoding = "utf-8"?>
    <resources>
        <color name = "fontWhite">#FFF</color>
        <color name = "main_background">#000</color>
    </resources>

strings.xml

 <resources>
        <string name = "app_name">my app</string>
    </resources>

styles.xml

 <resources>
        <style name = "AppTheme" parent = "Theme.AppCompat.Light.NoActionBar">
            <item name = "android:windowBackground">@color/main_background</item>
        </style>
        <style name = "PresetLayoutButton">
            <item name = "android:background">@drawable/background_selector_preset_button</item>
            <item name = "android:clickable">true</item>
        </style>
        <style name = "PresetLayoutButton_ValueText">
            <item name = "android:textColor">@color/fontWhite</item>
        </style>
        <style name = "PresetLayoutButton_UnitText">
            <item name = "android:textColor">@color/fontWhite</item>
        </style>
    </resources>

Можете ли вы опубликовать журналы или трассировку стека?

Yash Fatnani 01.11.2018 14:50

Я не могу найти много. На самом деле это вообще не ошибка, поэтому нет трассировки стека, на которую можно было бы смотреть. Я только что запустил код на Amazon Kindle API 22 и увидел предупреждение «Попытка получить пакет 0x0103003e, который недействителен или находится в цикле», но это могло быть легко не связано.

Dave Barnett 01.11.2018 15:28
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
2
931
0

Другие вопросы по теме