I have a recycler that show a list of images from api call. The images are different in height. When i make an API call to get the list of the images i also received the width and height of all images in pixel.
My ImageView height is wrap_content with width match_parent
I show the image with glide.
The problem i am having is after the image is shown there will be a height change to the ImageView.
How can i use the height and width which i receive from API and is in pixel to define the imageView height before the image is shown
You have a couple of options since you know the size of each image before it loads.
If you have a placeholder, use a placeholder image when you bind view holder data that is the same size as the image to be loaded. In other words, if the image is available, use it, otherwise, use a placeholder of the same size. This way, you can keep wrap_content.
If you don't have a placeholder, you can set the item view size programmatically when the view holder is created. This will result in a unique view holder for each image size. You also may be able to change the view holder size when the view is bound.
For #2, you would do something like the following in the RecyclerView adapter:
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
ItemViewHolder vh = (ItemViewHolder) holder;
// Retrieve the known height of the image to be displayed and set the
// variable thisImageHeight to that value. vh.mImageView is the ImageView
// that is being populated for this ViewHolder.
vh.mImageView.getLayoutParams().height = thisImageHeight;
// ... more stuff below
}
Related
I have to display posts from a json feed inside a RecyclerView and I have a layout for how a single row looks inside the RecyclerView as follows
I have not yet implemented the bottom part in my actual layout which contains the red and green boxes from the figure above and my layout looks like this
I am also implementing Swipe to delete with undo which as you know requires a FrameLayout as the root , and 2 nodes under it, one showing the normal area and one showing the layout which is revealed on swipe. In my case, when I swipe the item, this is what you will see.
Now the problem is, there are 13 views per row and I don't like the odds of that, I will be displaying a maximum of 100 items in the RecyclerView at a given time and as you see it would lead to a large number of Views.
I have certain approaches in mind to make a custom View to reduce the number of Views in the row. What would be the best way according to you to optimise this View or should I say, reduce the number of Views per row in the RecyclerView. I get all the data from JSON and the central text area needs to be expandable in nature with a Read More or Read Less depending on its state.
Approach 1
Slight simplification
In this approach, I will combine the person's profile picture at the top left, the TextView with the name and updated time into a single Custom View, in other words, it will extend from View, have its own canvas for drawing the Bitmap, the Strings but I'll have to manually code RTL and LTR and other items using a StaticLayout in Android. I need the central text area to be expandable in nature so I will stick with one of the Expandable Views everyone keeps mentioning on stackoverflow.
Approach 2
Highly modular Custom UI component.
The entire diagram composed of the user's image, text with name, time, central text and image can be made into a single CustomView, I am not sure how I can make this expandable yet because a StaticLayout once initialised in Android cannot be modified. What do you think? Is this the correct approach to go? I will end up having only 4 children per row if I make the entire thing a single View. Is that a valid use case for custom Views?
Well, I found the answer myself. There are two types of post items I am dealing with, ones that contain an ImageView to display a 16:9 post image and the ones that don't. My RecyclerView was lagging heavily in the scroll operation till now since I had a single layout file which had an ImageView with its width set to match_parent and height set to wrap_content. When I was scrolling, the posts with Images were using Glide to load images and were calling requestLayout() since the size of those items were changing. This was resulting in too many requestLayout() calls causing the heavy lag while scrolling.
How did I fix this?
I made an Adapter which was sectioned having 2 types of rows.
#Override
public int getItemViewType(int position) {
if (mResults != null) {
return mResults.get(position).getPicture() != null ? IMAGE : NO_IMAGE;
}
return NO_IMAGE;
}
Based on whether the result at the current position contains an Image in the post or not, I had my onCreateViewHolder method modified to set a predefined size for the ImageView incase my item had a post image.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if (viewType == IMAGE) {
view = mLayoutInflater.inflate(R.layout.row_post_image, parent, false);
RowImageHolder holder = new RowImageHolder(view);
//To set the width and height of the ImageView of our image in the post, we get its LayoutParams and adjust its width and height to maintain 16:9 ratio
ViewGroup.LayoutParams params = holder.mPostPicture.getLayoutParams();
params.width = mPostImageWidth;
params.height = mPostImageHeight;
holder.mPostPicture.setLayoutParams(params);
return holder;
} else {
view = mLayoutInflater.inflate(R.layout.row_post_image, parent, false);
RowNoImageHolder holder = new RowNoImageHolder(view);
return holder;
}
}
And thats all which needs to be done. The onBindViewHolder sets an image if the type of the item supports an image. The benefit is that space is reserved in advance for those items that have images and the performance of the RecyclerView on scroll improves tremendously. I achieved this without using any custom views so far :)
i have 2 imageview. first one for containing the image and the other one as the frame.
i already set image position to have the same position as the frame after loading the image.
i also already check the value and its the same.
but somehow the image position is not the same as the frame when i try changing picture with 2 different size (width x height).
ivProfilePicture.setX(ivFrame.getX());
ivProfilePicture.setY(ivFrame.getY());
by the way, i use custom imageview for the image container, i only custom the function onMeasure. there's no image repositioning.
Hi I'm trying to build something like an horizontal gallery where I can add or remove pages using images from my gallery/camera.
I'm trying to make it work with large bitmaps so I'm using an algorithm to scale the bitmap and set it to the imageview of each page.
The algorithm requires the width/height of the ImageView (to scale down).
The problem I have is that when my custom PagerAdapter method is executed, the width/height of the ImageView is not yet known (getWidth/getHeight return 0), so it does not work:
public Object instantiateItem(ViewGroup collection, int position) {
LayoutInflater inflater = (LayoutInflater) collection.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.document_page, null);
((ViewPager) collection).addView(view, 0);
ImageView imageView = (ImageView) view.findViewById(R.id.imageView);
// Obtain the image file URI
// Call algorithm to get scaled bitmap using ImageView width and height --> PROBLEM: imageView.getWidth()/Height() return 0!!
// Set ImageView with scaled bitmap to avoid OutOfMemory Exception
return view;
}
What do you suggest?
Thanks.
Scaling the image client-side:
You don't need your own algorithm to scale an image if it's already on your device. An ImageView has a ScaleType that you can set like this:
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP)
(or in XML with the android:scaleType attribute).
The remaining question is how to get the image from your URL to display in the ImageView. Use libraries such as SmartImageView or Android-Query (more powerful) to achieve that in an asynchronous manner (i.e. without blocking your UI thread).
With SmartImageView, for example, you are able to use something like this:
myImageView.setImageUrl("http://www.awesomeimages.com/myawesomeimage.jpg");
Just read the examples on that page, there are far more options.
Scaling server-side
If you have some kind of resize algorithm that is triggered e.g. by certain URL parameters, use this solution and pass the width and height as you desire (maybe with one of the above-mentioned libraries).
As you said that getWidth() and getHeight() return 0, please look at the layout_width and layout_height that you have set for your ImageView. If it's fill_parent, there should be no problem, but if it has to be wrap_content, consider using a local, transparent dummy image with same aspect ratio as your final image (until the "real image" has been set).
Use bitmap factory with justInbounds option. So it will just return the dimension of bitmap without loading bitmap to the ram.
options.inJustDecodeBounds = true;
try to sample the bitmap to the size of the screen (use inJustDecodeBounds in order to get the resolution of the bitmap , and displayMatrics in order to get the resolution of the screen ) .
if there is no transparency , you might also wish to use the RGB 565 format of bitmap decoding instead of 8888 . this will save about 1/4 of the memory being used .
in any case , i suggest reading this tutorial of handling bitmaps .
I was facing the same issue, but I came up with a solution: create a function and call it from "public Object instantiateItem(ViewGroup collection, int position)".
The trick is: the created function will be set to post execute after some miliseconds (to do that you will use the Handler object)
Doing that way the "instantiateItem" will be already done, and view elements will have widths and heights values set.
Bellow is some sample code on how to proceed:
#Override
public Object instantiateItem(ViewGroup container, int position)
{
View view = inflater.inflate(R.layout.pager_list_item,container,false);
TextView textView= (TextView) view.findViewById(R.id.pagerTextView);
ImageView imageView = (ImageView)view.findViewById(R.id.pagerImageView);
... //stuff that don't need the width and height
Handler handle = new Handler();
handle.postDelayed(new Runnable()
{
//here the widths and heights values should be already set
//now you can test the view.getWidth() and view.getHeight()
if(view.getWidth()!=0 && view.getHeight()!=0)
{
//now you sare safe to call the android algorithm for scaling large bitmaps
}
}, 100); //it will wait 100 miliseconds to execute
return view;
}
100 miliseconds is an arbitrary number, maybe too much!!
I recommend you to use a loop or recursive function to guarantee that the block inside the "if" statement will be executed, that way you can use even smaller miliseconds.
In my project I used a recursive function that called itself until the "if" block is executed, and I set to 5 miliseconds.
It should work without a loop or recursive methods, just by setting an arbitrary number like 100 and testing later, but you can't assure that the app will behave the same for all devices, for one can be fast and for other may take a little longer for view to be ready, you never know.
I would like to obtain ImageView dimensions during bindView method.
Unfortunately during bindView measured height and width are 0.
I need those dimensions to request specific image width and height from web.
Before your AdapterView starts recycling views, the views would have not been layered-out on the screen yet, so they will be width and height of zero, because it really is zero.
After your bindView starts to receive recycled views, they will have width and height, but then it's too late.
On those situations you must find a different way to calculate their size, for example, you can make it be a fixed value in DP for every view and just use that value. Example:
int size = context.getResources().getDimensionPixelSize(R.dimen.img_size);
I want to create a Gallery that displays a very large number of images. To do this I have created an adapter class with an ImageView[3] and I making getView pass out ImageView[position%3]. I am also setting the adapter to populate the ImageView with large bitmaps that are loaded in asynchronously.
Since a fling may scroll faster than the device can load in images, I also have a much longer array of thumbnails (which I will also be eventually using for a thumbnail Gallery). If the large bitmap is unavailable, the ImageView is populated with the thumbnail, then repopulated with the large bitmap once it has loaded.
My problem is therefore depressingly trivial: the ImageView is not scaling the way I want it to. The desirable behaviour is for the ImageView to be the same height as its parent, but have the same aspect ratio as its content. However, if I populate the ImageView[] using this code:
private void initializeStaticArrays() {
//initialize envelope arrays
for(int i=0; i<ENVELOPE_ARRAY_SIZE; ++i)
{
mEnvelopeViews[i] = new ImageView(mContext);
//make the views as wide as their content and as tall as their parent
mEnvelopeViews[i].setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
//then make them scale the contained image
mEnvelopeViews[i].setScaleType(ImageView.ScaleType.FIT_CENTER);
mEnvelopeIds[i] = Gallery.INVALID_POSITION;
mBundles[i] = new BitmapFilePathBundle();
}
for(int i=0; i<THUMBNAIL_ARRAY_SIZE; ++i)
{
mThumbnailViews[i] = new ImageView(mContext);
if(mThumbnailParams!=null) mThumbnailViews[i].setLayoutParams(new Gallery.LayoutParams(mThumbnailParams));
mThumbnailViews[i].setScaleType(ImageView.ScaleType.FIT_CENTER);
mThumbnailIds[i] = Gallery.INVALID_POSITION;
}
}
as soon as the Gallery calls getView() it crashes (please note I have called setImageBitmap elsewhere in code and that has definitely happened before getView() is called). I would imagine this is an arithmetic loop, something like trying to make the bitmap stretch to fit the view, which is trying to contain the bitmap, but I can't work out what combination of layout parameters would give the result I want.
So: is there a way to make an ImageView as tall as its parent, with its content zoomed to fit it with the correct aspect ratio, and with the width of the ImageView exactly matching the width of the content?
Edit: the crash was caused by a CastException from LayoutParams to Gallery.LayoutParams. Changing the appropriate line to this:
//make the views as wide as their content and as tall as their parent
mEnvelopeViews[i].setLayoutParams(new Gallery.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
fixes the crash but still does not fix the zoom problem.
Unfortunately, in the absence of any automatic scaling solutions to this problem, I ended up manually sizing the views to specific dimensions, and then using Matrix objects to determine how each image fit those dimensions.