Android LoadMore-ListView
Android LoadMore-ListView
我有一个包含许多条目的内部数据库 (SQLite)。所以我决定在用户启动 activity 时加载列表视图中的前 20 个条目,当他向下滚动时,如果还有条目,他可以在每次按下按钮时再加载 20 个条目。
编辑
//onCreate()
acceptedLogs = helper.getLogsRange(0, LOAD_AMOUNT);
loadedEntriesCounter = LOAD_AMOUNT;
logAdapter = new LogAdapter(acceptedLogs);
logRecyclerView.setAdapter(logAdapter);
logRecyclerView.setHasFixedSize(false);
linearLayoutManager = new LinearLayoutManager(getContext());
logRecyclerView.setLayoutManager(linearLayoutManager);
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = logRecyclerView.getChildCount();
totalItemCount = logRecyclerView.getLayoutManager().getItemCount();
int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
if (loading) { // boolean set to true if you are already loading and false after you will update adaptor
if (totalItemCount > previousTotalCount) {
previousTotalCount = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItemPosition + VISIBLE_THRESHOLD)) {
loading = true;
List<Log> newLogs = helper.getLogsRange(loadedEntriesCounter, LOAD_AMOUNT);
loadedEntriesCounter += LOAD_AMOUNT;
logAdapter.logs.addAll(newLogs);
logAdapter.notifyDataSetChanged();
}
}
加载只会发生!我必须在哪里设置 loading on false?
首先,最好将 RecyclerView 与 viewholder 一起使用,其次,你最好在滚动监听器上加载数据和一些技巧,例如你已经加载了 20 个项目,然后当你滚动列表时,当你滚动列表时加载数据会很好,你滚动到例如底部的 18 项,在这里你开始你的异步任务,当你滚动到 20 时你的加载将完成并且你将更新列表 20 更多这样用户甚至不会看到你何时加载。
mVisibleThreshold = 2 // this is count items to bottom of current list when load will start.
这是我的 recyclerview 滚动侦听器(可以根据您的列表视图进行调整):
mProductsResultsList.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(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 adaptor
if (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.
}
}
});
还有一点:不要在后台任务中触及任何视图(否则你会卡住主线程),在任务代码之后使用 RunOnUIThread 在 onPostExecute 中进行所有更新和通知。
好的,因为您对此解决方案感兴趣,将改进答案:
mLastPageRequest - int 我用来知道我加载了哪个页面以及接下来我必须加载哪个页面它每次递增 1 ++
加载数据后(从数据库或网络请求中,您应该将下载的项目添加到您的列表中)。
目前在我的项目中,我的列表是 "mCategoryProducts",这是我的适配器 mProductsResultsListAdapter。
您只需要将您下载的所有下一个项目添加到您使用
连接到适配器的列表中
mCategoryProducts.addAll(downloadedListProducts); // here you are adding items you just loaded to existing list to the end,
现在下一个代码:
public void displayGotResults(){
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);
}
}
如果适配器尚未初始化,那么我们创建它并将其附加到我们的列表 mCategoryProducts 否则,如果这是第二次加载,那么我们简单地通知适配器 "Hey man new data is comming :)" 使用:
mProductsResultsListAdapter.notifyDataSetChanged();
注意:mCategoryIsEmptyInfo - 是我在没有要显示的项目时显示的视图。
这是我的自定义适配器,带有可以在 activity 中处理的点击界面)
public class ProductListRecyclerViewAdapter extends RecyclerView.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;
private boolean mShowThumbNail;
//For animations
private int mLastPositiion = -1;
public ProductListRecyclerViewAdapter (Context context, List<ProductModel> navData, boolean showThumbNail){
mInflater = LayoutInflater.from(context);
this.navigationData = navData;
mContext = context;
mVollayService = VolleyServiceSingleton.getInstance();
mImageLoader = mVollayService.getImageLoader();
mShowThumbNail = showThumbNail;
}
@Override
public ProductListRecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.list_item_product, parent, false);
ProductListRecyclerViewHolder holder = new ProductListRecyclerViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(final ProductListRecyclerViewHolder viewHolder, int position) {
ProductModel currentItem = 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, new ImageLoader.ImageListener() {
@Override
public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
viewHolder.productThumbnail.setImageBitmap(response.getBitmap());
}
@Override
public void onErrorResponse(VolleyError error) {
}
});
}
}else{
viewHolder.productThumbnail.setVisibility(View.GONE);
}
}
public void setAnimation(View viewToAnimate, int position)
{
// If the bound view wasn't previously displayed on screen, it's animated
if (position > mLastPositiion)
{
Animation animation = AnimationUtils.loadAnimation(mContext, android.R.anim.slide_in_left);
viewToAnimate.startAnimation(animation);
mLastPositiion = position;
}
}
@Override
public int getItemCount() {
return navigationData.size();
}
public void setClickListener(ClickListener clickListener){
this.mClickListener = clickListener;
}
public void setLongClickListener(LongClickListener lognClickListener){
this.mLongClickListener = lognClickListener;
}
public class ProductListRecyclerViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener{
TextView productBrand;
TextView productName;
TextView productCode;
TextView productPrice;
ImageView productThumbnail;
public ProductListRecyclerViewHolder(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);
}
@Override
public void onClick(View v) {
if(mClickListener!=null){
mClickListener.itemClicked(v, getAdapterPosition());
}
}
@Override
public boolean onLongClick(View v) {
if(mLongClickListener!=null){
mLongClickListener.itemLongClicked(v, getAdapterPosition());
}
return false;
}
}
public interface ClickListener{
void itemClicked(View view, int position);
}
public interface LongClickListener{
void itemLongClicked(View view, int position);
}
}
如果您不更新您的需求,它可能无法工作,但很好的示例必须如此)
关注
mProductsResultsListAdapter.setClickListener(this);
然后在 activity 中,您可以通过以下方式捕获点击:
public class ProductListActivity extends ActionBarActivity implements ProductListRecyclerViewAdapter.ClickListener {
//.....
@Override
public void itemClicked(View view, int position) {
}
//.....
}
还有一件事,如果你需要在列表中的项目中的特定视图上捕获点击,例如在视图持有者中的图像上,然后将点击侦听器设置为 "this" 并将标签设置为此视图并在 activity 然后你可以从视图中获取标签,你就会知道你点击了哪里:)
您的加载来自数据库:
loading = true;
List<Log> newLogs = helper.getLogsRange(loadedEntriesCounter, LOAD_AMOUNT);
loadedEntriesCounter += LOAD_AMOUNT;
logAdapter.logs.addAll(newLogs);
loading = false; // somewhere here
但这不是加载数据的最终正确方法,如果数据库太大,您可能会卡住主线程,或者您将进行 "heavy" 选择,您可能会卡住几毫秒。您必须在异步任务中加载所有数据,然后在任务完成时通知适配器。
在我的项目中,我从 webapi 加载数据,而不是从数据库加载数据,但无论如何必须通过异步任务以相同的方式完成,这是正确的方式)
class LoadNextPageTask extends AsyncTask<Integer, Void, List<ProductModel>> {
int pageToLoad;
public LoadNextPageTask(int pageToLoad){
this.pageToLoad = pageToLoad;
}
@Override
protected List<ProductModel> doInBackground(Integer... params) {
return helper.getLogsRange(loadedEntriesCounter, LOAD_AMOUNT); // here apply page to load??? not sure
}
@Override
protected void onPreExecute() {
mMainLoadingProgress.setVisibility(View.VISIBLE);
loading = true;
}
@Override
protected void onPostExecute(List<ProductModel> newLogs) {
loading = false;
loadedEntriesCounter += LOAD_AMOUNT;
logAdapter.logs.addAll(newLogs);
runOnUiThread(new Runnable() {
@Override
public void run() {
logAdapter.notifyDataSetChanged();
}
});
mMainLoadingProgress.setVisibility(View.GONE);
}
}
以上是异步任务,请根据您的需要更新
然后从哪里开始数据加载简单:
new LoadNextPageTask(pagenumber).execute(); // page is int
我有一个包含许多条目的内部数据库 (SQLite)。所以我决定在用户启动 activity 时加载列表视图中的前 20 个条目,当他向下滚动时,如果还有条目,他可以在每次按下按钮时再加载 20 个条目。
编辑
//onCreate()
acceptedLogs = helper.getLogsRange(0, LOAD_AMOUNT);
loadedEntriesCounter = LOAD_AMOUNT;
logAdapter = new LogAdapter(acceptedLogs);
logRecyclerView.setAdapter(logAdapter);
logRecyclerView.setHasFixedSize(false);
linearLayoutManager = new LinearLayoutManager(getContext());
logRecyclerView.setLayoutManager(linearLayoutManager);
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = logRecyclerView.getChildCount();
totalItemCount = logRecyclerView.getLayoutManager().getItemCount();
int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
if (loading) { // boolean set to true if you are already loading and false after you will update adaptor
if (totalItemCount > previousTotalCount) {
previousTotalCount = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItemPosition + VISIBLE_THRESHOLD)) {
loading = true;
List<Log> newLogs = helper.getLogsRange(loadedEntriesCounter, LOAD_AMOUNT);
loadedEntriesCounter += LOAD_AMOUNT;
logAdapter.logs.addAll(newLogs);
logAdapter.notifyDataSetChanged();
}
}
加载只会发生!我必须在哪里设置 loading on false?
首先,最好将 RecyclerView 与 viewholder 一起使用,其次,你最好在滚动监听器上加载数据和一些技巧,例如你已经加载了 20 个项目,然后当你滚动列表时,当你滚动列表时加载数据会很好,你滚动到例如底部的 18 项,在这里你开始你的异步任务,当你滚动到 20 时你的加载将完成并且你将更新列表 20 更多这样用户甚至不会看到你何时加载。
mVisibleThreshold = 2 // this is count items to bottom of current list when load will start.
这是我的 recyclerview 滚动侦听器(可以根据您的列表视图进行调整):
mProductsResultsList.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(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 adaptor
if (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.
}
}
});
还有一点:不要在后台任务中触及任何视图(否则你会卡住主线程),在任务代码之后使用 RunOnUIThread 在 onPostExecute 中进行所有更新和通知。
好的,因为您对此解决方案感兴趣,将改进答案:
mLastPageRequest - int 我用来知道我加载了哪个页面以及接下来我必须加载哪个页面它每次递增 1 ++
加载数据后(从数据库或网络请求中,您应该将下载的项目添加到您的列表中)。 目前在我的项目中,我的列表是 "mCategoryProducts",这是我的适配器 mProductsResultsListAdapter。
您只需要将您下载的所有下一个项目添加到您使用
连接到适配器的列表中mCategoryProducts.addAll(downloadedListProducts); // here you are adding items you just loaded to existing list to the end,
现在下一个代码:
public void displayGotResults(){
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);
}
}
如果适配器尚未初始化,那么我们创建它并将其附加到我们的列表 mCategoryProducts 否则,如果这是第二次加载,那么我们简单地通知适配器 "Hey man new data is comming :)" 使用:
mProductsResultsListAdapter.notifyDataSetChanged();
注意:mCategoryIsEmptyInfo - 是我在没有要显示的项目时显示的视图。
这是我的自定义适配器,带有可以在 activity 中处理的点击界面)
public class ProductListRecyclerViewAdapter extends RecyclerView.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;
private boolean mShowThumbNail;
//For animations
private int mLastPositiion = -1;
public ProductListRecyclerViewAdapter (Context context, List<ProductModel> navData, boolean showThumbNail){
mInflater = LayoutInflater.from(context);
this.navigationData = navData;
mContext = context;
mVollayService = VolleyServiceSingleton.getInstance();
mImageLoader = mVollayService.getImageLoader();
mShowThumbNail = showThumbNail;
}
@Override
public ProductListRecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.list_item_product, parent, false);
ProductListRecyclerViewHolder holder = new ProductListRecyclerViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(final ProductListRecyclerViewHolder viewHolder, int position) {
ProductModel currentItem = 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, new ImageLoader.ImageListener() {
@Override
public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
viewHolder.productThumbnail.setImageBitmap(response.getBitmap());
}
@Override
public void onErrorResponse(VolleyError error) {
}
});
}
}else{
viewHolder.productThumbnail.setVisibility(View.GONE);
}
}
public void setAnimation(View viewToAnimate, int position)
{
// If the bound view wasn't previously displayed on screen, it's animated
if (position > mLastPositiion)
{
Animation animation = AnimationUtils.loadAnimation(mContext, android.R.anim.slide_in_left);
viewToAnimate.startAnimation(animation);
mLastPositiion = position;
}
}
@Override
public int getItemCount() {
return navigationData.size();
}
public void setClickListener(ClickListener clickListener){
this.mClickListener = clickListener;
}
public void setLongClickListener(LongClickListener lognClickListener){
this.mLongClickListener = lognClickListener;
}
public class ProductListRecyclerViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener{
TextView productBrand;
TextView productName;
TextView productCode;
TextView productPrice;
ImageView productThumbnail;
public ProductListRecyclerViewHolder(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);
}
@Override
public void onClick(View v) {
if(mClickListener!=null){
mClickListener.itemClicked(v, getAdapterPosition());
}
}
@Override
public boolean onLongClick(View v) {
if(mLongClickListener!=null){
mLongClickListener.itemLongClicked(v, getAdapterPosition());
}
return false;
}
}
public interface ClickListener{
void itemClicked(View view, int position);
}
public interface LongClickListener{
void itemLongClicked(View view, int position);
}
}
如果您不更新您的需求,它可能无法工作,但很好的示例必须如此)
关注
mProductsResultsListAdapter.setClickListener(this);
然后在 activity 中,您可以通过以下方式捕获点击:
public class ProductListActivity extends ActionBarActivity implements ProductListRecyclerViewAdapter.ClickListener {
//.....
@Override
public void itemClicked(View view, int position) {
}
//.....
}
还有一件事,如果你需要在列表中的项目中的特定视图上捕获点击,例如在视图持有者中的图像上,然后将点击侦听器设置为 "this" 并将标签设置为此视图并在 activity 然后你可以从视图中获取标签,你就会知道你点击了哪里:)
您的加载来自数据库:
loading = true;
List<Log> newLogs = helper.getLogsRange(loadedEntriesCounter, LOAD_AMOUNT);
loadedEntriesCounter += LOAD_AMOUNT;
logAdapter.logs.addAll(newLogs);
loading = false; // somewhere here
但这不是加载数据的最终正确方法,如果数据库太大,您可能会卡住主线程,或者您将进行 "heavy" 选择,您可能会卡住几毫秒。您必须在异步任务中加载所有数据,然后在任务完成时通知适配器。
在我的项目中,我从 webapi 加载数据,而不是从数据库加载数据,但无论如何必须通过异步任务以相同的方式完成,这是正确的方式)
class LoadNextPageTask extends AsyncTask<Integer, Void, List<ProductModel>> {
int pageToLoad;
public LoadNextPageTask(int pageToLoad){
this.pageToLoad = pageToLoad;
}
@Override
protected List<ProductModel> doInBackground(Integer... params) {
return helper.getLogsRange(loadedEntriesCounter, LOAD_AMOUNT); // here apply page to load??? not sure
}
@Override
protected void onPreExecute() {
mMainLoadingProgress.setVisibility(View.VISIBLE);
loading = true;
}
@Override
protected void onPostExecute(List<ProductModel> newLogs) {
loading = false;
loadedEntriesCounter += LOAD_AMOUNT;
logAdapter.logs.addAll(newLogs);
runOnUiThread(new Runnable() {
@Override
public void run() {
logAdapter.notifyDataSetChanged();
}
});
mMainLoadingProgress.setVisibility(View.GONE);
}
}
以上是异步任务,请根据您的需要更新 然后从哪里开始数据加载简单:
new LoadNextPageTask(pagenumber).execute(); // page is int