programing

RecyclerView에 스크롤 할 콘텐츠가 충분하지 않지만 AppBarLayout의 도구 모음을 스크롤 할 수 있습니다.

nasanasas 2020. 12. 2. 21:25
반응형

RecyclerView에 스크롤 할 콘텐츠가 충분하지 않지만 AppBarLayout의 도구 모음을 스크롤 할 수 있습니다.


"appbar_scrolling_view_behavior"가있는 기본 컨테이너에 실제로 스크롤 할 내용이 충분하지 않지만 AppBarLayout의 도구 모음이 스크롤 가능하도록 의도 된 것입니까?

지금까지 테스트 한 내용 :
NestedScrollView ( "wrap_content"속성 포함)를 기본 컨테이너로 사용하고 TextView를 자식으로 사용하면 AppBarLayout이 제대로 작동하고 스크롤되지 않습니다.

그러나 몇 개의 항목과 "wrap_content"속성 만있는 RecyclerView를 사용하면 (스크롤 할 필요가 없음) RecyclerView가 스크롤 이벤트를 수신하지 않더라도 AppBarLayout의 도구 모음을 스크롤 할 수 있습니다 (OnScrollChangeListener로 테스트 됨). ).

내 레이아웃 코드는 다음과 같습니다.

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/coordinatorLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:theme="@style/ToolbarStyle" />
    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>

다음과 같은 효과로 툴바는 필요하지 않지만 스크롤 할 수 있습니다.

또한 모든 RecyclerView 항목이 표시되는지 확인하고 RecyclerView의 setNestedScrollingEnabled () 메서드를 사용하여이 문제를 처리하는 방법을 찾았습니다.
그럼에도 불구하고 그것은 나에게 의도 한 버그처럼 보입니다. 의견이 있으십니까? :디

# 1 수정 :

내 현재 솔루션에 관심이있는 사람들을 위해 메서드를 호출 할 때 항상 -1을 반환하는 LayoutManager로 인해 5ms 지연이있는 핸들러의 postDelayed () 메서드에 setNestedScrollingEnabled () 논리를 넣어야했습니다. 첫 번째 항목과 마지막 항목이 표시되는지 여부.
이 코드는 onStart () 메서드 (내 RecyclerView가 초기화 된 후)와 RecyclerView의 콘텐츠 변경이 발생할 때마다 사용합니다.

final LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        //no items in the RecyclerView
        if (mRecyclerView.getAdapter().getItemCount() == 0)
            mRecyclerView.setNestedScrollingEnabled(false);
        //if the first and the last item is visible
        else if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0
                && layoutManager.findLastCompletelyVisibleItemPosition() == mRecyclerView.getAdapter().getItemCount() - 1)
            mRecyclerView.setNestedScrollingEnabled(false);
        else
            mRecyclerView.setNestedScrollingEnabled(true);
    }
}, 5);

# 2 수정 :

방금 새 앱을 가지고 놀았는데이 (의도하지 않은) 동작이 지원 라이브러리 버전 23.3.0 (또는 이전 버전)에서 수정 된 것 같습니다. 따라서 더 이상 해결 방법이 필요하지 않습니다!


편집 2 :

RecyclerView를 스크롤 할 수 없을 때 툴바가 스크롤되지 않도록하는 유일한 방법은 RecyclerView의 스크롤 가능 여부를 확인해야하는 프로그래밍 방식으로 setScrollFlags를 설정하는 것입니다. 이 검사는 어댑터가 수정 될 때마다 수행되어야합니다.

활동과 통신하기위한 인터페이스 :

public interface LayoutController {
    void enableScroll();
    void disableScroll();
}

주요 활동:

public class MainActivity extends AppCompatActivity implements 
    LayoutController {

    private CollapsingToolbarLayout collapsingToolbarLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        collapsingToolbarLayout = 
              (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);

        final FragmentManager manager = getSupportFragmentManager();
        final Fragment fragment = new CheeseListFragment();
        manager.beginTransaction()
                .replace(R.id.root_content, fragment)
                .commit();
    }

    @Override
    public void enableScroll() {
        final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
                                  collapsingToolbarLayout.getLayoutParams();
        params.setScrollFlags(
                AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL 
                | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
        );
        collapsingToolbarLayout.setLayoutParams(params);
    }

    @Override
    public void disableScroll() {
        final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
                                  collapsingToolbarLayout.getLayoutParams();
        params.setScrollFlags(0);
        collapsingToolbarLayout.setLayoutParams(params);
    }
}

activity_main.xml :

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:fitsSystemWindows="true">

    <android.support.design.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/main_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.design.widget.AppBarLayout
            android:id="@+id/appbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/collapsing_toolbar"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fitsSystemWindows="true"
                app:contentScrim="?attr/colorPrimary">

                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    android:background="?attr/colorPrimary"
                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

            </android.support.design.widget.CollapsingToolbarLayout>

        </android.support.design.widget.AppBarLayout>

        <FrameLayout
            android:id="@+id/root_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="fill_vertical"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

    </android.support.design.widget.CoordinatorLayout>

</android.support.v4.widget.DrawerLayout>

테스트 조각 :

public class CheeseListFragment extends Fragment {

    private static final int DOWN = 1;
    private static final int UP = 0;

    private LayoutController controller;
    private RecyclerView rv;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        try {
            controller = (MainActivity) getActivity();
        } catch (ClassCastException e) {
            throw new RuntimeException(getActivity().getLocalClassName()
                    + "must implement controller.", e);
        }
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        rv = (RecyclerView) inflater.inflate(
                R.layout.fragment_cheese_list, container, false);
        setupRecyclerView(rv);

        // Find out if RecyclerView are scrollable, delay required
        final Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (rv.canScrollVertically(DOWN) || rv.canScrollVertically(UP)) {
                    controller.enableScroll();
                } else {
                    controller.disableScroll();
                }
            }
        }, 100);

        return rv;
    }

    private void setupRecyclerView(RecyclerView recyclerView) {
        final LinearLayoutManager layoutManager = new LinearLayoutManager(recyclerView.getContext());

        recyclerView.setLayoutManager(layoutManager);

        final SimpleStringRecyclerViewAdapter adapter =
                new SimpleStringRecyclerViewAdapter(
                        getActivity(),
                        // Test ToolBar scroll
                        getRandomList(/* with enough items to scroll */)
                        // Test ToolBar pin
                        getRandomList(/* with only 3 items*/)
                );

        recyclerView.setAdapter(adapter);
    }
}

출처 :

편집하다:

동작을 제어하려면 CollapsingToolbarLayout이 필요합니다.

툴바를 AppBarLayout에 직접 추가하면 enterAlwaysCollapsed 및 exitUntilCollapsed 스크롤 플래그에 액세스 할 수 있지만 여러 요소가 축소에 반응하는 방법에 대한 자세한 제어는 할 수 없습니다. [...] 설정은 CollapsingToolbarLayout의 app : layout_collapseMode = "pin"을 사용하여보기가 축소되는 동안 툴바 자체가 화면 상단에 고정되도록합니다. http://android-developers.blogspot.com.tr/2015/05/android-design-support-library.html

<android.support.design.widget.CollapsingToolbarLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_scrollFlags="scroll|exitUntilCollapsed">

    <android.support.v7.widget.Toolbar
        android:id="@+id/drawer_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:layout_collapseMode="pin"/>

</android.support.design.widget.CollapsingToolbarLayout>

더하다

app:layout_collapseMode="pin"

xml의 ​​툴바에.

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:layout_scrollFlags="scroll|enterAlways"
        app:layout_collapseMode="pin"
        app:theme="@style/ToolbarStyle" />

따라서 적절한 신용,이 답변은 거의 https://stackoverflow.com/a/32923226/5050087을 해결했습니다 . 그러나 실제로 스크롤 가능한 recyclerview가 있고 마지막 항목이 표시되었을 때 도구 모음을 표시하지 않았기 때문에 (첫 번째 스크롤에서 도구 모음을 표시하지 않음) 더 쉬운 구현과 동적에 맞게 수정하고 적용하기로 결정했습니다. 어댑터.

먼저 앱 바에 대한 사용자 지정 레이아웃 동작을 만들어야합니다.

public class ToolbarBehavior extends AppBarLayout.Behavior{

private boolean scrollableRecyclerView = false;
private int count;

public ToolbarBehavior() {
}

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

@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
    return scrollableRecyclerView && super.onInterceptTouchEvent(parent, child, ev);
}

@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes, int type) {
    updatedScrollable(directTargetChild);
    return scrollableRecyclerView && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);
}

@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
    return scrollableRecyclerView && super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}

private void updatedScrollable(View directTargetChild) {
    if (directTargetChild instanceof RecyclerView) {
        RecyclerView recyclerView = (RecyclerView) directTargetChild;
        RecyclerView.Adapter adapter = recyclerView.getAdapter();
        if (adapter != null) {
            if (adapter.getItemCount()!= count) {
                scrollableRecyclerView = false;
                count = adapter.getItemCount();
                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
                if (layoutManager != null) {
                    int lastVisibleItem = 0;
                    if (layoutManager instanceof LinearLayoutManager) {
                        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
                        lastVisibleItem = Math.abs(linearLayoutManager.findLastCompletelyVisibleItemPosition());
                    } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                        StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
                        int[] lastItems = staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(new int[staggeredGridLayoutManager.getSpanCount()]);
                        lastVisibleItem = Math.abs(lastItems[lastItems.length - 1]);
                    }
                    scrollableRecyclerView = lastVisibleItem < count - 1;
                }
            }
        }
    } else scrollableRecyclerView = true;
  }
}

그런 다음 레이아웃 파일에서 앱 바에 대해이 동작을 정의하기 만하면됩니다.

<android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:fitsSystemWindows="true"
    app:layout_behavior="com.yourappname.whateverdir.ToolbarBehavior"
    >

화면 회전을 테스트하지 않았으므로 이렇게 작동하는지 알려주세요. 회전이 발생할 때 카운트 변수가 저장되지 않는다고 생각하므로 작동해야한다고 생각하지만 그렇지 않은 경우 알려주십시오.

이것은 나에게 가장 쉽고 깨끗한 구현이었습니다.


버그가 아닙니다. viewGroup의 모든 이벤트는 이런 방식으로 처리됩니다. recyclerview는 coordinatorLayout의 자식이므로 이벤트가 생성 될 때마다 먼저 부모가 있는지 확인하고 부모 만 관심이없는 경우 자식에게 전달됩니다. Google 문서 참조


LayoutManager하위 클래스 에서 이와 같은 것이 원하는 동작을 초래 하는 것 같습니다.

@Override
public boolean canScrollVertically() {
    int firstCompletelyVisibleItemPosition = findFirstCompletelyVisibleItemPosition();
    if (firstCompletelyVisibleItemPosition == RecyclerView.NO_POSITION) return false;

    int lastCompletelyVisibleItemPosition = findLastCompletelyVisibleItemPosition();
    if (lastCompletelyVisibleItemPosition == RecyclerView.NO_POSITION) return false;

    if (firstCompletelyVisibleItemPosition == 0 &&
            lastCompletelyVisibleItemPosition == getItemCount() - 1)
        return false;

    return super.canScrollVertically();
}

에 대한 문서는 canScrollVertically()말한다 :

/**
 * Query if vertical scrolling is currently supported. The default implementation
 * returns false.
 *
 * @return True if this LayoutManager can scroll the current contents vertically
 */

" 현재 내용을 세로로 스크롤 할 수 있음"이라는 문구에 주목하십시오 . 이는 현재 상태가 반환 값에 반영되어야 함을 의미합니다.

그러나 LayoutManager이것은 v7 recyclerview 라이브러리 (23.1.1)를 통해 제공 되는 하위 클래스에 의해 수행되지 않습니다 . 따라서 올바른 솔루션인지 여부가 다소 주저하게됩니다. 이 질문에서 논의 된 것 이외의 다른 상황에서 원하지 않는 결과를 초래할 수 있습니다.


AppBarLayout에 연결될 수있는 내 자신의 Behavior 클래스를 사용하여 구현했습니다.

public class CustomAppBarLayoutBehavior extends AppBarLayout.Behavior {

private RecyclerView recyclerView;
private int additionalHeight;

public CustomAppBarLayoutBehavior(RecyclerView recyclerView, int additionalHeight) {
    this.recyclerView = recyclerView;
    this.additionalHeight = additionalHeight;
}

public boolean isRecyclerViewScrollable(RecyclerView recyclerView) {
    return recyclerView.computeHorizontalScrollRange() > recyclerView.getWidth() || recyclerView.computeVerticalScrollRange() > (recyclerView.getHeight() - additionalHeight);
}

@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
    if (isRecyclerViewScrollable(mRecyclerView)) {
        return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
    }
    return false;
}

}

이 동작을 설정하는 방법은 다음과 같습니다.

final View appBarLayout = ((DrawerActivity) getActivity()).getAppBarLayoutView();
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
layoutParams.setBehavior(new AppBarLayoutNoEmptyScrollBehavior(recyclerView, getResources().getDimensionPixelSize(R.dimen.control_bar_height)));

라이브러리 요소를 지원하기 위해이 샘플을 사용해 보도록 제안했습니다 .

이것은 샘플의 레이아웃과 같은 레이아웃입니다.

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
            app:layout_scrollFlags="scroll|enterAlways" />

        <android.support.design.widget.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </android.support.design.widget.AppBarLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</android.support.design.widget.CoordinatorLayout>

감사합니다. RecyclerView의 사용자 정의 클래스를 만들었지 만 키는 여전히 setNestedScrollingEnabled(). 내 편에서 잘 작동했습니다.

public class RecyclerViewCustom extends RecyclerView implements ViewTreeObserver.OnGlobalLayoutListener
{
    public RecyclerViewCustom(Context context)
    {
        super(context);
    }

    public RecyclerViewCustom(Context context, @Nullable AttributeSet attrs)
    {
        super(context, attrs);
    }

    public RecyclerViewCustom(Context context, @Nullable AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }

    /**
     *  This supports scrolling when using RecyclerView with AppbarLayout
     *  Basically RecyclerView should not be scrollable when there's no data or the last item is visible
     *
     *  Call this method after Adapter#updateData() get called
     */
    public void addOnGlobalLayoutListener()
    {
        this.getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    @Override
    public void onGlobalLayout()
    {
        // If the last item is visible or there's no data, the RecyclerView should not be scrollable
        RecyclerView.LayoutManager layoutManager = getLayoutManager();
        final RecyclerView.Adapter adapter = getAdapter();
        if (adapter == null || adapter.getItemCount() <= 0 || layoutManager == null)
        {
            setNestedScrollingEnabled(false);
        }
        else
        {
            int lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
            boolean isLastItemVisible = lastVisibleItemPosition == adapter.getItemCount() - 1;
            setNestedScrollingEnabled(!isLastItemVisible);
        }

        unregisterGlobalLayoutListener();
    }

    private void unregisterGlobalLayoutListener()
    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
        {
            getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
        else
        {
            getViewTreeObserver().removeGlobalOnLayoutListener(this);
        }
    }
}

I would like to add a little to user3623735's answer. The following code is absolutely incorrect.

// Find out if RecyclerView are scrollable, delay required
    final Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            if (rv.canScrollVertically(DOWN) || rv.canScrollVertically(UP)) {
                controller.enableScroll();
            } else {
                controller.disableScroll();
            }
        }
    }, 100);

And even when it works - it doesn't cover all cases. There is absolutely no guarantee that a data will be displayed in 100 ms, and the data can stretch the height of the view in the process of working with it, not only in the onCreateView method. That's why you should use next code and track changes in view height:

view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
        @Override
        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
            if(bottom != oldBottom)
            {
                mActivity.setScrollEnabled(view.canScrollVertically(0) || view.canScrollVertically(1));
            }
        }
    });

Moreover no need to create two separated method to control scrolling status, you should use one setScrollEnabled method:

public void setScrollEnabled(boolean enabled) {
    final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
            mToolbar.getLayoutParams();

    params.setScrollFlags(enabled ?
            AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS : 0);

    mToolbar.setLayoutParams(params);
}

In your Toolbar remove the scroll flag, leaving only the enterAlways flag and you should get the effect you intended. For completeness, your layout should look like:

<android.support.design.widget.CoordinatorLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/coordinatorLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="enterAlways"
            app:theme="@style/ToolbarStyle" />
    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>

참고URL : https://stackoverflow.com/questions/32398500/toolbar-in-appbarlayout-is-scrollable-although-recyclerview-has-not-enough-conte

반응형