Skip to content Skip to sidebar Skip to footer

Android Loadmore-listview

I have an internal-database(SQLite) with many entries. So I decided to load the first 20 entries in the listview when the user starts the activity and when he scrolls down he can l

Solution 1:

First, better use RecyclerView with viewholder, second you better load data on scroll listener and some trick, for example you have loaded 20 items then when you scroll list there will be good to load data when you scroll list and you scrolled to for example to 18 item at bottom, here you start your async task and when you scroll to 20 your loading will finish and you will update list with 20 more so user will not even see when you are loading.

mVisibleThreshold = 2// this is count items to bottom of current list when load will start.

here is my on scroll listener for recyclerview (can be adjusted to your listview):

mProductsResultsList.setOnScrollListener(newRecyclerView.OnScrollListener() {
            @OverridepublicvoidonScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @OverridepublicvoidonScrolled(RecyclerView recyclerView, int dx, int dy) {


                super.onScrolled(recyclerView, dx, dy);
                mVisibleItemCount = mProductsResultsList.getChildCount();
                mTotalItemCount = mProductsResultsLayoutManager.getItemCount();
                mFirstVisibleItem = mProductsResultsLayoutManager.findFirstVisibleItemPosition();

                if (mLoadingInProgress) { // boolean set to true if you are already loading and false after you will update adaptorif (mTotalItemCount > mPreviousTotal) {

                        mPreviousTotal = mTotalItemCount;
                    }
                }
                if (!mLoadingInProgress && (mTotalItemCount - mVisibleItemCount)
                        <= (mFirstVisibleItem + mVisibleThreshold)) {
                    mLoadingInProgress = true;
                        mLastPageRequest++; // current page to load and add// Here you load data and update adapter// if in async task then start it here and set mLoadingInProgress to true and onPostExecute add to list result and make notifyDatasetChanged on adapter then mLoadingInProgress to false.


                }
            }
        });

One more: don't touch any views in background tasks (otherwise you will stuck main thread), make all updates and notifications after task code in onPostExecute with RunOnUIThread.

okay as you are interested in this solution will improve answer:

mLastPageRequest - int I use to know which page I was loaded and which i must load next it's incrementing each time by 1 with ++

after loading data (from database or from web request you should add downloaded items to your list). Currently in my project my list is "mCategoryProducts" and this is my adapter mProductsResultsListAdapter.

all U need is to add all next items you downloaded to list you attached to adapter with

mCategoryProducts.addAll(downloadedListProducts); // here you are adding items you just loaded to existing list to the end, 

now next code:

publicvoiddisplayGotResults(){
        if(mCategoryProducts != null){
            if(mProductsResultsListAdapter == null && mLastPageRequest==1){
                mProductsResultsListAdapter = new ProductListRecyclerViewAdapter(this, mCategoryProducts, true);
                mProductsResultsList.setAdapter(mProductsResultsListAdapter);
                mProductsResultsListAdapter.setClickListener(this);
            }else{
                mProductsResultsListAdapter.notifyDataSetChanged();
                //notifyItemInserted(mSearchList.size()-1);
            }
            if(mCategoryProducts.size()>0) {
                mCategoryIsEmptyInfo.setVisibility(View.GONE);
            }else{
                mCategoryIsEmptyInfo.setVisibility(View.VISIBLE);
            }
        }else{
            mCategoryIsEmptyInfo.setVisibility(View.VISIBLE);
        }
    }

Here if adapter not already initialised then we create it and attaching to it our list mCategoryProducts otherwise if this is second loading then we simple notify adapter that "Hey man new data is comming :)" with:

mProductsResultsListAdapter.notifyDataSetChanged();

Notes: mCategoryIsEmptyInfo - is view which i show when there is no items to display.

Here you are my custom adaptor with interface for clicks that can be handled in activity )

publicclassProductListRecyclerViewAdapterextendsRecyclerView.Adapter<ProductListRecyclerViewAdapter.ProductListRecyclerViewHolder> {
    private LayoutInflater mInflater;
    private Context mContext;
    private DrawerLayout mNavigationDrawer;
    private ClickListener mClickListener;
    private LongClickListener mLongClickListener;
    List<ProductModel> navigationData = Collections.emptyList();
    private ImageLoader mImageLoader;
    private VolleyServiceSingleton mVollayService;
    privateboolean mShowThumbNail;

    //For animationsprivateintmLastPositiion= -1;

    publicProductListRecyclerViewAdapter(Context context, List<ProductModel> navData, boolean showThumbNail){
        mInflater = LayoutInflater.from(context);
        this.navigationData = navData;
        mContext = context;
        mVollayService = VolleyServiceSingleton.getInstance();
        mImageLoader = mVollayService.getImageLoader();
        mShowThumbNail = showThumbNail;
    }


    @Overridepublic ProductListRecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Viewview= mInflater.inflate(R.layout.list_item_product, parent, false);
        ProductListRecyclerViewHolderholder=newProductListRecyclerViewHolder(view);
        return holder;
    }

    @OverridepublicvoidonBindViewHolder(final ProductListRecyclerViewHolder viewHolder, int position) {
        ProductModelcurrentItem= navigationData.get(position);
        viewHolder.productBrand.setText(currentItem.ManufacturerName);
        viewHolder.productName.setText(currentItem.Name);
        viewHolder.productCode.setText(currentItem.Code);
        viewHolder.productPrice.setText(String.format(mContext.getString(R.string.money_sign), Utils.decimalWithCommas(String.valueOf(currentItem.DealerPrice))));

        if(Constants.SHOW_IMAGE_THUMBNAILS_IN_LIST && mShowThumbNail){
            if(currentItem.CatalogImage != null && currentItem.CatalogImage.contains("http")){
                viewHolder.productThumbnail.setVisibility(View.VISIBLE);
                mImageLoader.get(currentItem.CatalogImage, newImageLoader.ImageListener() {
                    @OverridepublicvoidonResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
                        viewHolder.productThumbnail.setImageBitmap(response.getBitmap());
                    }

                    @OverridepublicvoidonErrorResponse(VolleyError error) {

                    }
                });
            }
        }else{
            viewHolder.productThumbnail.setVisibility(View.GONE);
        }

    }

    publicvoidsetAnimation(View viewToAnimate, int position)
    {
        // If the bound view wasn't previously displayed on screen, it's animatedif (position > mLastPositiion)
        {
            Animationanimation= AnimationUtils.loadAnimation(mContext, android.R.anim.slide_in_left);
            viewToAnimate.startAnimation(animation);
            mLastPositiion = position;
        }
    }
    @OverridepublicintgetItemCount() {
        return navigationData.size();
    }

    publicvoidsetClickListener(ClickListener clickListener){
        this.mClickListener = clickListener;
    }
    publicvoidsetLongClickListener(LongClickListener lognClickListener){
        this.mLongClickListener = lognClickListener;
    }


    publicclassProductListRecyclerViewHolderextendsRecyclerView.ViewHolder implementsView.OnClickListener, View.OnLongClickListener{
        TextView productBrand;
        TextView productName;
        TextView productCode;
        TextView productPrice;
        ImageView productThumbnail;
        publicProductListRecyclerViewHolder(View itemView) {
            super(itemView);
            productBrand = (TextView) itemView.findViewById(R.id.product_brand);
            productName = (TextView) itemView.findViewById(R.id.product_name);
            productCode = (TextView) itemView.findViewById(R.id.product_code);
            productPrice = (TextView) itemView.findViewById(R.id.product_price);
            productThumbnail = (ImageView) itemView.findViewById(R.id.product_thumbnail);
            itemView.setOnClickListener(this);
            itemView.setTag(this);
            itemView.setOnLongClickListener(this);

        }

        @OverridepublicvoidonClick(View v) {
            if(mClickListener!=null){
                mClickListener.itemClicked(v, getAdapterPosition());
            }
        }

        @OverridepublicbooleanonLongClick(View v) {
            if(mLongClickListener!=null){
                mLongClickListener.itemLongClicked(v, getAdapterPosition());
            }
            returnfalse;
        }
    }
    publicinterfaceClickListener{
        voiditemClicked(View view, int position);
    }

    publicinterfaceLongClickListener{
        voiditemLongClicked(View view, int position);
    }
}

it will probably not work without you update to your needs, but nice sample how must be )

Make attention on

mProductsResultsListAdapter.setClickListener(this);

then in activity you can catch clicks with :

publicclassProductListActivityextendsActionBarActivityimplementsProductListRecyclerViewAdapter.ClickListener {

    //.....@OverridepublicvoiditemClicked(View view, int position) {

    }

    //.....
}

One more thing if you need to catch click for example on specific view in item in list for example on image in view holder then set click listener to "this" and set tag to this view and in activity then you can get tag from view and you will know where you clicked :)

your loading is from database:

 loading =true;
                    List<Log> newLogs = helper.getLogsRange(loadedEntriesCounter, LOAD_AMOUNT);
                    loadedEntriesCounter +=LOAD_AMOUNT;
                    logAdapter.logs.addAll(newLogs);
 loading =false; // somewhere here 

but this is not finally correct way to load data you can stuck main thread coz if database will be too big or you will make "heavy" selection you can get stuck for several milisec. you must do all data loading in async tasks and then on task done notify adapter.

in my project i load data from webapi, not from database but in anyway must be done in same way via async task, this is proper way )

classLoadNextPageTaskextendsAsyncTask<Integer, Void, List<ProductModel>> {
        int pageToLoad;

        publicLoadNextPageTask(int pageToLoad){
            this.pageToLoad = pageToLoad;
        }


        @Overrideprotected List<ProductModel> doInBackground(Integer... params) {
            return helper.getLogsRange(loadedEntriesCounter, LOAD_AMOUNT); // here apply page to load??? not sure
        }

        @OverrideprotectedvoidonPreExecute() {
            mMainLoadingProgress.setVisibility(View.VISIBLE);
            loading = true;
        }

        @OverrideprotectedvoidonPostExecute(List<ProductModel> newLogs) {
            loading = false;
            loadedEntriesCounter += LOAD_AMOUNT;
            logAdapter.logs.addAll(newLogs);

            runOnUiThread(newRunnable() {
                @Overridepublicvoidrun() {
                    logAdapter.notifyDataSetChanged();
                }
            });
            mMainLoadingProgress.setVisibility(View.GONE);

        }
    }

above is async task please update to your needs then where data loading simple start it with:

new LoadNextPageTask(pagenumber).execute(); // page is int

Post a Comment for "Android Loadmore-listview"