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 load 20 more each time pressing a button if there are entries left.
EDIT
//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();
}
}
The loading only happens ones! Where do I have to set loading on false?
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(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.
}
}
});
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:
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);
}
}
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 )
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);
}
}
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 :
public class ProductListActivity extends ActionBarActivity implements ProductListRecyclerViewAdapter.ClickListener {
//.....
#Override
public void itemClicked(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 )
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);
}
}
above is async task please update to your needs
then where data loading simple start it with:
new LoadNextPageTask(pagenumber).execute(); // page is int
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
How can I get the position of item in recycler view while clicking on it using pagination?
Example : If I click on movie 10, Toast message should display
saying "Clicked on Item 10"
make interface into adapter class for click event on recycler view item.
onItemClickListner onItemClickListner;
public void setOnItemClickListner(CommentsAdapter.onItemClickListner onItemClickListner) {
this.onItemClickListner = onItemClickListner;
}
public interface onItemClickListner {
void onClick(int position);//pass your object types.
}
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
// below code handle click event on recycler view item.
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
onItemClickListner.onClick(position); //pass any data
}
});
}
after that adapter class bind into recyclerview and make sure adapter not null then call below code..
adapter.setOnItemClickListner(new CommentsAdapter.onItemClickListner() {
#Override
public void onClick(int position) {
// pass data get here
Toast.makeText(getApplicationContext(),""+position,Toast.LENGTH_LONG).show();
}
});
// Used to cache the views within the item layout for fast access
public class ViewHolder extends RecyclerView.ViewHolder implements
View.OnClickListener {
public TextView tvName;
public TextView tvHometown;
private Context context;
public ViewHolder(Context context, View itemView) {
super(itemView);
this.tvName = (TextView) itemView.findViewById(R.id.tvName);
this.tvHometown = (TextView) itemView.findViewById(R.id.tvHometown);
// Store the context
this.context = context;
// Attach a click listener to the entire row view
itemView.setOnClickListener(this);
}
// Handles the row being being clicked
#Override
public void onClick(View view) {
int position = getAdapterPosition(); // gets item position
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
User user = users.get(position);
// We can access the data within the views
Toast.makeText(context, tvName.getText(), Toast.LENGTH_SHORT).show();
}
}
}
for more info link
In your Adapter try this
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemView.setOnClickListener {
// Do something here.
}
make an interface for loading next page items
public interface LoadMoreItems {
void LoadItems();
}
Then add on addOnScrollListener to load more item when your view comes to end
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;
}
}
});
add onCreateViewHolder() is where I put the ProgressBar or not.
#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 that is where I put the LoadItems() to add the others items is
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
}
});
just followed this Github repository(Note: this is using AsyncTask maybe it's useful as my answer
Giving attribute from this answer
I have a RecyclerView inside a NestedScrollView that show some data downloaded asynchronously. The problem is that there is a significant lag when the items are initilized. After some tests I found out that the problem is that onCreateViewHolder is called for every item and it took some time to inflate the layout. This is my adapter:
public class EpisodeAdapter extends RecyclerView.Adapter<EpisodeAdapter.ViewHolder> {
private static final String TAG = "EpisodeAdapter";
private static final int NO_POSITION = -1;
private static final int EXPAND = 1;
private static final int COLLAPSE = 2;
private SparseArray<Episode> episodes;
private OnItemClickListener<Episode> downloadClickListener;
private OnItemClickListener<Episode> playClickListener;
private RecyclerView recyclerView;
private final EpisodeAnimator episodeAnimator;
private final Transition expandCollapse;
private int expandedPosition = NO_POSITION;
public EpisodeAdapter() {
episodes = new SparseArray<>();
episodeAnimator = new EpisodeAnimator();
expandCollapse = new AutoTransition();
}
//Called when first loading items
public void swapEpisodes(SparseArray<Episode> newEpisodes){
final int previousSize = episodes.size();
episodes = newEpisodes;
expandedPosition = NO_POSITION;
Log.e(TAG, "Swap called");
if(previousSize == 0) {
notifyItemRangeInserted(0, episodes.size());
}
else {
notifyItemRangeChanged(0, Math.max(previousSize, episodes.size()));
}
}
//Called when downloading other information, this seems to work fine without delay
public void setEpisodesDetails(final List<TmdbEpisode> episodeList){
for (TmdbEpisode episode : episodeList){
final int position = episodes.indexOfKey(episode.getNumber());
notifyItemChanged(position, episode);
}
}
#Override
public EpisodeAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Log.e(TAG, "Start createViewHolder");
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_episode, parent, false);
ViewHolder viewHolder = new ViewHolder(view);
viewHolder.downloadButton.setOnClickListener(v -> {
if(downloadClickListener != null)
downloadClickListener.onItemClick(v, episodes.valueAt(viewHolder.getAdapterPosition()));
});
viewHolder.playButton.setOnClickListener(v -> {
if(playClickListener != null)
playClickListener.onItemClick(v, episodes.valueAt(viewHolder.getAdapterPosition()));
});
viewHolder.itemView.setOnClickListener(v -> {
final int position = viewHolder.getAdapterPosition();
if(position == NO_POSITION) return;
TransitionManager.beginDelayedTransition(recyclerView, expandCollapse);
episodeAnimator.setAnimateMoves(false);
//Collapse any currently expanded items
if(expandedPosition != NO_POSITION){
notifyItemChanged(expandedPosition, COLLAPSE);
}
//Expand clicked item
if(expandedPosition != position){
expandedPosition = position;
notifyItemChanged(position, EXPAND);
}
else {
expandedPosition = NO_POSITION;
}
});
Log.e(TAG, "Finish createViewHolder");
return viewHolder;
}
#Override
public void onBindViewHolder(EpisodeAdapter.ViewHolder holder, int itemPosition) {
Log.e(TAG, "Start");
holder.number.setText(String.valueOf(episodes.keyAt(itemPosition)));
holder.details.setVisibility(View.GONE);
holder.itemView.setActivated(false);
Log.e(TAG, "Finish");
}
#Override
public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
Log.e(TAG, "Start payloads");
if(payloads.contains(EXPAND) || payloads.contains(COLLAPSE)){
setExpanded(holder, position == expandedPosition);
}
else if(!payloads.isEmpty() && payloads.get(0) instanceof TmdbEpisode){
TmdbEpisode episode = (TmdbEpisode) payloads.get(0);
holder.title.setText(episode.getName());
holder.details.setText(episode.getOverview());
}
else {
onBindViewHolder(holder, position);
}
Log.e(TAG, "Finish payloads");
}
private void setExpanded(ViewHolder holder, boolean isExpanded) {
holder.itemView.setActivated(isExpanded);
holder.details.setVisibility((isExpanded) ? View.VISIBLE : View.GONE);
}
public void setPlayClickListener(OnItemClickListener<Episode> onItemClickListener){
playClickListener = onItemClickListener;
}
public void setDownloadClickListener(OnItemClickListener<Episode> onItemClickListener){
downloadClickListener = onItemClickListener;
}
#Override
public int getItemCount() {
return episodes.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
View itemView;
TextView number;
FadeTextSwitcher title;
ImageButton downloadButton;
FloatingActionButton playButton;
TextView details;
ViewHolder(View itemView) {
super(itemView);
Log.e(TAG, "Start constructor");
this.itemView = itemView;
number = itemView.findViewById(R.id.number);
title = itemView.findViewById(R.id.title);
downloadButton = itemView.findViewById(R.id.download_button);
playButton = itemView.findViewById(R.id.play_button);
details = itemView.findViewById(R.id.details);
Log.e(TAG, "Finish constructor");
}
}
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
this.recyclerView = recyclerView;
this.recyclerView.setItemAnimator(episodeAnimator);
expandCollapse.setDuration(recyclerView.getContext().getResources().getInteger(R.integer.episode_expand_collapse_duration));
expandCollapse.setInterpolator(AnimationUtils.loadInterpolator(this.recyclerView.getContext(), android.R.interpolator.fast_out_slow_in));
expandCollapse.addListener(new Transition.TransitionListener() {
#Override
public void onTransitionStart(android.transition.Transition transition) {
EpisodeAdapter.this.recyclerView.setOnTouchListener((v, event) -> true);
}
#Override
public void onTransitionEnd(android.transition.Transition transition) {
episodeAnimator.setAnimateMoves(true);
EpisodeAdapter.this.recyclerView.setOnTouchListener(null);
}
#Override
public void onTransitionCancel(android.transition.Transition transition) {}
#Override
public void onTransitionPause(android.transition.Transition transition) {}
#Override
public void onTransitionResume(android.transition.Transition transition) {}
});
}
static class EpisodeAnimator extends SlideInItemAnimator {
private boolean animateMoves = false;
EpisodeAnimator() {
super();
}
void setAnimateMoves(boolean animateMoves) {
this.animateMoves = animateMoves;
}
#Override
public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
if (!animateMoves) {
dispatchMoveFinished(holder);
return false;
}
return super.animateMove(holder, fromX, fromY, toX, toY);
}
}
}
Is there a way to force reusing the same ViewHolder for every items? So onCreateViewHolder will be called once.
I've also set nestedScrollingEnabled="false" in the recyclerview.
I have a RecyclerView inside a NestedScrollView
I'm going to guess that your <RecyclerView> tag has its height defined as wrap_content. If it does, that means that you're inflating a layout resource (and creating a ViewHolder object) for every single item in your data set; potentially thousands of layout inflations and object creations.
The recycling behavior of RecyclerView only works when the height of the recyclerview is smaller than the height needed to display its children. It's normal for a recyclerview to create a small double-digit number of ViewHolder instances (usually however many items you can see on screen at once plus a few to optimize views just off-screen), but this depends on the fact that your recyclerview's size is constrained by the screen size (i.e. you're using match_parent or a fixed size).
In the case of a RecyclerView with wrap_content height inside a NestedScrollView, the user won't be able to see all of the items at a single time, but the Android framework only knows that you have a recyclerview large enough to hold every single item in your data set and so it has to create a viewholder for every single item.
You'll have to figure out a way to rework your layout hierarchy so that you can use some limited height for your RecyclerView.
I have a problem with RecyclerView. I implemented OnScrollListener to it and when user reach end of list it should load a new data depending on index (index 1 loading first 8 index 2 loading second 8 etc) But problem is as soon as adapter start loading new data it scroll me to top :(
This is my code from function where I'm calling server:
public void showlist() {
progressBar = new ProgressDialog(getActivity());
progressBar.setMessage("Please wait ...");
progressBar.show();
NetworkSDK.getInstance().getNews(size, offset, new Callback<List<News>>() {
#Override
public void onResponse(Call<List<News>> call, Response<List<News>> response) {
if (response.code() == 401) {
Intent intent = new Intent(getContext(), MainPreLogin.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
progressBar.dismiss();
startActivity(intent);
SharedData.getInstance().removeString("token");
}
if (response.isSuccess()) {
newsAdapter = new NewsAdapterRecycler(listNews);
if (response.body().size() == 0) {
progressBar.dismiss();
Toast.makeText(getActivity(), R.string.noMorenews, Toast.LENGTH_SHORT).show();
} else {
for (int i = 0; i < response.body().size(); i++) {
newsAdapter.insert(newsAdapter.getItemCount() + 1, response.body().get(i));
}
newsList.setAdapter(newsAdapter);
newsAdapter.notifyDataSetChanged();
progressBar.dismiss();
}
}
}
#Override
public void onFailure(Call<List<ba.project.models.News>> call, Throwable t) {
Toast.makeText(getContext(), R.string.errorNoconnection, Toast.LENGTH_LONG).show();
progressBar.dismiss();
}
});
}
This is my adapter:
public class NewsAdapterRecycler extends RecyclerView.Adapter<NewsAdapterRecycler.MyViewHolder> {
private static int selectedItem = -1;
ArrayList<News> newslist;
Context context;
public NewsAdapterRecycler(ArrayList<News> newslist) {
this.newslist = newslist;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
context = parent.getContext();
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.single_news_layout, parent, false);
return new MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
View view = null;
Log.d("Pozicija News",String.valueOf(position));
News news = newslist.get(position);
holder.shortText.setText(news.getIntro());
holder.name.setText(news.getSubject());
if (news.getImagePath().equals(""))
Picasso.with(context).load(R.drawable.logo_Def).fit().centerCrop().into(holder.picture);
else
Picasso.with(context).load(news.getImagePath()).fit().centerCrop().into(holder.picture);
holder.date.setText(news.getShortDate());
if (selectedItem == position)
holder.itemView.setSelected(true);
}
#Override
public int getItemCount() {
return newslist.size();
}
public void insert(int position, News data) {
newslist.add(position - 1, data);
notifyItemInserted(position);
}
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView name;
TextView shortText;
TextView date;
ImageView picture;
public MyViewHolder(View view) {
super(view);
this.name=(TextView)view.findViewById(R.id.news_title);
this.shortText=(TextView)view.findViewById(R.id.news_desc);
this.picture=(ImageView)view.findViewById(R.id.news_image);
this.date=(TextView)view.findViewById(R.id.news_date);
}
}
}
This is my scrolllsitener
newsList.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = newsList.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
Log.d("Visibleitem", String.valueOf(visibleItemCount));
Log.d("totalItem", String.valueOf(totalItemCount));
Log.d("firstvisibleitem", String.valueOf(firstVisibleItem));
if (restarting) {
previousTotal = 0;
visibleThreshold = 5;
}
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount)
<= (firstVisibleItem + visibleThreshold)) {
offset++;
showlist();
// Do something
loading = true;
restarting = false;
}
}
});
Also if it's meter this funciton is called every time when user scroll to this fragment via this:
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && !areNewsLoaded) {
listNews = new ArrayList<>();
offset=0;
showlist();
restarting = true;
}
}
I tried solutions posted above but none worked. Helpfully I manage to find some fix. Solution is in 3 magic lines :
private Parcelable recyclerViewState;
recyclerViewState = newsList.getLayoutManager().onSaveInstanceState();
newsList.getLayoutManager().onRestoreInstanceState(recyclerViewState);
So basically I added 2. line before adding new content to my list and I guess it remember current position and I added 3. line after adding content so I assume this piece of code remember position
Try using notifyDataSetChanged() instead of notifyItemInserted().
A new insert method:
public void insert(News data) {
newslist.add(data);
notifyDataSetChanged();
}
Also i suggest create method to append all news at one time and use addAll method of ArrayList
try something like this ,
first you have to create interface
public interface Update{
public void OnDBUpdate();
}
then in Main Activity or somewhere add this
public class MainActivity extends AppCompatActivity implements DialogFragUpdateListener
and create method where you call the Recycler adapter
public void OnDBUpdate() {
dbList =helpher.getDataFromDB();
mAdapter = new RecyclerAdapter(this,dbList);
mRecyclerView.setAdapter(mAdapter);
}
and the last step ,, call the method where you need to update recycler
((Update) getActivity()).OnDBUpdate();
hope this help :)
Hey community I want to make a RecyclerView like in the chat applications.
I tried setStackFromEnd and it's working good.
final LinearLayoutManager llm = new LinearLayoutManager(getApplicationContext());
llm.setOrientation(LinearLayoutManager.VERTICAL);
llm.setStackFromEnd(true);
My on load more code based on this tutorial, At the end of tutorial you should see a video : Recyclerview Bottom Progress
mRecyclerView.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 (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
if (mOnLoadMoreListener != null) {
mOnLoadMoreListener.onLoadMore();
}
isLoading = true;
}
}
});
So I need to make a RecyclerView like in chat applications. Stack from end is working perfect but I need to trigger load more method when I go top instead of going bottom. Also add new items on top instead of bottom. Thanks in advance.
You don't need to make use of onScrollListener, if you simply want to show a Button "Load Earlier Messages" at the top. You can simply create an xml for your load more Button, make that as the first row of your RecyclerView and create an Interface to handle click events. I am posting code for the Adpater from one of my projects. Hope that will help you in getting some idea to proceed further.
/**
* Created by Mukesh on 21/12/2015.
*/
public class ChatListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mContext;
private List<UserMessage> userMessagesList;
private LayoutInflater mLayoutInflater;
private static final int ROW_TYPE_LOAD_EARLIER_MESSAGES = 0;
private static final int ROW_TYPE_SENDER = 1;
private static final int ROW_TYPE_RECEIVER = 2;
private int userId;
private boolean isLoadEarlierMsgs;
private LoadEarlierMessages mLoadEarlierMessages;
public ChatListAdapter(Context context, int userId, List<UserMessage> userMessagesList) {
mContext = context;
this.userMessagesList = userMessagesList;
mLayoutInflater = LayoutInflater.from(mContext);
this.userId = userId;
mLoadEarlierMessages = (LoadEarlierMessages) mContext;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case ROW_TYPE_LOAD_EARLIER_MESSAGES:
return new LoadEarlierMsgsViewHolder(mLayoutInflater.inflate(R.layout
.row_load_earlier_messages, parent, false));
case ROW_TYPE_SENDER:
return new SenderMsgViewHolder(mLayoutInflater.inflate(R.layout.row_sender_msg,
parent, false));
case ROW_TYPE_RECEIVER:
return new ReceiverMsgViewHolder(mLayoutInflater.inflate(R.layout
.row_receiver_msg, parent, false));
default:
return null;
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (getItemViewType(position)) {
case ROW_TYPE_LOAD_EARLIER_MESSAGES:
LoadEarlierMsgsViewHolder loadEarlierMsgsViewHolder =
(LoadEarlierMsgsViewHolder) holder;
if (isLoadEarlierMsgs) {
loadEarlierMsgsViewHolder.btLoadEarlierMessages
.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (mLoadEarlierMessages != null) {
mLoadEarlierMessages.onLoadEarlierMessages();
}
}
});
} else {
loadEarlierMsgsViewHolder.btLoadEarlierMessages.setVisibility(View.GONE);
}
break;
case ROW_TYPE_SENDER:
SenderMsgViewHolder senderMsgViewHolder = (SenderMsgViewHolder) holder;
// set data for your sender chat bubble
break;
case ROW_TYPE_RECEIVER:
ReceiverMsgViewHolder receiverMsgViewHolder = (ReceiverMsgViewHolder) holder;
// set data for your receiver chat bubble
break;
}
}
#Override
public int getItemCount() {
return userMessagesList.size() + 1;
}
#Override
public int getItemViewType(int position) {
if (position == 0) {
return ROW_TYPE_LOAD_EARLIER_MESSAGES; // row load earlier messages
} else if (userMessagesList.get(position - 1).getUser_id() == userId) {
return ROW_TYPE_SENDER; // sender row;
} else {
return ROW_TYPE_RECEIVER; // receiver row;
}
}
public interface LoadEarlierMessages {
void onLoadEarlierMessages();
}
public void setLoadEarlierMsgs(boolean isLoadEarlierMsgs) {
this.isLoadEarlierMsgs = isLoadEarlierMsgs;
}
static class LoadEarlierMsgsViewHolder extends RecyclerView.ViewHolder {
#Bind(R.id.btLoadEarlierMsgs) Button btLoadEarlierMessages;
public LoadEarlierMsgsViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
static class SenderMsgViewHolder extends RecyclerView.ViewHolder {
public SenderMsgViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
static class ReceiverMsgViewHolder extends RecyclerView.ViewHolder {
public ReceiverMsgViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}
And finally implement the LoadEarlierMessages listener in your ChatActivity and Override the onLoadEarlierMessages() method
/**
* Created by Mukesh on 21/12/2015.
*/
public class ChatActivity extends AppCompatActivity
implements ChatListAdapter.LoadEarlierMessages {
// getting users recent messages and init RecyclerView
private void showUserMessages() {
// initialising LayoutManager for RecyclerView and setting that to RecyclerView
mLinearLayoutManager = new LinearLayoutManager(this);
mLinearLayoutManager.setStackFromEnd(true); // to start the list from bottom
rvChatsList.setLayoutManager(mLinearLayoutManager);
// initialising RecyclerView Adapter and setting that to the RecyclerView
mChatListAdapter = new ChatListAdapter(this, userId, userMessagesList);
rvChatsList.setAdapter(mChatListAdapter);
// getting count of friend/contact messages and toggling visibility of load more button accordingly
int count = mDataBaseHandler.getCountOfMessages(contactId);
if (count != 0) {
if (count > Constants.MESSAGES_LIMIT_FOR_LOCAL_DATABASE) {
mChatListAdapter.setLoadEarlierMsgs(true);
} else {
mChatListAdapter.setLoadEarlierMsgs(false);
}
userMessagesList.addAll(mDataBaseHandler.getAllMessagesOfContact(contactId));
mChatListAdapter.notifyDataSetChanged();
}
}
#Override
public void onLoadEarlierMessages() {
ArrayList<UserMessage> tempList = mDataBaseHandler
.getPreviousMessagesOfContact(contactId, userMessagesList.size());
if (tempList.size() > 0) {
if (tempList.size() < Constants.MESSAGES_LIMIT_FOR_LOCAL_DATABASE) {
mChatListAdapter.setLoadEarlierMsgs(false);
}
View v = rvChatsList.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
for (int i = 0; i < tempList.size(); i++) {
userMessagesList.add(0, tempList.get(i));
}
mChatListAdapter.notifyDataSetChanged();
mLinearLayoutManager.scrollToPositionWithOffset(tempList.size(), top);
} else {
mChatListAdapter.setLoadEarlierMsgs(false);
}
}
}
Hope this will help..!!
I've implemented a Recyclerview in my test app. Currently I pull around 5000 items of data into a model and load my view with this list. Everything is great, and performs nicely but I don't particularly want to load all 5000 items. I would prefer to load 100 items and once the user hits the bottom, load the next 100 and essentially make it an ever growing list.
I can implement onScrollListener against the Recyclerview to do the detection of when I've reached the end but my issue (as simple as this sounds) is, what is the best way to tell the Recyclerview to load only 100 until I say?
My Adapter:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private List<ImageFeed> mFeedList;
public MyAdapter(List<ImageFeed> feedList) {
this.mFeedList = feedList;
}
public void setFeedList(List<ImageFeed> feedList) {
mFeedList = feedList;
}
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup vGroup, int i) {
View v = LayoutInflater.from(vGroup.getContext()).inflate(R.layout.recycler_list, vGroup, false);
return new MyAdapter.ViewHolder(v);
}
#Override
public void onBindViewHolder(final MyAdapter.ViewHolder viewHolder, int i) {
String mTitle = mFeedList.get(i).getTitle();
...//Do other stuff
}
#Override
public int getItemCount() {
return mFeedList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
protected ImageView mImageView;
protected TextView mTitle;
protected ProgressBar mLoader;
private int mItemId;
public ViewHolder(View itemView) {
super(itemView);
this.mImageView = (ImageView) itemView.findViewById(R.id.imgItem);
this.mTitle = (TextView) itemView.findViewById(R.id.txtTitle);
this.mLoader = (ProgressBar) itemView.findViewById(R.id.mLoaderProgress);
this.mImageView.setOnClickListener(this);
itemView.setOnClickListener(this);
}
public void setItem(int item) {
this.mItemId = item;
}
#Override
public void onClick(View v) {
if (v instanceof ImageView) {
Toast.makeText(v.getContext(), "Image view clicked: " + this.mItemId, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(v.getContext(), this.mItemId + " ", Toast.LENGTH_SHORT).show();
}
}
}
}
Thinking the scenario out I would have thought passing a smaller list is a possibility but it doesn't feel completely right.
Ok so this wasn't particularly difficult after all - I think my main issue was over thinking the scenario...
So the first thing I do is get the appropriate data (in this case through JSON) and store it in a JSON array for manipulation later - currently I'm still getting the full set (I.e 5000) but that's easily changeable.
This is done through an AsyncTask with an indeterminate progress bar. Once it's completed, I only parse the JSON to 20 elements and load this into the adapter.
Once done I have my Recyclerview.onScrollListener....
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mOnScreenItems = mRecyclerView.getChildCount();
mTotalItemsInList = llm.getItemCount();
mFirstVisibleItem = llm.findFirstVisibleItemPosition();
if (mLoadingItems) {
if (mTotalItemsInList > mPreviousTotal) {
mLoadingItems = false;
mPreviousTotal = mTotalItemsInList;
}
}
if (!mLoadingItems && (mTotalItemsInList - mOnScreenItems) <= (mFirstVisibleItem + mVisibleThreshold)) {
new AsyncLoadTask().execute();
mLoadingItems = true;
}
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
});
When it nears the end or hits the bottom a new AsynTask is kicked off to load additional items into the list. The onPostExecute() then updates the adapter through its mMyAdapter.notifyDataSetChanged(); method resulting in a nice smooth update. A lot of cleaning up to do but the basics are there.
Thanks for the direction guys!