마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

NestedScrollView 안의 리스목록 선택했을때 선택이 바로 되지 않는경우가 있습니다.

0 추천
안녕하세요

아래 NestedScrollView 안에 RecyclerView 목록을 선택하면

이벤트 발생이 2번만에 발생 하는경우가 계속발생합니다.

아무리 구글링해서 비슷한것 적용해도 안되네요

혹시 이런경험 해결하신분 있으신가요

<androidx.coordinatorlayout.widget.CoordinatorLayout>
        <androidx.core.widget.NestedScrollView
                android:id="@+id/btn_sheet"
                android:layout_below="@+id/rv_list"
                android:layout_width="match_parent"
                android:layout_height="250dp"
                android:background="@android:color/holo_blue_light"
                app:behavior_hideable="true"
                app:behavior_peekHeight="55dp"
                app:layout_behavior="@string/bottom_sheet_behavior">
                
                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:gravity="fill_horizontal"
                    android:orientation="vertical">

                    <TextView
                        android:id="@+id/btn_slider"
                        android:layout_centerHorizontal="true"
                        android:layout_width="40dp"
                        android:layout_height="4dp"
                        android:gravity="center"
                        android:layout_marginTop="8dp"
                        android:background="@drawable/bg_notice"
                        android:clickable="false"
                        android:enabled="true"/>
                    <androidx.recyclerview.widget.RecyclerView
                        android:id="@+id/bottm_sheet_menus"
                        android:layout_below="@+id/btn_slider"
                        android:layout_marginTop="15dp"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_alignTop="@+id/btn_slider"
                        android:nestedScrollingEnabled="false"
                        android:visibility="visible" />
                </RelativeLayout>
                
        </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
일 지매 (300 포인트) 님이 2021년 5월 2일 질문
RecyclerView의 height = wrap_content 와 NestedSrcollView를 같이 쓰시면 RecyclerView.Adapter의 아이템 숫자가 많아지면 많아질 수록 퍼포먼스에가 많이 느려지는 건 알고 계시죠?
아 그런가요 좋은 정보 감사드립니다. 참조 하겠습니다.

그리고 위에 조건은 목록에 4개만 담는 로직입니다.
감사합니다.
4개라면 퍼포먼스는 문제가 될 게 없겠네요. 이벤트 발생 관련해서는 adapter와 viewholder쪽 로직을 체크하셔야 할 것 같습니다. XML로는 파악이 안돼요.
답변 감사드립니다. 구글링 하면
NestedScrollView버그 였다고들 하네요
그리고 안드로이드 엔지니어가 버그 수정을 했다고들 하는데
당쵀 저는 그 버그수정이 안먹히고 있습니다.
https://issuetracker.google.com/issues/37051723
(참고로 영어가 딸려서 구글번역기를 돌렸습니다. ㅋㅋ)


그래서 아래 사이트 참조해서 이것저것 해봤는데 전혀 안됩니다.
https://bateaux.tistory.com/m/4
(위 사이트 참조하다가 포기했습니다. 저랑 차이가 많이 있네요)
참으로 죽을맞입니다.
아래는 커스텀 NestedScrollView로 따로 만들어서 적용해보기도 했습니다.
결과는 똑같았습니다.

public class WorkaroundNestedScrollView extends NestedScrollView {

    public WorkaroundNestedScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            // Explicitly call computeScroll() to make the Scroller compute itself
            Log.d("TEST","onInterceptTouchEvent  ############");
            computeScroll();
        }
//        else if(ev.getAction() == MotionEvent.ACTION_UP)
//        {
//            Log.d("TEST","OACTION_UP  ############");
//        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        final RecyclerView rv = (RecyclerView) target;
        if ((dy < 0 && isRvScrolledToTop(rv)) || (dy > 0 && !isNsvScrolledToBottom(this))) {
            // Scroll the NestedScrollView's content and record the number of pixels consumed
            // (so that the RecyclerView will know not to perform the scroll as well).
            scrollBy(0, dy);
            consumed[1] = dy;
            return;
        }
        super.onNestedPreScroll(target, dx, dy, consumed);
    }

    @Override
    public boolean onNestedPreFling(View target, float velX, float velY) {
        final RecyclerView rv = (RecyclerView) target;
        if ((velY < 0 && isRvScrolledToTop(rv)) || (velY > 0 && !isNsvScrolledToBottom(this))) {
            // Fling the NestedScrollView's content and return true (so that the RecyclerView
            // will know not to perform the fling as well).
            fling((int) velY);
            return true;
        }
        return super.onNestedPreFling(target, velX, velY);
    }

    /**
     * Returns true iff the NestedScrollView is scrolled to the bottom of its
     * content (i.e. if the card's inner RecyclerView is completely visible).
     */
    private static boolean isNsvScrolledToBottom(NestedScrollView nsv) {
        return !nsv.canScrollVertically(1);
    }

    /**
     * Returns true iff the RecyclerView is scrolled to the top of its
     * content (i.e. if the RecyclerView's first item is completely visible).
     */
    private static boolean isRvScrolledToTop(RecyclerView rv) {
        final LinearLayoutManager lm = (LinearLayoutManager) rv.getLayoutManager();
        return lm.findFirstVisibleItemPosition() == 0
                && lm.findViewByPosition(0).getTop() == 0;
    }
}
왜 NestedScrollView의 height가 250dp인가요? 의도하신 건가요? match_parent와 fillViewPort =true가 일반적인 세팅일텐데...
아네 match_parent 주면 화면전체를 덮는경우라
저희는 기획상 화면전체를 덮는게 아니네요 ^^!

혹시나 해서 wrap_content 줬을때도 계속 버그는 발생하는군요
계속 관심주셔서 감사드립니다.
그게 좀 이해가 잘 안가서요. 화면 전체을 덮지 않으면 NestedScrolView가 없어도 될 것 같은데 말이죠. 저도 프로젝트에 NestedScrollView + RecyclerView를 많이 사용하긴 하는데 모두다 height =match_parent를 이고 님과 같은 문제는 본 적이 없는데, 좀 이상한 증상이네요.
아네
카톡기능중에 기관이나 특정인에  밑에노출시켜주는 메뉴같은 기능입니다.
(이런기능을 정확히 뭐라고 부르는지 모르겠네요)
여턴 안드로이드같은경우 ButtomSheet라고 하네요
그래서 NestedScrollView + RecyclerView 이기능이 꼭 필요한것 같습니다.

감사합니다.
https://material.io/components/sheets-bottom/android#using-standard-bottom-sheets
저도 해당 기능에 리스트를 보여주는 화면이 있지만, NestedScrollView는 굳이 필요하지 않을 것 같네요. 보여지는 아이템의 갯수가 몇개 안된다면 RecyclerView조차도 필요없을 것 같구요. 안드로이드에서 기본으로 지원하는 뷰를 사용하시는 원하시는 처리가 가능한지 한번 살펴보세요.
그리고 해당 이슈는 먼저 NestedSrcollView가 없이 RecyclerView만 단독으로 했을 때, 정상적으로 동작하는 지 확인해 보세요. 만약 같은 증상이라면, Adapter나 ViewHolder 쪽에 문제가 있다고 봐야겠죠. 만약 NestedScrollView를 추가했을 때 생기는 문제라면 layout의 구성 상의 문제이거나 님이 말씀하신대로 안드로이드 뷰의 버그일 수도 있겠구요.
buttomSheet 라는게 화면단 xml 작업에서 NestedScrollView 속성에
app:layout_behavior="@string/bottom_sheet_behavior" 아래 항목이 들어가줘야 하는걸로 알고 있습니다.

https://material.io/components/sheets-bottom/android#using-standard-bottom-sheets 이 주소는 저도 현재 기능구현을 위해 참조를 했었던 기억은 납니다.
그런데 개인적으로 저에게는 참조할정도의 가이드는 찾지를 못했습니다.!
아님 놓혔는지 모르겠네요

그리고 안되는 부분만 따로 분리해서 샘플을 만들어서 테스터를 해봤습니다.
결과는 똑같은 버거 발생이됩니다.

주말에도 쉬지못하고 스트레스 만당에 지치네요 ~~ ^^!
내일 출근해서 또 지칠것 생각하니 답답하기 까지 합니다.

그래도
님께서 끝까지 도와주시기 위해 힘쓰주시니 너무 감사하게 생각합니다.

1개의 답변

0 추천

프로젝트에  BottomSheet를 사용하고 있는데, 레이아웃이 님이 구성한 것과 다르 긴 합니다만, 그렇게 복잡한 세팅을 필요없습니다. 제가 사용하는 레이아웃입니다.
BottomSheetFragemt를 호출하는 프레그먼트 쪽의 레이아웃:
    

<com.cmcmarketsstockbroking.android.ui.widget.scrollview.LockableNestedScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true"
        android:scrollbars="none">

   <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/bg_fragment"
            android:paddingTop="@dimen/spacing_4x"
            android:paddingBottom="@dimen/spacing_4x">

      //다른 커스텀뷰들이 여기에 들어갑니다.

    </androidx.constraintlayout.widget.ConstraintLayout>

</com.cmcmarketsstockbroking.android.ui.widget.scrollview.LockableNestedScrollView>

LockableNestedScrollView은 NestScrollView와 같습니다. 내부적으로 변수값을 하나 사용해서 스크롤이 막아져 있는지 체크합니다. 

BottomSheetFragment layout:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/headerTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:fontFamily="sans-serif"
            android:padding="@dimen/spacing_4x"
            android:text="@string/exchange_selection_title"
            android:textColor="@color/black80"
            android:textSize="16sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <LinearLayout
            android:id="@+id/containerLnl"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="@dimen/spacing_2x"
            android:orientation="vertical"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/headerTxt" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

 

BottomSheetFragment:

class ExchangeListBottomSheet : BottomSheetDialogFragment() {

    companion object {
        const val TAG = "ExchangeModalBottomSheet"
    }

    private val exchangeItems = arrayListOf<StockRowItem>()

    override fun onAttach(context: Context) {
        super.onAttach(context)
        requireNotNull(targetFragment)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? =
        inflater.inflate(R.layout.modal_bottom_sheet, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setupArguments()
        setupViews()
    }

    private fun setupArguments() {
        val items =
            requireNotNull(requireArguments().getParcelableArrayList<StockRowItem>(EXTRA_BUNDLE_KEY))
        exchangeItems.clear()
        exchangeItems.addAll(items)
    }

    private val exchangeItemClickListener by lazy {
        SimpleItemClickListener<StockRowItem> { item ->
            setResult(item)
            dismiss()
        }
    }

    private fun setupViews() {
        containerLnl.removeAllViews()
        exchangeItems.forEach {
            containerLnl.addView(
                ExchangeModalViewItem(requireContext()).apply {
                    setData(it)
                    itemClickListener = exchangeItemClickListener
                }
            )
        }
    }

    private fun setResult(selectedItem: StockRowItem) {
        val intent = Intent().apply {
            putExtra(EXTRA_RESULT_KEY, selectedItem)
        }
        targetFragment?.onActivityResult(targetRequestCode, Activity.RESULT_OK, intent)
    }
}

 

spark (224,800 포인트) 님이 2021년 5월 3일 답변
답변 감사드립니다
올려주신 소스를 봐서는 잘 이해가 가질않는데
죄송한 부탁을 드려봅니다

제 개인메일로 샘플소스좀 주실수 있으신가여?
nakrlove@naver.com

부탁좀 드립니다
감사합니다
애고 해결했습니다
좀 허무합니다

NestedScrollView -> LinearLayout 이렇게만 바꾸니 되는군여
괜한 NestedScrollView에대한 고집이였네요

감사합니다
...