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 && 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 aRecyclerView
untuk menunjuk ke item berikutnya ketika berbicara tentangnextFocus
. - Apa sebenarnya maksud dari stacktrace di atas?
- Apa arti sebenarnya dari pesan di dalam
Stetho
inspektur?
Terima kasih telah membaca!