Следующий фокус на пользовательском представлении внутри RecyclerView - PullRequest
0 голосов
/ 28 ноября 2018

У меня есть RecyclerView, которые представляют формы.Эти RecyclerView сделаны из нескольких типов View.Каждый из них имеет base_layout и специфический View в зависимости от типа данных, который они представляют (например: строка будет представлена ​​EditText).

Вот файл макета, представляющий string тип данных:

<android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <include android:id="@+id/base_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        layout="@layout/view_form_field_base"
        app:label="@{viewModel.label}"
        app:errorMessage="@{viewModel.formElement.errorMessage}"
        app:canGenerate="@{viewModel.formElement.canBeGenerated &amp;&amp; viewModel.editable}" />

    <com.my.app.views.edittext.ActionEditText
        android:id="@+id/field_content"
        android:layout_width="0dp"
        android:layout_height="wrap_content"

        android:layout_marginEnd="8dp"
        android:padding="@{viewModel.editable ? 10 : 0}"

        android:background="@{viewModel.editable ? @drawable/my_edit_text_background : @drawable/empty_drawable}"

        android:clickable="@{viewModel.editable}"
        android:cursorVisible="@{viewModel.editable}"
        android:focusableInTouchMode="@{viewModel.editable}"
        android:focusable="@{viewModel.editable}"
        android:inputType="textMultiLine|textNoSuggestions"

        android:ellipsize="end"
        android:text="@={viewModel.formElement.value}"
        app:layout_constraintBottom_toTopOf="@+id/error_message"
        app:layout_constraintEnd_toStartOf="@+id/generate_button"
        app:layout_constraintStart_toStartOf="@+id/label"
        app:layout_constraintTop_toBottomOf="@+id/label" />

</android.support.constraint.ConstraintLayout>

Когда форма просто состоит из строк, их проблем нет.Но их также несколько пользовательских взглядов.Например:

<android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <include android:id="@+id/base_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        layout="@layout/view_form_field_base"
        app:label="@{viewModel.label}"
        app:errorMessage="@{viewModel.formElement.errorMessage}"/>

    <com.my.app.views.dynamic_list.DynamicRadioGroup
        android:id="@+id/field_content"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="@+id/label"
        app:layout_constraintEnd_toStartOf="@+id/medical_record_level"
        app:layout_constraintBottom_toTopOf="@+id/error_message"
        app:layout_constraintTop_toBottomOf="@+id/label"

        app:setEditable="@{viewModel.editable}"
        android:layout_marginEnd="8dp" />

</android.support.constraint.ConstraintLayout>

Когда перед полем пользовательского типа, подобным указанному выше, предшествует строковый тип, происходит сбой приложения, когда мы нажимаем на кнопку next на клавиатуре при фокусировкестроковое поле типа.

Трассировка стека аварии:

java.lang.IllegalStateException: focus search returned a view that wasn't able to take focus!
        at android.widget.TextView.onKeyUp(TextView.java:7520)
        at android.view.KeyEvent.dispatch(KeyEvent.java:3361)
        at android.view.View.dispatchKeyEvent(View.java:10615)
        at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1697)
        at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1697)
        at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1697)
        at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1697)
        at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1697)
        at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1697)
        at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1697)
        at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1697)
        at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1697)
        at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1697)
        at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1697)
        at com.android.internal.policy.DecorView.superDispatchKeyEvent(DecorView.java:590)
        at com.android.internal.policy.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1885)
        at android.support.v4.view.KeyEventDispatcher.activitySuperDispatchKeyEventPre28(KeyEventDispatcher.java:130)
        at android.support.v4.view.KeyEventDispatcher.dispatchKeyEvent(KeyEventDispatcher.java:87)
        at android.support.v4.app.SupportActivity.dispatchKeyEvent(ComponentActivity.java:126)
        at android.support.v7.app.AppCompatActivity.dispatchKeyEvent(AppCompatActivity.java:535)
        at com.pascalwelsch.compositeandroid.activity.CompositeActivity.super_dispatchKeyEvent(CompositeActivity.java:2264)
        at com.pascalwelsch.compositeandroid.activity.ActivityDelegate.dispatchKeyEvent(ActivityDelegate.java:756)
        at com.pascalwelsch.compositeandroid.activity.CompositeActivity.dispatchKeyEvent(CompositeActivity.java:278)
        at android.support.v7.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:59)
        at android.support.v7.app.AppCompatDelegateImpl$AppCompatWindowCallback.dispatchKeyEvent(AppCompatDelegateImpl.java:2533)
        at android.support.v7.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:59)
        at com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:467)
        at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:5041)
        at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:5003)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4532)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4585)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4551)
        at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4684)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4559)
        at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4741)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4532)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4585)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4551)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4559)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4532)
        at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7092)
        at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:7024)
        at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6985)
        at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:4181)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6776)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1496)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1386)

Наконец, вот класс Java DynamicRadioGroup:

public class DynamicRadioGroup extends ViewSwitcher {
    private CharSequence[] values;
    private int defaultValuePosition;
    private int orientation;
    private String selectedValue;
    private RadioGroup radioGroup;
    private TextView checkedOption;
    private OnValueChangeListener listener;

    public DynamicRadioGroup(Context context) {
        super(context);
        init();
    }

    /**
     * Initialize the view.
     */
    private void init() {
        //The ViewSwitcher will wrap its currently displayed layout.
        this.setMeasureAllChildren(false);

        //Tried this to fix the issue.
        this.setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);

        radioGroup = new RadioGroup(getContext());
        checkedOption = new TextView(getContext(), null, dynamicRadioGroupStyle);
        checkedOption.setGravity(Gravity.CENTER_VERTICAL);
        radioGroup.setOrientation(orientation);

        radioGroup.setOnCheckedChangeListener((group, checkedId) -> {
            RadioButton radioButton = group.findViewById(checkedId);
            if (radioButton != null) {
                selectedValue = radioButton.getText().toString();
                checkedOption.setText(selectedValue);
                if (listener != null)
                    listener.onValuesChanged(this, selectedValue, checkedId);
            }
        });

        addView(radioGroup, 0);
        addView(checkedOption, 1);
    }

    //region GETTER & SETTER
    public void setValues(CharSequence[] values) {
        this.values = values;
        if(CollectionUtils.isNullOrEmpty(values)
            return;
        radioGroup.removeAllViews();
        for (int i = 0; i < this.values.length; i++) {
            RadioButton radioButton = new RadioButton(getContext());
            radioButton.setText(this.values[i]);
            radioButton.setId(i);
            if (radioGroup.getOrientation() == LinearLayout.HORIZONTAL)
                radioButton.setLayoutParams(new RadioGroup.LayoutParams(0, RadioGroup.LayoutParams.WRAP_CONTENT, 1.0f));
            radioGroup.addView(radioButton);
        }

    }

    public void setDefaultValuePosition(int defaultValuePosition) {
        this.defaultValuePosition = defaultValuePosition;
        RadioButton radioButton = (RadioButton) radioGroup.getChildAt(this.defaultValuePosition);
        if (radioButton != null) {
            radioGroup.clearCheck();
            radioButton.setChecked(true);
        }
    }

    public void setEditable(boolean editable) {
        setDisplayedChild(editable ? 0 : 1);
        setFocusable(editable);
    }

    public String getSelectedValue() {
        return selectedValue;
    }

    public void setSelectedValue(String selectedValue) {
        this.selectedValue = selectedValue;
    }

    public int getOrientation() {
        return orientation;
    }

    public void setOrientation(int orientation) {
        this.orientation = orientation;
        if (radioGroup != null)
            radioGroup.setOrientation(orientation);
    }

    public void setOnValueChangeListener(OnValueChangeListener listener) {
        this.listener = listener;
    }

    @Override
    protected boolean onRequestFocusInDescendants(final int dir, final Rect rect) {
        final View view = radioGroup.findViewById(radioGroup.getCheckedRadioButtonId());
        return view.requestFocus();
    }
    //endregion

}

Как мы видим, я пыталсяиграть по методу setDescendantFocusability, но безуспешно (может быть, я делаю не так)?

Я также попытался установить следующий фокус на следующем элементе RecyclerView, используя этот фрагмент кода:

@Override
public void onBindViewHolder(BaseFormViewHolder viewHolder, int index) {
    // (omitted stuffs)
    RecyclerView.ViewHolder previousViewHolder = getRecyclerView().findViewHolderForAdapterPosition(index -1);
    if(previousViewHolder != null)
        previousViewHolder.itemView.setNextFocusDownId(viewHolder.itemView.getId());

}

Другая информация состоит в том, что я использовал Stetho для анализа своих макетов и обнаружил в Accessibility Properties из DynamicRadioGroup это сообщение:

Поиск фокуса Android вернул aвид, который не мог сфокусироваться!

Не уверен, что с этим делать, но это может помочь.


TL; DR

Дажеесли я не получу полный ответ здесь, я хотел бы лучше понять, как это работает, получив ответы на вопросыНесколько вопросов:

  • Как создать пользовательский View / custom ViewGroup focusable и реагировать на усиление / потерю фокуса.
  • Как заставить View внутри ааRecyclerView чтобы указать на следующий элемент при разговоре о nextFocus.
  • Что на самом деле означает вышеупомянутая трассировка стека?
  • Что на самом деле означает сообщение в инспекторе Stetho?

Спасибо за чтение!

Ответы [ 2 ]

0 голосов
/ 06 декабря 2018

Эта ошибка возникает, когда значение параметра ime EditorInfo.IME_ACTION_NEXT и следующий найденный объект не фокусируются или его родитель не фокусируется.

Как упомянуто в документации IME_ACTION_NEXT здесь

Биты IME_MASK_ACTION: ключ действия выполняет «следующую» операцию, переводя пользователя в следующее поле, которое будет принимать текст.

Таким образом, он вызываетнайти следующий элемент, на который должен быть перенесен фокус, но это следующее представление не существует или не может быть сфокусировано, или родитель не может фактически обработать вызов findFocus () и возвращает ноль.

Итак, в вашем случаеесли ваш пользовательский класс расширяется от RadioGroup , который расширяет LinearLayout , то вам нужно будет сделать свой пользовательский вид как clickable и focusable до true такон может обрабатывать запрос фокуса.

0 голосов
/ 04 декабря 2018

Я не вижу следующих двух функций в конструкторе для вашего пользовательского представления или в xml, и для того, чтобы сделать представление фокусируемым, вам нужны эти. setFocusable(true); setClickable(true);

...