반응형

안녕하세요. Simple& Happy Dev입니다.

 

커스텀 리스트뷰(Custom ListView)를 가지고 "접근성 검사기를 이용한 접근성 개선 예제"를 만드는 과정에서 리스트뷰의 아이템를 클릭했지만 동작하지 않는 이슈가 있었습니다.

 

2018/12/20 - 접근성 검사기(Accessibility Scanner)를 이용한 접근성 개선 예제

 

아래와 같이 ImageView + TextView + Switch 버튼으로 이루어진 아이템을 가진 커스텀 리스트뷰입니다.

 

아이템 클릭 시 리스너 코드는 아래와 같습니다.

ListView colorListView = findViewById(R.id.lvColor);
colorListView.setAdapter(mColorListAdapter);
colorListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ColorItem selectedItem = (ColorItem) mColorListAdapter.getItem(position);
AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(MainActivity.this, R.style.AlertDialogCustom));
AlertDialog alert = builder.setMessage(selectedItem.getColorName() + getResources().getString(R.string.dialog_msg) + (selectedItem.getColorUse() ? getResources().getString(R.string.yes) : getResources().getString(R.string.no)))
.setPositiveButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
}).create();
alert.show();
}
});

그런데, 아이템 클릭 시 동작을 하지 않아서 디버깅해보니 onItemClick()이 호출되지 않았습니다.

 

몇 가지 테스트를 해보니, 일단 아이템을 TextView 하나로만 구성한 커스텀 리스트뷰에서는 정상 동작합니다.

그 경우에 onItemClick이 호출될 때의 콜스택은 아래와 같이 나옵니다.

 

"main"@10,638 in group "main": RUNNING
  • onItemClick:40, MainActivity$1 {kr.happydev.accessibilitytest}
  • performItemClick:318, AdapterView {android.widget}
  • performItemClick:1159, AbsListView {android.widget}
  • run:3136, AbsListView$PerformClick {android.widget}
  • onTouchUp:4064, AbsListView {android.widget}
  • onTouchEvent:3822, AbsListView {android.widget}
  • dispatchTouchEvent:12513, View {android.view}
  •  

    ImageView + TextView 2개로 구성한 커스텀 리스트뷰에서도 정상 동작하였습니다.

     

    Switch 버튼이 문제인 것 같은데, 프레임워크 레벨에서 왜 호출되지 않는지 확인이 필요했습니다.

     

    다시 ImageView + TextView + Switch 버튼으로 이루어진 아이템으로 소스를 돌려서 확인한 결과 일단 onTouchUp 까지는 정상적으로 호출됩니다.

     

    그런데, onTouchUp 안에서 performClick.run() 이 호출되지 않았습니다.

     

    /android/widget/AbsListView.java

    private void onTouchUp(MotionEvent ev) {
    switch (mTouchMode) {
    case TOUCH_MODE_DOWN:
    case TOUCH_MODE_TAP:
    case TOUCH_MODE_DONE_WAITING:
    final int motionPosition = mMotionPosition;
    final View child = getChildAt(motionPosition - mFirstPosition);
    if (child != null) {
    if (mTouchMode != TOUCH_MODE_DOWN) {
    child.setPressed(false);
    }

    final float x = ev.getX();
    final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
    if (inList && !child.hasExplicitFocusable()) {
    if (mPerformClick == null) {
    mPerformClick = new PerformClick();
    }

    final AbsListView.PerformClick performClick = mPerformClick;
    performClick.mClickMotionPosition = motionPosition;
    performClick.rememberWindowAttachCount();

    mResurrectToPosition = motionPosition;

    if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
    removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
    mPendingCheckForTap : mPendingCheckForLongPress);
    mLayoutMode = LAYOUT_NORMAL;
    if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
    mTouchMode = TOUCH_MODE_TAP;
    setSelectedPositionInt(mMotionPosition);
    layoutChildren();
    child.setPressed(true);
    positionSelector(mMotionPosition, child);
    setPressed(true);
    if (mSelector != null) {
    Drawable d = mSelector.getCurrent();
    if (d != null && d instanceof TransitionDrawable) {
    ((TransitionDrawable) d).resetTransition();
    }
    mSelector.setHotspot(x, ev.getY());
    }
    if (mTouchModeReset != null) {
    removeCallbacks(mTouchModeReset);
    }
    mTouchModeReset = new Runnable() {
    @Override
    public void run() {
    mTouchModeReset = null;
    mTouchMode = TOUCH_MODE_REST;
    child.setPressed(false);
    setPressed(false);
    if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
    performClick.run();
    }
    }
    };
    postDelayed(mTouchModeReset,
    ViewConfiguration.getPressedStateDuration());
    } else {
    mTouchMode = TOUCH_MODE_REST;
    updateSelectorState();
    }
    return;
    } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
    performClick.run();
    }
    }
    }
    mTouchMode = TOUCH_MODE_REST;
    updateSelectorState();
    break;

     

    조금 더 디버깅해보니 아래 조건이 맞지 않아서 그런 것이었습니다.

    if (inList && !child.hasExplicitFocusable()) {

    아래 보면 inList는 true이지만, !child.hasExplicitFocusable()이 false라서 동작하지 않게 된 것입니다.

    즉, Switch 버튼으로 인하여 child.hasExpliciFocusable()이 true로 리턴이 되고 있어서 발생한 문제입니다.

     

    이는 커스텀 리스트뷰에서 아이템을 클릭하더라도 이미 switch 버튼이 포커스를 가지고 있기 때문에 동작하지 않았다고 이해하면 됩니다.

     

     

    해결 방법은 switch 버튼을 포커스를 가지고 있지 않도록 처리해주면 됩니다.

     

    java에서 처리할 경우 아래 2라인을 추가하면 해결됩니다. setFocusable(false)만 해줘도 onItemClick()이 호출되지만, setFocusableInTouchMode(false)를 해주지 않으면 switch 상태 변경 후 switch 값을 얻어올 때 변경된 값을 읽지 못합니다.

    public ColorItemView(Context context) {
    super(context);
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    inflater.inflate(R.layout.color_item, this, true);

    mColorImage = findViewById(R.id.ivColor);
    mColorName = findViewById(R.id.tvName);
    mColorUse = findViewById(R.id.swUse);

    mColorUse.setFocusable(false);

    mColorUse.setFocusableInTouchMode(false);

    }

    xml에서 처리할 경우에는 android:focusableandroid:focusableInTouchMode를 false로 만들어 줍니다.

    <Switch
    android:id="@+id/swUse"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    android:layout_marginEnd="16dp"
    android:focusable="false"
    android:focusableInTouchMode="false"
    app:layout_constraintBottom_toBottomOf="@+id/ivColor"
    app:layout_constraintEnd_toEndOf="parent"

    app:layout_constraintTop_toTopOf="@+id/ivColor" />

    여기까지입니다. 읽어주셔서 감사합니다.

    + Recent posts