Selanjutnya fokus pada tampilan kustom di dalam RecyclerView

Saya memiliki RecyclerView yang mewakili formulir. RecyclerView ini terbuat dari beberapa jenis View. Masing-masing memiliki base_layout dan View spesifik bergantung pada tipe data yang diwakilinya (misalnya: string akan diwakili oleh EditText).

Berikut adalah file tata letak yang mewakili tipe data 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>

Ketika suatu formulir hanya terbuat dari string, tidak ada masalah. Namun ada juga beberapa tampilan khusus. Misalnya:

<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>

Bila bidang jenis khusus seperti di atas diawali dengan jenis string, aplikasi akan mogok saat kita mengetuk tombol berikutnya pada keyboard sambil memfokuskan bidang jenis string.

Pelacakan tumpukan kerusakan:

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)

Akhirnya inilah kelas 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

}

Seperti yang bisa kita lihat, saya mencoba bermain dengan metode setDescendantFocusability tetapi tidak berhasil (mungkin saya melakukan kesalahan)?

Saya juga mencoba memaksakan pengaturan fokus berikutnya pada item RecyclerView berikutnya dengan menggunakan cuplikan kode ini:

@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());

}

Informasi lainnya adalah saya menggunakan Stetho untuk menganalisis tata letak saya dan saya menemukan di bawah Accessibility Properties dari DynamicRadioGroup pesan ini:

pencarian fokus android mengembalikan tampilan yang tidak dapat mengambil fokus!

Tidak yakin apa yang harus dilakukan tetapi ini mungkin membantu.


TL;DR

Meskipun saya tidak mendapatkan jawaban lengkap di sini, saya ingin lebih memahami cara kerjanya dengan mendapatkan jawaban atas berbagai pertanyaan:

  • Cara membuat Tampilan khusus/Grup Tampilan khusus focusable dan bereaksi terhadap perolehan/kehilangan fokus.
  • Cara memaksa View di dalam a RecyclerView untuk menunjuk ke item berikutnya ketika berbicara tentang nextFocus.
  • Apa sebenarnya maksud dari stacktrace di atas?
  • Apa arti sebenarnya dari pesan di dalam Stetho inspektur?

Terima kasih telah membaca!


person MHogge    schedule 28.11.2018    source sumber
comment
Jawaban ini dapat membantu stackoverflow.com/a/49433332/8547916   -  person snersesyan    schedule 06.12.2018


Jawaban (2)


Saya tidak melihat dua fungsi berikut dalam konstruktor untuk tampilan khusus Anda atau di xml dan untuk membuat tampilan dapat difokuskan, Anda memerlukan ini. setFocusable(true); setClickable(true);

person Navi    schedule 04.12.2018

Kesalahan ini terjadi ketika nilai opsi ime EditorInfo.IME_ACTION_NEXT dan objek berikutnya yang ditemukan tidak dapat difokus atau induknya tidak dapat difokus.

Seperti yang disebutkan dalam dokumentasi IME_ACTION_NEXT di sini

Bit IME_MASK_ACTION: tombol tindakan melakukan operasi "berikutnya", membawa pengguna ke bidang berikutnya yang akan menerima teks.

Oleh karena itu, ia memanggil untuk menemukan item berikutnya yang fokusnya harus ditransfer, tetapi tampilan berikutnya ini tidak ada atau tidak dapat difokus atau induknya tidak dapat benar-benar menangani panggilan findFocus() dan mengembalikan nol.

Jadi, dalam kasus Anda jika kelas khusus Anda diperluas dari RadioGroup yang meluas LinearLayout, maka Anda perlu membuat tampilan khusus Anda sebagai clickable dan focusable menjadi true< /strong> sehingga dapat menangani permintaan fokus.

person karan    schedule 06.12.2018