I am developing an Android Application for Online Shopping. I have created following view for List of Products using RecyclerView, in that i want to change view on selecting option menu item:
I have created following adapter named ProductAdapter, in that I have implemented code for changing layout in onCreateViewHolder for selecting layout file based on boolean value.
Code of Adapter ProductAdapter:
/***
* ADAPTER for Product to binding rows in List
*/
private class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductRowHolder> {
private List<Product> productList;
private Context mContext;
public ProductAdapter(Context context, List<Product> feedItemList) {
this.productList = feedItemList;
this.mContext = context;
}
#Override
public ProductRowHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(isProductViewAsList ? R.layout.product_row_layout_list : R.layout.product_row_layout_grid, null);
ProductRowHolder mh = new ProductRowHolder(v);
return mh;
}
#Override
public void onBindViewHolder(ProductRowHolder productRowHolder, int i) {
Product prodItem = productList.get(i);
// Picasso.with(mContext).load(feedItem.getName())
// .error(R.drawable.ic_launcher)
// .placeholder(R.drawable.ic_launcher)
// .into(productRowHolder.thumbnail);
double price = prodItem.getPrice();
double discount = prodItem.getDiscount();
double discountedPrice = price - (price * discount / 100);
String code = "";
if(prodItem.getCode() != null)
code = "[" + prodItem.getCode() + "] ";
productRowHolder.prodIsNewView.setVisibility(prodItem.getIsNew() == 1 ? View.VISIBLE : View.INVISIBLE);
productRowHolder.prodNameView.setText(code + prodItem.getName());
productRowHolder.prodOriginalRateView.setText("Rs." + new BigDecimal(price).setScale(2,RoundingMode.DOWN));
productRowHolder.prodDiscView.setText("" + new BigDecimal(discount).setScale(2,RoundingMode.DOWN) + "% off");
productRowHolder.prodDiscRateView.setText("Rs." + new BigDecimal(discountedPrice).setScale(2,RoundingMode.DOWN));
productRowHolder.prodOriginalRateView.setPaintFlags(productRowHolder.prodOriginalRateView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
}
#Override
public int getItemCount() {
return (null != productList ? productList.size() : 0);
}
public class ProductRowHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
//Declaration of Views
public ProductRowHolder(View view) {
super(view);
view.setOnClickListener(this);
//Find Views
}
#Override
public void onClick(View view) {
//Onclick of row
}
}
}
After that i have done code for Changing RecyclerView layout from List to Grid and Vice Versa in onOptionsItemSelected, here i am calling mAdapter.notifyDataSetChanged(); so it will call adapter again and change value.
onOptionsItemSelected:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
switch (id) {
case R.id.action_settings:
return true;
case android.R.id.home:
finish();
break;
case R.id.product_show_as_view:
isProductViewAsList = !isProductViewAsList;
supportInvalidateOptionsMenu();
mRecyclerView.setLayoutManager(isProductViewAsList ? new LinearLayoutManager(this) : new GridLayoutManager(this, 2));
mAdapter.notifyDataSetChanged();
break;
}
return super.onOptionsItemSelected(item);
}
I got little bit success like:
Image of Grid layout:
Image of List layout:
BUT NOW WHEN I SCROLL and then CHANGING VIEW is Displaying like:
Grid layout:
List layout:
I dont know why it happens after scrolling. Is there any other way to change view like this.
Today i just saw that this problem is because of ImageView, without it working perfectly.
Help please, You help will be appreciated.
I found solution with the starting of activity I have set LinearLayoutManager like:
mLayoutManager = new LinearLayoutManager(this);
mProductListRecyclerView.setLayoutManager(mLayoutManager);
after that onOptionsItemSelected written like:
case R.id.menu_product_change_view:
isViewWithCatalog = !isViewWithCatalog;
supportInvalidateOptionsMenu();
//loading = false;
mProductListRecyclerView.setLayoutManager(isViewWithCatalog ? new LinearLayoutManager(this) : new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
mProductListRecyclerView.setAdapter(mAdapter);
break;
and changing view in onCreateViewHolder like:
#Override
public ProductRowHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(isViewWithCatalog ? R.layout.product_row_layout_list : R.layout.product_row_layout_grid, null);
ProductRowHolder mh = new ProductRowHolder(v);
return mh;
}
From Starting to Ending you have to manage isViewWithCatalog variable for displaying which layout first.
In this SDK samples there is a complete example however it uses one layout file for both LayoutManagers
For keeping the scrolling position when switching the layout they used this function
public void setRecyclerViewLayoutManager(LayoutManagerType layoutManagerType) {
int scrollPosition = 0;
// If a layout manager has already been set, get current scroll position.
if (mRecyclerView.getLayoutManager() != null) {
scrollPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager())
.findFirstCompletelyVisibleItemPosition();
}
switch (layoutManagerType) {
case GRID_LAYOUT_MANAGER:
mLayoutManager = new GridLayoutManager(getActivity(), SPAN_COUNT);
mCurrentLayoutManagerType = LayoutManagerType.GRID_LAYOUT_MANAGER;
break;
case LINEAR_LAYOUT_MANAGER:
mLayoutManager = new LinearLayoutManager(getActivity());
mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER;
break;
default:
mLayoutManager = new LinearLayoutManager(getActivity());
mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER;
}
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.scrollToPosition(scrollPosition);
}
I solved this problem by setting the adapter again to the RecyclerView, after changing the layout manager.
listView.setLayoutManager(new LinearLayoutManager(this));
listView.setAdapter(adapter);
I think after scrolling and changed back to grid view, the first item is not recreated. I solved this by override getItemViewType() and inflate the layout files in onCreateViewHolder accordingly.
Related
It is basically a recyclerview that I fill with some textviews to show some categories. I have access to the position of clicked item within recyclerview, but how can I get a reference to the actual textview to set the background color?
here is my code
RecyclerView CategoriesRecyclerView;
RecyclerView.LayoutManager CategoriesLayoutManager;
CategoriesAdapter CategoriesAdapter;
List<Category> categories;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Get our RecyclerView layout:
CategoriesRecyclerView = FindViewById<RecyclerView>(Resource.Id.CategoriesRecyclerView);
//............................................................
// Layout Manager Setup:
// Use the built-in linear layout manager:
CategoriesLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.Horizontal, true);
// Plug the layout manager into the RecyclerView:
CategoriesRecyclerView.SetLayoutManager(CategoriesLayoutManager);
//............................................................
// Adapter Setup:
CategoriesAdapter = new CategoriesAdapter(categories);
// Register the item click handler (below) with the adapter:
CategoriesAdapter.ItemClick += CategoriesOnItemClick;
// Plug the adapter into the RecyclerView:
CategoriesRecyclerView.SetAdapter(CategoriesAdapter);
}
void CategoriesOnItemClick(object sender, int position)
{
//here I want the reference to the textview
// ((TextView).SetBackgroundColor(Color.Aqua);
Toast.MakeText(this, "This is category " + categories[position].Id + categories[position].Name, ToastLength.Short).Show();
}
I found the answer to my question in case it would help someone. you use OnBindViewHolder method of the RecyclerView.Adapter and make a reference list of all the textviews. and then use the position to get the one clicked.
// ADAPTER
// Adapter to connect the data set to the RecyclerView:
public class CategoriesAdapter : RecyclerView.Adapter
{
// Event handler for item clicks:
public event EventHandler<int> ItemClick;
// Underlying data set
public List<Category> Categories;
public List<TextView> TextViews = new List<TextView>();
// Load the adapter with the data set at construction time:
public CategoriesAdapter(List<Category> categories)
{
this.Categories = categories;
}
// Create a new CardView (invoked by the layout manager):
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
// Inflate the CardView for the photo:
View itemView = LayoutInflater.From(parent.Context).
Inflate(Resource.Layout.CategoryTextView, parent, false);
// Create a ViewHolder to find and hold these view references, and
// register OnClick with the view holder:
CategoryViewHolder vh = new CategoryViewHolder(itemView, OnClick);
return vh;
}
// Fill in the contents (invoked by the layout manager):
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
var vh = holder as CategoryViewHolder;
vh.CategoryTextView.Text = Categories[position].Name;
TextViews.Add(vh.CategoryTextView);
if (position == 0)
{
vh.CategoryTextView.SetBackgroundColor(Color.Aqua);
}
vh.CategoryTextView.Click += (sender, e) =>
{
foreach (var textView in TextViews)
{
textView.SetBackgroundColor(Color.White);
}
((TextView)sender).SetBackgroundColor(Color.Aqua);
};
}
// Return the number of photos available in the photo album:
public override int ItemCount
{
get { return Categories.Count; }
}
// Raise an event when the item-click takes place:
void OnClick(int position)
{
if (ItemClick != null)
ItemClick(this, position);
}
}
file with working code to test my issue, you have to add 2 items, then delete any of those and then add a new one to see how the deleted gets on top of the newly addded
https://www.dropbox.com/s/ojuyz5g5f3kaz0h/Test.zip?dl=0
I have a problem when deleting an item from the recyclerview, when ever I delete an item, IF I add a new item, the deleted item will appear in top of the newly added item, how could I get a fresh view or avod this from happening as is a big issue.
this is how i add items from my main activity
if (!resta || (diff > (3*60*1000)))
{
Ri.add(dataroot);
Integer position = adapter.getItemCount() + 1;
adapter.notifyItemInserted(position);
}
here my Adapter
public class ComandaAdapter extends RecyclerView.Adapter<ComandaAdapter.ComandaAdapterViewHolder>{
private Context mContext;
private ArrayList<Integer> lista_entradas = new ArrayList<>();
private ArrayList<Integer> lista_fondos = new ArrayList<>();
private ArrayList<Integer> lista_postres= new ArrayList<>();
private Boolean primeritem;
private ArrayList<DataRoot> Rir;
private TextView txt_comandas;
private TextView txt_entracola;
private TextView txt_fondocola;
private TextView txt_postrecola;
public ComandaAdapter(Context context, TextView tx_entracola, TextView tx_fondocola, TextView tx_postrecola, TextView tx_comandas, ArrayList<DataRoot> Rir)
{
this.mContext = context;
this.txt_comandas = tx_comandas;
this.txt_entracola = tx_entracola;
this.txt_fondocola = tx_fondocola;
this.txt_postrecola = tx_postrecola;
this.Rir= Rir;
}
#Override
public ComandaAdapter.ComandaAdapterViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
return new ComandaAdapter.ComandaAdapterViewHolder(LayoutInflater.from(mContext).inflate(R.layout.row,parent,false));
}
#Override
public void onBindViewHolder(final ComandaAdapter.ComandaAdapterViewHolder holder, final int position)
{
DataRoot Rdata = Rir.get(position);
holder.setdata(Rdata);
}
public void delete(int position)
{
Rir.remove(position);
notifyItemRemoved(position);
}
public class ComandaAdapterViewHolder extends RecyclerView.ViewHolder
{
Button btn_cerrar;
public ComandaAdapterViewHolder(View itemView)
{
super(itemView);
btn_cerrar = (Button) itemView.findViewById(R.id.btn_cerrar);
void setData(final DataRoot Rdata)
{
btn_cerrar.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View view)
{
btn_cerrar.setEnabled(false);
btn_cerrar.setBackgroundTintList(mContext.getColorStateList(R.color.cboff));
updateRetrofitEstadoorden(Rdata.get_id());
updateRetrofitNrocomanda(Rdata.get_id(), txt_comanda.getText().toString());
delete(getAdapterPosition());
}
});
Rdata.gerOrder();
creaboton():
and here my recyler
private void setAdapter()
{
adapter = new ComandaAdapter(this, txt_entracola, txt_fondocola, txt_postrecola, txt_comandas, Ri);
recyclerView.getRecycledViewPool().setMaxRecycledViews(-1, Ri.size());//va en 0 supuestamente -1 es default
recyclerView.setItemViewCacheSize(Ri.size()); //ver si hay que cambiar con cada item
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(adapter);
}
Thanks in advance for any help.
Images to show the problem
You are using the Recyclerview in a very non standard way. The issue you are seeing is because the views are being recycled (as they should be in a Recyclerview) but you are not clearing out the items from the previous view.
The problem in in this method:
public void setData(String value) {
container.removeAllViews(); // Remove all previously added views
textview.setText(value);
Random r = new Random();
int i1 = r.nextInt(5 - 1) + 1;
for (int i = 0; i < i1; i++) {
be = new Button(mContext);
be.setText("Boton " + i);
be.setLayoutParams(new LinearLayout.LayoutParams(240, LinearLayout.LayoutParams.WRAP_CONTENT));
container.addView(be);
}
}
By calling container.addView(be), you are manually adding extra views to the views that the Recyclerview creates. When you remove these views, they are cached and reused the next time you press "Add". The problem is that the cached view still contains all of the manually added views so you are then adding more views under the existing ones.
As you can see in the code above, i added container.removeAllViews(); which removes the views that were added previously ensuring that "Container" is empty before you start adding your extra views to it.
Also, unless you have a very specific reason for doing so, I would removes these lines as I believe you are hurting performance by having them:
list.getRecycledViewPool().setMaxRecycledViews(-1, index);
list.setItemViewCacheSize(index);
Desired Result
Maintain scroll position and give right position on specific view of custom row.
I am using RecyclerView in which I am trying to add on Demand Data on beginning of list as well as end of list.
Fetching data through Scrolling bottom is working perfectly.
But Scrolling top having issues as follows:
Set Click listener in Adapter's onBindViewHolder.
Set Click listener in Recyclerview's OnItemTouchListener.
Here is the case:
When I scroll up the list and then It loads 15 data and add it to the top of list.
Our intention is to Maintain scroll position.
Which can be achieved through
adapter.notifyItemRangeInserted(0, stringArrayListTemp.size());
But the Issue is while I am clicking on any Item without scrolling then View's OnClickListener gives wrong position.
You can check adapter's method:
#Override
public void onBindViewHolder(MyViewHolder holder, final int position) {
String item = moviesList.get(position);
holder.title.setText(item);
holder.title.setTag(position);
holder.title.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.e("onClick", "stringArrayList position: " + moviesList.get(position) + " - view position: " + moviesList.get((int) view.getTag()));
}
});
}
But at the same time in Recyclerview's addOnItemTouchListener gives right position with stringArrayList.get(position) but gives wrong position using getTag.
mRecyclerView.addOnItemTouchListener(
new RecyclerItemClickListener(this, new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
Log.e("onItemClick", "stringArrayList position: "+stringArrayList.get(position)+ " - view position: "+stringArrayList.get((int)view.getTag()));
}
}));
When I try to add data in the beginning of the list by following I am getting the exact issue.
// retain scroll position and gives wrong position onClick
adapter.notifyItemRangeInserted(0, stringArrayListTemp.size());
// does not retain scroll position, scroll to top & gives right position onClick
//adapter.notifyDataSetChanged();
This code work for me, so try some thing like that
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
private final Typeface font;
public TextView mTitle, mDetail, mDate, mCategory;
public CheckBox mCheckBox;
public ImageButton mImageButton;
public ImageView mImage;
public View mview;
private SparseBooleanArray selectedItems = new SparseBooleanArray();
public MyViewHolder(View view) {
super(view);
mTitle = (TextView) view.findViewById(R.id.item_title);
mDetail = (TextView) view.findViewById(R.id.item_detail);
mImage = (ImageView) view.findViewById(R.id.item_thumbnail);
mDate = (TextView) view.findViewById(R.id.date);
mCategory = (TextView) view.findViewById(R.id.category);
mCheckBox = (CheckBox) view.findViewById(R.id.checkbox);
mImageButton = (ImageButton) view.findViewById(R.id.imageButton);
mview = view;
view.setOnClickListener(this);
view.setOnLongClickListener(this);
}
#Override
public void onClick(View v) {
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl(MainActivity.activity, Uri.parse(mDataSet.get(getAdapterPosition()).getPostLink()));
}
how i maintain the recyclerview position
public void setRecyclerViewLayoutManager(LayoutManagerType layoutManagerType) {
int scrollPosition = 0;
if (mRecyclerView.getLayoutManager() != null) {
scrollPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager())
.findFirstCompletelyVisibleItemPosition();
}
switch (layoutManagerType) {
case GRID_LAYOUT_MANAGER:
mLayoutManager = new GridLayoutManager(getActivity(), SPAN_COUNT);
mCurrentLayoutManagerType = LayoutManagerType.GRID_LAYOUT_MANAGER;
break;
case LINEAR_LAYOUT_MANAGER:
mLayoutManager = new LinearLayoutManager(getActivity());
mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER;
break;
default:
mLayoutManager = new LinearLayoutManager(getActivity());
mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER;
}
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.scrollToPosition(scrollPosition);
}
I can change properties of selected item in RecyclerView but I want to remove selection for older selections.
Here is how I create RecyclerView :
fragmentViewPagerAdapter.addFragmentView((arg1, arg2, arg3) -> {
View view = arg1.inflate(R.layout.recyclerview_layout, arg2, false);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
selectDateRecyclerViewAdapter = new SelectDateRecyclerViewAdapter(dayList,this,(v,position) ->
{
AppCompatButton appCompatButton = (AppCompatButton)v.findViewById(R.id.selectHourButton);
AppCompatImageView appCompatImageView = (AppCompatImageView)v.findViewById(R.id.calendarDot);
highlightButton(appCompatButton,appCompatImageView);
});
recyclerView.setHasFixedSize(false);
recyclerView.addItemDecoration(selectDateRecyclerViewAdapter. new CalendarItemDecoration(10,dayList.size()));
GridLayoutManager gridLayoutManager = new GridLayoutManager(getApplicationContext(),4,GridLayoutManager.VERTICAL,false);
recyclerView.setLayoutManager(gridLayoutManager);
recyclerView.setAdapter(selectDateRecyclerViewAdapter);
selectDateRecyclerViewAdapter.notifyDataSetChanged();
return view;
});
highlightButton method changes background of Button etc.
Thanks.
You may need to hold flags to record which buttons are selected, when you select a new item, clear the flags first and reset it to the position of your new selected item. Then notifyDataSetChanged() or notifyItemChanged().done.
The main code of this function may be placed in highlightButton method. so It's better If you post the highlightButton code.
Since its a single selection, you can track the selected position using an external variable, say int selectedIndex;
In your adapter code :
public class ViewHolder extends RecyclerView.ViewHolder {
View itemView;
public ViewHolder(View v) {
super(v);
itemView = v;
v.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
selectedPostion = getAdapterPosition();
if( selectedPosition == RecyclerView.NO_POSITION) return;
recyclerViewOnItemClickListener.onItemSelect(itemView, getAdapterPosition()); //Custom listener - in turn calls your highlightButton method
//call notifyDataSetChanged(); or notifyItemRangeChanged();
}
});
}
}
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
holder.itemView.setSelected(position == selectedPostion);
}
My adapter code:
public class BrandAdapter extends RecyclerView.Adapter<BrandAdapter.BrandViewHolder> {
private static final String TAG = BrandAdapter.class.getSimpleName();
private List<BrandItem> brands;
private Context context;
public BrandAdapter(Context context, List<BrandItem> data) {
this.context = context;
this.brands = data;
}
public void setData(List<BrandItem> dataDownload) {
this.brands = dataDownload;
}
#Override
public BrandViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_item_brand, null);
BrandViewHolder holder = new BrandViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(BrandViewHolder holder, int position) {
BrandItem brandItem = brands.get(position);
String name = brandItem.getName();
int count = brandItem.getCountArticles();
holder.tvName.setText(name);
if (count > 0) {
holder.tvCount.setText("" + count);
} else {
holder.tvCount.setVisibility(View.GONE);
}
}
#Override
public int getItemCount() {
return brands.size();
}
public static class BrandViewHolder extends RecyclerView.ViewHolder {
TextView tvName;
TextView tvCount;
public BrandViewHolder(View itemView) {
super(itemView);
tvName = (TextView) itemView.findViewById(R.id.tv_brand_name);
tvCount = (TextView) itemView.findViewById(R.id.tv_count_article);
}
}
}
Fragment code :
recyclerView = (RecyclerView) view.findViewById(R.id.recycleView);
linearLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearLayoutManager);
adapter = new BrandAdapter(getActivity(), brands);
recyclerView.setAdapter(adapter);
Data for brands is downloaded from server. After downloaded finished, I just set new data for adapter by this code :
brands = downloadedBrands();
adapter.setData(brands);
adapter.notifyDataSetChanged();
Everything is Ok when data loaded for first time after the download finish. But when I scroll down the recycleview and scroll up again, data for each item is wrong now, all textview tvCount is gone. I do not know why.
Is there any problem from my code ?
Greenrobo's answer is correct but here is an explanation as to WHY you are having this issue.
You are assuming that your view is always set to the default values in your onBindViewHolder method.
The RecyclerView re-uses views that have scrolled off screen and therefore the view you are binding to may have already been previously used (and changed).
You onBindViewHolder method should always set EVERYTHING up. i.e all views reset to the exact values you want and do not assume that because you default an item to visible, it will always be so.
Please make tvCount visible when setting a non-zero count.
if (count > 0) {
holder.tvCount.setText("" + count);
holder.tvCount.setVisibility(View.VISIBLE);
} else {
holder.tvCount.setVisibility(View.GONE);
}
See if this helps.
You told that if count is less than 0, hide the view. What if count is greater than zero ? You are not making the view visible again. So simply make the below changes in your if condition:
if (count > 0) {
holder.tvCount.setText("" + count);
holder.tvCount.setVisibility(View.VISIBLE);
} else {
holder.tvCount.setVisibility(View.GONE);
}