Development Tip

페이지 매김을위한 ProgressBar가있는 Endless RecyclerView

yourdevel 2020. 12. 31. 23:01
반응형

페이지 매김을위한 ProgressBar가있는 Endless RecyclerView


나는 RecyclerViewAPI에서 객체를 10 개 단위로 사용하고있다. 페이지 매김에는 EndlessRecyclerOnScrollListener.

모두 제대로 작동하고 있습니다. 이제 남은 것은 목록 맨 아래에 진행률 스피너를 추가하는 것입니다. 그러면 다음 개체 배치가 API에서 가져옵니다. 여기에 구글 플레이 스토어 앱의 스크린 샷은을 보여주는 것입니다 ProgressBar확실히 무엇에 RecyclerView:

여기에 이미지 설명 입력

문제는 다음 배치의 객체를 가져 오는 동안 맨 아래에 를 표시하기위한 지원 기능이 내장 되어 RecyclerView있지 않다는 것 입니다.EndlessRecyclerOnScrollListenerProgressBar

나는 이미 다음과 같은 답변을 보았습니다.

1. 미확정 ProgressBarRecyclerView그리드에 바닥 글로 넣습니다 .

2. 무한 스크롤에 항목을 추가 RecyclerViewProgressBar하단에 .

나는 그 대답에 만족하지 않습니다 (둘 다 같은 사람에 의해). 여기에는 null사용자가 스크롤하는 동안 데이터 세트에 객체를 넣은 다음 다음 배치가 전달 된 후 꺼내는 작업이 포함됩니다. 제대로 작동하거나 작동하지 않을 수있는 주요 문제를 회피하는 해킹처럼 보입니다. 그리고 그것은 목록에 약간의 흔들림과 왜곡을 유발합니다.

사용 SwipeRefreshLayout은 여기서 해결책이 아닙니다. 최신 항목 SwipeRefreshLayout을 가져 오기 위해 맨 위에서 당기는 작업이 포함되며 어쨌든 진행률보기를 표시하지 않습니다.

누군가가 이것에 대한 좋은 해결책을 제공 할 수 있습니까? Google이 자체 앱에이를 구현 한 방법을 알고 싶습니다 (Gmail 앱에도 적용됨). 자세한 내용이 표시된 기사가 있습니까? 모든 답변과 의견을 주시면 감사하겠습니다. 감사합니다.

다른 참조 :

1. 와 매김RecyclerView . (훌륭한 개요 ...)

2. RecyclerView머리글 및 바닥 글 . (더 많이 ...)

3. 바닥에 RecyclerViewProgressBar없습니다 .


여기에 더 간단하고 깔끔한 접근 방식이 있습니다.

이 코드 경로 가이드 에서 무한 스크롤링을 구현 한 후 다음 단계를 따르십시오.

1. RecyclerView 아래에 진행률 표시 줄을 추가합니다.

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_movie_grid"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:paddingBottom="50dp"
        android:clipToPadding="false"
        android:background="@android:color/black"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </android.support.v7.widget.RecyclerView>

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="invisible"
        android:background="@android:color/transparent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

여기서 android : paddingBottom = "50dp"android : clipToPadding = "false" 는 매우 중요합니다.

2. 진행률 표시 줄에 대한 참조를 가져옵니다.

progressBar = findViewById(R.id.progressBar);

3. 진행률 표시 줄을 표시하고 숨기는 방법을 정의합니다.

void showProgressView() {
    progressBar.setVisibility(View.VISIBLE);
}

void hideProgressView() {
    progressBar.setVisibility(View.INVISIBLE);
}

이전 프로젝트에서 이것을 구현했으며 다음과 같이 수행했습니다.

나는 interface당신의 예의 사람들이 것처럼 만들었습니다.

public interface LoadMoreItems {
  void LoadItems();
}

그런 다음 addOnScrollListener()Adapter

 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView,
                                   int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                totalItemCount = linearLayoutManager.getItemCount();
                lastVisibleItem = linearLayoutManager
                        .findLastVisibleItemPosition();
                if (!loading
                        && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                    //End of the items
                    if (onLoadMoreListener != null) {
                        onLoadMoreListener.LoadItems();
                    }
                    loading = true;

                }
            }
        });

onCreateViewHolder()내가 어디다입니다 ProgressBar여부.

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                  int viewType) {
    RecyclerView.ViewHolder vh;
    if (viewType == VIEW_ITEM) {
        View v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.list_row, parent, false);

        vh = new StudentViewHolder(v);
    } else {
        View v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.progressbar_item, parent, false);

        vh = new ProgressViewHolder(v);
    }
    return vh;
}

에서 다른 항목을 추가하기 위해 MainActivity넣은 곳 LoadItems()은 다음과 같습니다.

mAdapter.setOnLoadMoreListener(new LoadMoreItems() {
        @Override
        public void LoadItems() {
            DataItemsList.add(null);
            mAdapter.notifyItemInserted(DataItemsList.size() - 1);

            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    //   remove progress item
                    DataItemsList.remove(DataItemsList.size() - 1);
                    mAdapter.notifyItemRemoved(DataItemsList.size());
                    //add items one by one
                    //When you've added the items call the setLoaded()
                    mAdapter.setLoaded();
                    //if you put all of the items at once call
                    // mAdapter.notifyDataSetChanged();
                }
            }, 2000); //time 2 seconds

        }
    });

자세한 내용은 그냥이 다음 Github repository( 참고 :이 사용하고 AsyncTask내가 그것을했다 수동으로하지의 데이터로부터, 내 답변으로 유용 어쩌면 API하지만 잘으로 작동합니다 ) 또한이 게시물이 나에게 도움이되었다 무한 recyclerview-와 진행을 -바

또한 이름을 지 었는지 모르겠지만이 게시물 infinite_scrolling_recyclerview를 찾았습니다 . 어쩌면 도움이 될 수도 있습니다.

찾고있는 것이 아닌 경우이 코드의 문제점을 알려 주시면 원하는대로 수정하겠습니다.

도움이 되었기를 바랍니다.

편집하다

당신이 항목을 제거하고 싶지 않기 때문에 ... 나는 footer이 게시물 에서 유일한 사람을 제거하는 한 사람을 발견 했습니다 : diseño-android-endless-recyclerview .

이것은입니다 ListView하지만 난에 당신이 그것을 적용 할 수 있습니다 알고 RecyclerView그가 단지 태우고 모든 항목 삭제 아니에요 Visible/Invisible(가) ProgressBar를 살펴를 :detecting-end-of-listview

또한보십시오 :이 질문 android-implementing-progressbar-and-loading-for-endless-list-like-android


이를 수행하는 다른 방법이 있습니다.

  • 먼저 어댑터의 getItemCount가 반환합니다. listItems.size() + 1
  • VIEW_TYPE_LOADINGgetItemViewType()대한 반환 position >= listItems.size(). 이렇게하면 로더가 리사이클 러보기 목록의 끝에 만 표시됩니다. 이 솔루션의 유일한 문제는 마지막 페이지에 도달 한 후에도 로더가 표시되므로 x-pagination-total-count어댑터에 를 저장 하고
  • 그런 다음 뷰 유형을 반환하도록 조건을 변경합니다.

    (position >= listItem.size())&&(listItem.size <= xPaginationTotalCount) .

방금이 아이디어를 내놓았습니다. 어떻게 생각하십니까?


진행률보기 홀더를 어댑터에 추가하는 아이디어가 마음에 들지만 원하는 것을 얻기 위해 추악한 논리 조작으로 이어지는 경향이 있습니다. 당신의 반환 값으로 초조해로 추가 바닥 글 항목을 방지하기 위해 뷰 홀더 접근 힘은 getItemCount(), getItemViewType(), getItemId(position)및 모든 종류의 getItem(position)당신이 할 수 있다는 방법은 다음을 포함합니다.

다른 방법은 관리하는 ProgressBar상기 시야 Fragment또는 Activity표시 또는 숨기기 의해 레벨 ProgressBar아래 RecyclerView로드 할 때 시작 및 종료를 각각. 이는 ProgressBar뷰 레이아웃에 직접 포함 하거나 사용자 정의 RecyclerView ViewGroup클래스 에 추가하여 수행 할 수 있습니다. 이 솔루션은 일반적으로 유지 관리 및 버그 감소로 이어집니다.

업데이트 : 콘텐츠가로드되는 동안보기를 위로 스크롤 할 때 내 제안에 문제가 발생합니다. ProgressBar뷰 레이아웃의 하단에 충실 할 것이다. 이것은 아마도 당신이 원하는 행동이 아닐 것입니다. 이러한 이유로 어댑터에 진행률보기 홀더를 추가하는 것이 아마도 최상의 기능적 솔루션 일 것입니다. 항목 접근 자 메서드를 보호하는 것을 잊지 마십시오. :)


다음은 가짜 항목을 추가하지 않고 해결 방법입니다 (Kotlin에서는 간단하지만).

어댑터에 다음을 추가하십시오.

private var isLoading = false
private val VIEWTYPE_FORECAST = 1
private val VIEWTYPE_PROGRESS = 2

override fun getItemCount(): Int {
    if (isLoading)
        return items.size + 1
    else
        return items.size
}

override fun getItemViewType(position: Int): Int {
    if (position == items.size - 1 && isLoading)
        return VIEWTYPE_PROGRESS
    else
        return VIEWTYPE_FORECAST
}

override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
    if (viewType == VIEWTYPE_FORECAST)
        return ForecastHolder(LayoutInflater.from(context).inflate(R.layout.item_forecast, parent, false))
    else
        return ProgressHolder(LayoutInflater.from(context).inflate(R.layout.item_progress, parent, false))
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) {
    if (holder is ForecastHolder) {
        //init your item
    }
}

public fun showProgress() {
    isLoading = true
}


public fun hideProgress() {
    isLoading = false
}

이제 showProgress()API 호출 전에 쉽게 호출 할 수 있습니다 . 그리고 hideProgress()API 호출이 완료된 후.


이 솔루션은이 페이지의 Akshar Patels 솔루션에서 영감을 받았습니다. 나는 그것을 약간 수정했다.

  1. 첫 번째 항목을로드 할 때 ProgressBar를 중앙에 배치하는 것이 좋습니다.

  2. 더 이상로드 할 항목이 없을 때 하단에 남아있는 빈 패딩이 마음에 들지 않았습니다. 이 솔루션에서는 제거되었습니다.

먼저 XML :

<android.support.v7.widget.RecyclerView
    android:id="@+id/video_list"
    android:paddingBottom="60dp"
    android:clipToPadding="false"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent">

</android.support.v7.widget.RecyclerView>

<ProgressBar
    android:id="@+id/progressBar2"
    style="?android:attr/progressBarStyle"
    android:layout_marginTop="10dp"
    android:layout_width="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>

그런 다음 프로그래밍 방식으로 다음을 추가했습니다.

첫 번째 결과가로드되면이를 onScrollListener에 추가하십시오. ProgressBar를 중앙에서 아래쪽으로 이동합니다.

ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) loadingVideos.getLayoutParams();
layoutParams.topToTop = ConstraintLayout.LayoutParams.UNSET;
loadingVideos.setLayoutParams(layoutParams);

더 이상 항목이 없으면 다음과 같이 하단의 패딩을 제거합니다.

recyclerView.setPadding(0,0,0,0);

평소처럼 ProgressBar를 숨기고 표시합니다.


recycleView에서 다음과 같이 layout_above 태그를 사용할 수 있습니다.

<RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_marginLeft="16dp"
                    android:layout_marginRight="16dp"
                    android:orientation="vertical">
                    <androidx.recyclerview.widget.RecyclerView
                        android:id="@+id/rv"
                        android:layout_below="@+id/tv2"
                        android:layout_width="match_parent"
                        android:focusable="false"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="24dp"
                        android:layout_above="@+id/pb_pagination"/>

                     <ProgressBar
                        android:id="@+id/pb_pagination"
                        style="@style/Widget.AppCompat.ProgressBar"
                        android:layout_width="30dp"
                        android:layout_height="30dp"
                        android:indeterminate="true"
                         android:visibility="gone"
                        android:layout_alignParentBottom="true"
                        android:layout_centerHorizontal="true" />
    </RelativeLayout>

다른 접근 방식은 onBindViewHolder 내에서 API 호출을 시작하고 일부 진행률 표시기를 항목보기에 초기에 배치하는 것입니다. 통화가 완료된 후보기를 업데이트합니다 (진행률 숨기기 및 수신 된 데이터 표시). 예를 들어 이미지 로딩을위한 Picasso의 경우 onBindViewHolder 메서드는 다음과 같습니다.

@Override
public void onBindViewHolder(final MovieViewHolder holder, final int position) {
    final Movie movie = items.get(position);
    holder.imageProgress.setVisibility(View.VISIBLE);
    Picasso.with(context)
            .load(NetworkingUtils.getMovieImageUrl(movie.getPosterPath()))
            .into(holder.movieThumbImage, new Callback() {
                @Override
                public void onSuccess() {
                    holder.imageProgress.setVisibility(View.GONE);
                }
                @Override
                public void onError() {

                }
            });
}

내가보기에 나타날 수있는 두 가지 경우가 있습니다.

  1. 한 번의 호출로 모든 항목을 라이트 버전으로 다운로드하는 경우 (예 : 어댑터는 40 장의 사진을 처리해야한다는 것을 즉시 알고 있지만 요청시 다운로드 —> 이전에 피카소에서 보여준 사례)
  2. 실제 지연 로딩으로 작업하고 서버에 추가 데이터 청크를 요청하는 곳입니다. 이 경우 첫 번째 전제 조건은 필요한 정보와 함께 서버로부터 적절한 응답을받는 것입니다. 예 : { "offset": 0, "total": 100, "items": [{items}]}

응답은 총 100 개의 데이터 중 첫 번째 청크를 수신했음을 의미합니다. 내 접근 방식은 다음과 같습니다.

보기 첫 번째 데이터 청크 (예 : 10)를 가져온 후 어댑터에 추가합니다.

RecyclerView.Adapter.getItemCount 현재 사용 가능한 항목의 양이 총 수량 (예 : 사용 가능한 10, 총 100)보다 적 으면 getItemCount 메서드에서 items.size () + 1을 반환합니다.

RecyclerView.Adapter.getItemViewType 데이터의 총량이 어댑터에서 사용 가능한 항목의 양보다 많고 position = items.size () (즉, getItemCount 메서드에 항목을 가상으로 추가 한 경우), 뷰 유형으로 일부 진행 표시기를 반환합니다. . 그렇지 않으면 일반 레이아웃 유형을 반환합니다.

RecyclerView.Adapter.onCreateViewHolder 진행률 표시기보기 유형을 사용하라는 메시지가 표시되면 발표자에게 추가 항목 청크를 가져오고 어댑터를 업데이트하도록 요청하기 만하면됩니다.

따라서 기본적으로 이것은 목록에서 항목을 추가 / 제거 할 필요가없고 지연로드가 트리거되는 상황을 제어 할 수있는 접근 방식입니다.

다음은 코드 예입니다.

public class ForecastListAdapter extends RecyclerView.Adapter<ForecastListAdapter.ForecastVH> {
private final Context context;
private List<Forecast> items;
private ILazyLoading lazyLoadingListener;

public static final int VIEW_TYPE_FIRST         = 0;
public static final int VIEW_TYPE_REST          = 1;
public static final int VIEW_TYPE_PROGRESS      = 2;

public static final int totalItemsCount = 14;

public ForecastListAdapter(List<Forecast> items, Context context, ILazyLoading lazyLoadingListener) {
    this.items = items;
    this.context = context;
    this.lazyLoadingListener = lazyLoadingListener;
}

public void addItems(List<Forecast> additionalItems){
    this.items.addAll(additionalItems);
    notifyDataSetChanged();
}

@Override
public int getItemViewType(int position) {
    if(totalItemsCount > items.size() && position == items.size()){
        return VIEW_TYPE_PROGRESS;
    }
    switch (position){
        case VIEW_TYPE_FIRST:
            return VIEW_TYPE_FIRST;
        default:
            return VIEW_TYPE_REST;
    }
}

@Override
public ForecastVH onCreateViewHolder(ViewGroup parent, int viewType) {
    View v;
    switch (viewType){
        case VIEW_TYPE_PROGRESS:
            v = LayoutInflater.from(parent.getContext()).inflate(R.layout.forecast_list_item_progress, parent, false);
            if (lazyLoadingListener != null) {
                lazyLoadingListener.getAdditionalItems();
            }
            break;
        case VIEW_TYPE_FIRST:
            v = LayoutInflater.from(parent.getContext()).inflate(R.layout.forecast_list_item_first, parent, false);
            break;
        default:
            v = LayoutInflater.from(parent.getContext()).inflate(R.layout.forecast_list_item_rest, parent, false);
            break;
    }
    return new ForecastVH(v);
}

@Override
public void onBindViewHolder(ForecastVH holder, int position) {
    if(position < items.size()){
        Forecast item = items.get(position);
        holder.date.setText(FormattingUtils.formatTimeStamp(item.getDt()));
        holder.minTemperature.setText(FormattingUtils.getRoundedTemperature(item.getTemp().getMin()));
        holder.maxTemperature.setText(FormattingUtils.getRoundedTemperature(item.getTemp().getMax()));
    }

}

@Override
public long getItemId(int position) {
    long i = super.getItemId(position);
    return i;
}

@Override
public int getItemCount() {
    if (items == null) {
        return 0;
    }
    if(items.size() < totalItemsCount){
        return items.size() + 1;
    }else{
        return items.size();
    }
}

public class ForecastVH extends RecyclerView.ViewHolder{
    @BindView(R.id.forecast_date)TextView date;
    @BindView(R.id.min_temperature)TextView minTemperature;
    @BindView(R.id.max_temperature) TextView maxTemperature;
    public ForecastVH(View itemView) {
        super(itemView);
        ButterKnife.bind(this, itemView);
    }
}

public interface ILazyLoading{
    public void getAdditionalItems();
}}

아마도 이것은 당신이 당신의 필요에 맞는 것을 만들도록 영감을 줄 것입니다.

참조 URL : https://stackoverflow.com/questions/34431734/endless-recyclerview-with-progressbar-for-pagination

반응형