I have a ViewPager as the row of a RecyclerView. It is like a "featured products" row.
I set the adapter of the ViewPager in the onBindViewHolder of the RecyclerView. ViewPager contains a TextView and an ImageView. ImageView is loaded from an URL via Glide in instantiateItem. The list of items in the ViewPager is 4.
The problem is, the ImageViews of the first two items in the ViewPager are not loaded. If I swipe the ViewPager to the 3rd item and back, I see the first ImageView successfully.
TextViews work fine. The problem is only with the images.
If I debug the code, I observe that the code block that belongs to Glide is reached.
If I use the ViewPager as a standalone view in the fragment (not as a row of the RecyclerView) I observe no problems.
There is a similar question:
Image not loading in first page of ViewPager (PagerAdapter)
But that unfortunately does not apply to my case. I declare my variables locally and with the final modifier already.
PagerAdapter's instantiateItem is like follows:
#Override
public Object instantiateItem(ViewGroup container, int position) {
final LayoutInflater inflater = (LayoutInflater) container.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View v = inflater.inflate(viewpager_layout, container, false);
final ImageView imgProduct = (ImageView) v.findViewById(R.id.imgProduct);
final TextView lblName = (TextView) v.findViewById(R.id.lblName);
final Product product = data.get(position);
lblName.setText(product.name);
Glide
.with(ctx)
.load(product.url)
.asBitmap()
.thumbnail((float) 0.4)
.placeholder(R.drawable.placeholder)
.error(R.drawable.placeholder)
.animate(R.animator.fade_in)
.into(imgProduct);
}
RecyclerView's onBindViewHolder looks like:
#Override
public void onBindViewHolder(final DataObjectHolder holder, int position) {
int listType = getItemViewType(position);
final ProductList item = data.get(position);
if (listType == 0) {
final List<Product> lstProducts = GetProducts(item.products);
final MyPagerAdapter myAdapter = new MyPagerAdapter(ctx, lstProducts);
holder.viewPager.setAdapter(myAdapter);
myAdapter.notifyDataSetChanged(); // this changes nothing also..
}
else {
// removed..
}
}
I work with the AppCompat library by the way.
All suggestions are welcome. Thank you.
Glide sets images asynchronously and helps to avoid any lag in UI thread. If there are many images it does take some time for the image to set in, but it doesn't cause any lag.
It's a simple Asynchronous request issue, when you swipe to the third tab and come back, till the data would have come and come and then it's gets binded with the UI.
I had faced the similar issue in my project.
I am assuming that you are using a new fragment for each view pager
One thing you can do is...
1.While creating fragment in viewPager in **getItem()**, set a tag with fragment.
2. create a viewPager **addOnPageChangeListener** and on page selected, get that fragment by tag and check if image is loaded or not.
3. If not loaded, then show some loader, and wait for the response, after response hide the loader
Related
this question is related with this one that I asked before.
I create a viewpager in MainActivity.java like this:
final ViewPager viewPager = (ViewPager) findViewById(R.id.vp_horizontal_ntb);
viewPager.setAdapter(new PagerAdapter() {
#Override
public int getCount() {
return 5;
}
#Override
public boolean isViewFromObject(final View view, final Object object) {
return view.equals(object);
}
#Override
public void destroyItem(final ViewGroup container, final int position, final Object object) {
((ViewPager) container).removeView((View) object);
}
#Override
public Object instantiateItem(final ViewGroup container, final int position) {
if(position==0) {
// here is important!
} else if(position == 1) {
}
...
}
});
Now I want fill each page with some json RecyclerView data list.(get json from network).
each page has independent data list.
For first time, I create a fragment for each page like this:
if (position == 0) {
final View view = LayoutInflater.from(getActivity().getBaseContext()).inflate(R.layout.fragment_tab0, null, false);
tabFragment.MyAdapter adapter = new tabFragment.MyAdapter(getActivity().getSupportFragmentManager());
adapter.addFragment(new tab0Fragment(), getResources().getString(R.string.tab0));
container.addView(view);
return view;
}
(so for 5 page, I have 5 fragment.
DEMO
But My application run slow (laggy) when I swipe pages.(with tap buttom is normal)
So I tried write an Adapter class directly for each page like this:
if (position == 0) {
final View view = LayoutInflater.from(getBaseContext()).inflate(R.layout.item_vp_list, null, false));
final RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.rv);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(getBaseContext(), LinearLayoutManager.VERTICAL, false));
recyclerView.setAdapter(new Tab0RecycleAdapter());
container.addView(view);
return view;
}
with top code,my application run fast again with swap pages!
Is it important to create fragment per each page?
why I must use fragment?(because some programmer recommended it in viewpager)
my method (second method without fragment) is true or false for a real application?
(I am noob and this is my first app)
Now I want fill each page with some json RecyclerView data list.(get json from network).
If you perform this network task on the UI thread, it will block and could cause laggy performance. This could be the reason your pages load slowly. You should perform network tasks on a separate thread.
So I tried write an Adapter class directly for each page like this
You only need one adapter per recycler view. If you want to support multiple views within the same adapter, override getItemViewType(). Example here: https://stackoverflow.com/a/26245463/7395923
Is it important to create fragment per each page?
Why I must use fragment? (because some programmer recommended it in view pager)
It is possible to use a view pager without fragments. Base on your previous question (linked at the top), it does seem overkill to load an entire fragment just to inflate a view. Here is a link to an example of a view pager without fragments: https://stackoverflow.com/a/18710626/7395923
I hope this helps.
I am having a problem with the ListView adapter. I am trying to develop a simple timeline view. I have decided to use ListView and BaseAdapter for it, in order to efficiently show items in the vertical row. Everything seems to be fine but there is a problem with the reuse of views used to visualize items in the list. Each item has three TextView (Title, Details and Story), ViewPager which I am using to display photos and Jake Wharton's LinePageIndicator to show how many photos are there and which of them is currently displayed.
There are always right pictures loaded in the ViewPager and they are swiped from left to the right, however the indicator does not work right. Sometimes is shows that there are two photos although there is only one (this happens because the previous view had two photos) and it does not show that the first photo in the ViewPager is currently presented. It shows the last current position in the ViewPager from the previous view.
Obviously there is a problem with the indicator and it should be somehow restarted every time when a view recycles. I have already looked in the LinePageIndicator source and seen that setViewPager(...) method has this
if (mViewPager != null) {
//Clear us from the old pager.
mViewPager.setOnPageChangeListener(null);
}
but obviously it does not help…
Finally here is my getView(...) method
...
#Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView txtTimelineItemTitle;
TextView txtTimelineItemDetails;
TextView txtTimelineItemText;
ViewPager viewPager;
LinePageIndicator indicator;
if (convertView == null) {
convertView = LayoutInflater.from(context)
.inflate(R.layout.timeline_item, parent, false);
txtTimelineItemTitle = (TextView) convertView.findViewById(R.id.timeline_item_title);
txtTimelineItemDetails = (TextView) convertView.findViewById(R.id.timeline_item_details);
txtTimelineItemText = (TextView) convertView.findViewById(R.id.timeline_item_text);
viewPager = (ViewPager) convertView.findViewById(R.id.timeline_view_pager);
indicator = (LinePageIndicator) convertView.findViewById(R.id.indicator);
convertView.setTag(new ViewHolder(context, txtTimelineItemTitle, txtTimelineItemDetails, txtTimelineItemText, viewPager, indicator));
} else {
ViewHolder viewHolder = (ViewHolder) convertView.getTag();
txtTimelineItemTitle = viewHolder.txtTimelineItemTitle;
txtTimelineItemDetails = viewHolder.txtTimelineItemDetails;
txtTimelineItemText = viewHolder.txtTimelineItemText;
viewPager = viewHolder.viewPager;
indicator = viewHolder.indicator;
}
Story story = (Story) getItem(position);
txtTimelineItemTitle.setText(story.getTitle());
txtTimelineItemDetails.setText(Utils.dateFormat.format(stories.get(position).getDate()) + ", " + stories.get(position).getLocation());
txtTimelineItemText.setText(story.getText());
if (story.getImages().isEmpty()) {
viewPager.setVisibility(View.GONE);
indicator.setVisibility(View.GONE);
} else {
viewPager.setVisibility(View.VISIBLE);
indicator.setVisibility(View.VISIBLE);
ImageAdapter imageAdapter = new ImageAdapter(context);
imageAdapter.updateImages(story.getImages());
viewPager.setAdapter(imageAdapter);
indicator.setViewPager(viewPager);
}
return convertView;
}
...
Has anyone of you already encountered issue like this? How could it be fixed and if not, is there an alternative?
Thank you very much
Since this was very important for me, I have continued with exploring source code of the LinePageIndicator. I have seen that besides of the method setViewPager(ViewPager viewPager) there is also method setViewPager(ViewPager view, int initialPosition) which as you can see allows you to set the current item in the ViewPager. I have changed only one line in the code above
and it solved my issue.
indicator.setViewPager(viewPager, 0); //always start from the first item in the ViewPager
I'm trying to make an activity able to capture up to 4 images for sending them to our server.
I know how to capture the image and to add them to the activity, this already works, in a non efficient nor elegant way, and I would like to improve that.
Right now, I have Button with an onClick method that attaches an image to an empty ImageView, and and keeps track of how many images have been attached, because I can delete an image in order to pick a new one.
I'm wondering the best strategy, code-wise for future changes.
Options that I have considered but I have not (yet) implemented:
Button adds the image to a GridView so I can add and remove images from it's adapter instead.
The ImageView can attach and remove image from itself by onClick and therefore remove the Button
Any suggestions, ideas, strategies, thoughts?
Thank you in advance!
EDIT 1: my first approach to improve the code
I've implemented a GridView with a custom adapter (GridImageAdapter)
public class GridImagesAdapter extends BaseAdapter{
private List<Bitmap> images = new ArrayList<Bitmap>();
private int img_height;
public GridImagesAdapter(Context context, List<Bitmap> imagenes){
this.imagenes = imagenes;
inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
img_height = (int) (metrics.density * Constants.ONE_ROW_IMG_HEIGHT);
}
/*
* other common methods
*/
#Override
public View getView(int position, View row, ViewGroup parent) {
ViewHolder holder;
if(row == null){
row = inflater.inflate(R.layout.grid_img_item, parent, false);
holder = new ViewHolder();
holder.image = (ImageView) row.findViewById(R.id.img_add_in_grid);
holder.image.setLayoutParams(new GridView.LayoutParams(LayoutParams.MATCH_PARENT, img_height));
holder.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
}else{
holder = (ViewHolder)row.getTag();
}
holder.image.setImageBitmap(getItem(position));
row.setTag(holder);
return row;
}
private class ViewHolder{
ImageView image;
}
}
So I can populate my GridView in the activity like:
private void populateGridView(){
Bitmap a;
a = BitmapFactory.decodeResource(getResources(), R.drawable.no_image);
images.add(a);
images.add(a);
images.add(a);
images.add(a);
GridImagesAdapter adapter = new GridImagesAdapter(this, images);
mGridView.setAdapter(adapter);
mGridView.setOnItemClickListener(new GetImage());
}
where new GetImage() is an OnItemClickListener who takes care of the image capture itself, and replaces one of the R.drawable.no_image Bitmap
(no relevant code in there, just showing a Dialog to the user in order to choose from camera or gallery and start such Activity, and then in the onActivityResult method where I have a Bitmap to work with is where I handle the adapter change)
My question is more about the strategy chosen here than the actual code itself.
Drawbacks? Any more elegant or proper way to achieve the same result?
Everything is welcome, thank you for your answers.
Have u create a gridview and set up the adapter class?
Because I have implemented a similar case. Items are added to listview dynamically (by user input), and there is a button in each row that deletes that particular row (also from the database).
If u have, Let me know from where shall we begin.
Your general idea is more or less correct. You mentioned GetImage is irrelevant, but it absolutely isn't. From I understood, you are changing the view directly in there. Instead you should manipulate the adapter, changing the data it's holding, and then call notifyDataSetChanged. The adapter will take care of notifying the gridview that the data changed and the view will be updated. I would probably use an ArrayAdapter instead of a BaseAdapter as well.
The following is part of my code for onBindViewHolder (inside MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
// - get element from your dataset at this position
StatusItem item = mDataset.get(position);
//......
//Add content and timing to the textview
String content = item.getContent();
holder.mTextViewTime.setText(timing);
//Set the img
holder.imgViewIcon.setImageDrawable(item.getProfileDrawable());
//Set content image (for Instagram)
holder.mImageViewContentPic.setImageDrawable(item.getContentDrawable());
//HIDE THE VIEW Start
if(item.getContentDrawable() == null){
holder.mImageViewContentPic.setVisibility(View.GONE);
}
//HIDE THE VIEW End
}
The part HIDE THE VIEW is not working as expected.
When I am scrolling downwards, the views are working normally. However, when I start to scroll upwards, i.e. revisited the previous views, the ImageViews that are supposed to be VISIBLE becomes GONE, although I checked my dataset and verified that it has not been modified. Try calling other methods on the views also give erratic results(positions and items in dataset do not match).
It seems that the view holders are not binded to specific positions inside the RecyclerView.
The code works as expected if I remove the HIDE THE VIEW part.
Is there any way to solve this issue and dynamically hide views in my case?
Note: I used some AsyncTasks to update the dataset and call notifyDataSetChanged(), if that is relevant.
###This is the solution to your problem:###
holder.mImageViewContentPic.setVisibility(View.VISIBLE);
if(item.getContentDrawable() == null){
holder.mImageViewContentPic.setVisibility(View.GONE);
}
Because RecyclerView use recycle very well, ViewHolder A may be used to be ViewHolder B, so you need to specific every attribute of a ViewHolder in case of some attributes attach to a wrong object.
This seems very weird, may be I am missing something please help me.
I have an image in a listitem in a listview and I am trying to set bitmap but it is not working.
Code where I am setting the Bitmap:
MainActivity activity = (MainActivity) context;
// FragmentManager fmanager = activity.getFragmentManager();
ListFragment fragment = activity.getCurrentFragment();
Adapter adapter = fragment.getListView().getAdapter();
int counter = 0;
for (String key: logoAndCorrespondingPosition.keySet()){
View x = adapter.getView(logoAndCorrespondingPosition.get(key), null, null);
//logoAndCorrespondingPosition.
ImageView xx = (ImageView) x.findViewWithTag(key);
TextView xy = (TextView) x.findViewById(R.id.listitem_brand_with_offers_brandName);
if (xy!=null)
xy.setText("asdasdsdas");
byte[] logoBytes = response.ResponseResult1.get(counter);
if (logoBytes!=null){
Bitmap bitmap = BitmapFactory.decodeByteArray(logoBytes, 0, logoBytes.length);
//xx.setImageResource(R.color.transparent);
xx.setImageBitmap(bitmap);
}
counter++;
}
Yes, I am accessing this listview after it has been populated as I am filling the blank images with bitmap value.
Interestingly, on debugging, none of the views (Image and Text, code above) returns null, they have a value associated with them, in spite of it the settext and setimagebitmap never works.
Any help much appreciated
It's bad idea to fill images outside of adapter. It's important to understand how adapter works. When you call getView() method of adapter, adapter binding existing view (or creating new) to display on the screen. If you try to call method getView() for position, which is not displaying on the screen, adapter will return the first view on screen. (if you properly working with convertView). That's why you can set bitmap outside adapter only for items, which are on the screen now. There some techniks to do what you want:
Get image in getView(). In this case, you must controll, that your imageview still on screen and not reused, while scrolling. It's happening because, while you loading data, user can scroll and this list item reused. Your imageView still exist, but situated in another position. To prevent this, you can setTag() for your image view inside bindView with position. And on post execute check tag of your imageView with your value. For exaple:
View bindView(final View v, int position) {
final String url = "url to bitmap";
ImageView iv = <get image view>;
iv.setTag(url);
new AsyncTask<Void, Void, Bitmap> {
Bitmap doInBackground(Void... void) {
Bitmap bitmap = <load your bitmap>;
return bitmap;
}
void onPostExecute(Bitmap bitmap) {
if (iv.getTag().equals(url))
iv.setImageBitmap(bitmap);
}
}
return view;
}
Note, i wrote this code right here and it's pseudocode to show idea.
The second way is to use lib like Picasso. It will all this work for you.