Viewpager with large bitmaps - android

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.

Related

Scaled images with volley

I am trying to stretch images that I load with volley. XML isn't much help, while it can shrink them it doesn't help enlarging them. My exploration of the topic led me to the conclusion that this can be achieved only programmatically.
What is my idea ?
To extend the volley library and overriding one of the methods, resizing the bitmap right after download and before displaying it.
I used this code to resize images that were already on the phone, but this doesn't help me much with random images from the internet.
Point outSize=new Point();
float destWidth=1;
float destHeight=1;
#SuppressWarnings("deprecation")
#TargetApi(13)
private Bitmap scaleBitmap(int mFile){
Display display = getActivity().getWindowManager().getDefaultDisplay();
if (android.os.Build.VERSION.SDK_INT >= 13){
display.getSize(outSize);
destWidth = outSize.x;
destHeight = outSize.y;
}else{
destWidth=display.getWidth();
destWidth=display.getHeight();
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap orig = BitmapFactory.decodeResource(getResources(), mFile);
float srcWidth = orig.getWidth();
float srcHeight = orig.getHeight();
float inSampleSize = 1;
if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
inSampleSize=(destHeight/2)/srcHeight;
}else{
inSampleSize=(destHeight/4)/srcHeight;}
options = new BitmapFactory.Options();
Bitmap resized=Bitmap.createScaledBitmap(orig, (int)Math.round(orig.getWidth()*inSampleSize), (int)Math.round(orig.getHeight()*inSampleSize), true);
destWidth=1;
destHeight=1;
return resized;
}
Basically I want to assign to orig the image that is downloaded, then resize it and then display it.
My question is: What class do I extend ? I took a look there but since I am inexperienced I couldn't figure out what exactly to look for. Is it ImageLoader ? And more specifically: should i Override the getBitmap() method and add an edited version of the code for scaling ?
Disclaimer: I am very inexperienced and I would accept other ideas too.
I solved it a few days ago. Here is how the regular ImageView handles resizing of images: If you are using Match_parent as width and wrap_content as height(I tested with both as match_parent and the results were the same) it will NOT scale the image IF the Height of your picture is lower than the width (which is quite typical) . Meaning that image view scales based on the smaller side. This meant that there are 2 solutions:
Do what I wanted to do originally which involves writing a new class that extends imageview/networkimageview and then programmatically adding the code that i listed to enlarge the picture.
Or simply specify the desired Height of the view programmatically and imageview will then simply scale the image to fit the height of the new View. This options invalidates the wrap_content for Width and simply puts in as much as you need.
Here is the code I used:
Display display = getActivity().getWindowManager().getDefaultDisplay();
Point outSize=new Point();
int destWidth;
if (android.os.Build.VERSION.SDK_INT >= 13){
display.getSize(outSize);
destWidth = outSize.x;
}else{
destWidth=display.getWidth();
}
img.setLayoutParams(new RelativeLayout.LayoutParams( LayoutParams.MATCH_PARENT,destWidth*5625/10000));
In my case I needed to fit a 640x360 image so I used a ratio of 0,5625. This method will work if you do not know the picture's parameters BUT you have to get the parameters beforehand and put them in place, so I guess it might be more useful to use the first strategy, but since I solved my problem I don't have an example for the first method.
I think the issue that you're seeing is caused by the default behavior of how the underlying code for NetworkImageView works.
Have a look at the code for ImageRequest which is used by NetworkImageView . There is a function there called getResizedDimension which says something like "Scales one side of a rectangle to fit aspect ratio" . This may have caused your issue since the image(s) that you're downloading may not have the perfect aspect ratio computed every time for a specific device.
Are you displaying a single image, or images in a grid? If it is just a single image, there might be a work around. I haven't seen your full code, on where you set the image or image url, so I am going to assume the flow here:
1.) Use the regular ImageView.
2.) Use the regular Volley request pointing to the image's URL
3.) When you receive the response (parseNetworkResponse), decode (the array of bytes) it yourself, using the method you mentioned above that works for you. Some tips can also be found here on how to do that: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
4.) After decoding, programmatically set the ImageView's bitmap with your decoded result
If this is a grid type thing, then maybe you need to make your own version of NetworkImageView and its underlying support classes (sub-class).
Suggestion:
Have a look at Picasso as well, IF you have the freedom to choose the library. Looks very simple to use for your stated needs.

Android- Image Rendering

Is there a way the this problem can be fixed? I tried invalidate() but, it still displays the same problem. What happens is that, after opening the page/ the Activity, the images behaves like the one in Figure A. It only renders to my desired layout (Figure B) after scrolling it back and forth.
What I'm trying to do is set the width and heigth of the image during runtime. So this is also in relation to my previous questions : Images in my HorizontalListView changes it size randomly and ImageView dynamic width and height which received a very little help.
Any tips regarding this matter, please?
EDIT: btw, my classes are:
MyCustomAdapter (extends baseadapter, this calls the displayimage() from ImageLoader ),
MyActivity and
ImageLoader (this is where my image url are loaded, decoded, displayed asynchronously)
Im also confused as to where i will set the height and width of the imageView. For now, i set it at ImageLoader. It was okay. but i dont know if i did the right thing.
If you want to set the width and height manually at runtime, grab a reference to the ImageView's LayoutParams AFTER the View has been measured by the layout system. If you do this too early in the rendering phase, your view's width and height as well as its parent view and so on will be 0.
I have some code in an open source library that might help you. The process is two parts:
Set up an OnPreDrawListener attached to the ViewTreeObserver for your control. My example does this inside of a custom control, but you can do this in your activity as well.
Inside the onPreDraw method, your image and it's parent will now have their width and height values assigned to them. You can make your calculations and then set your width and/or height manually to the LayoutParams object of your view (don't forget to set it back).
Check out this example where I'm applying an aspect ratio to a custom ImageView just before it's rendered to the screen. I don't know if this exactly fits your use case, but this will demonstrate how to add an OnPreDrawListener to a ViewTreeObserver, removing it when you're done, and applying dynamic sizing to a View at runtime
https://github.com/aguynamedrich/beacon-utils/blob/master/Library/src/us/beacondigital/utils/RemoteImageView.java#L78
Here's a modified version that removes my particular resizing logic. It also grabs the ViewTreeObserver from the imageView, which is a more likely scenario if you're not implementing a custom control and you only want to do this in the Activity
private void initResizeLogic() {
final ViewTreeObserver obs = imageView.getViewTreeObserver();
obs.addOnPreDrawListener(new OnPreDrawListener() {
public boolean onPreDraw() {
dynamicResize();
obs.removeOnPreDrawListener(this);
return true;
}
});
}
protected void dynamicResize() {
ViewGroup.LayoutParams lp = imageView.getLayoutParams();
// resize logic goes here...
// imageView.getWidth() and imageView.getHeight() now return
// their initial layout values
lp.height = someCalculatedHeight;
lp.width = someCalculatedWidth;
imageView.setLayoutParams(lp);
}
}

Getting View size inside a BaseAdapter.getView()

I have an activity wich contains a ListView, the list is a thumbnail on the left and some text on the right, pretty much like a contact list. The thing is, the thumbnail is loading from an absolute path from the sdcard, and each image size is about 5mb. For avoiding running out of memory I created the classes in this tutorial which loads only a scaled version of the images and doesn't take a lot of memory.
Well, what's the problem? Since, I have to pass the width and the height (in px) that I want the image to be resized, but for differente devices I would have a problem setting absolute values. So, I just tried passing the view through args, and just manipulate it in my class to get the width and height, nice eh? Ok, but it didn't work, since the thumbnail hasn't been draw yeat (i guesss), I can't take his size values.
How can I get the size inside the method getView() from my lisview adapter?
/** short passage of getView() method */
ImageView image = (ImageView) view.findViewById(R.id.image_thumb);
Bitmap bMap = BitmapAdjusted.decodeSampleBitmap("/sdcardpath/",
image); // "image" is the view passed by args
image.setImageBitmap(bMap);
Here's the problem:
public static decodeSimpleBitmap(String path, View view){
/** unworth previous code */
int width = view.getWidth(); // always give zero, getMeasuredWidth() too
int height = view.getHeight(); // always give zero
// options is BitmapFactory.Options class, declared before that do the magic
options.inSampleSize = calculateInSampleSize(options, width, height);
}
Thanks!

Creating ImageViews for a Gallery that fit the Gallery's height in Android

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.

Moving views inside a layout/view in Android

I have more than one question, but I'll start with the more important and problematic one:
I have a FrameLayout with a ImageView inside it. I need to get the size of the "usable area" of the screen my activity is ocupping, so I set the onSizeChanged on my View (I extended the ImageView class). Everything worked fine here. Now I have a 1000x1000 image that I want to show on the screen, but without scaling. I want it to be clipped, really. If I set the ImageView dimensions using viewObject.setLayoutParams(new Gallery.LayoutParams(1000, 1000)); I get the image being showed correctly, but then the onSizeChanged event returns me always the "1000 x 1000" custom size rather than the real screen size value.
Any ideas of how can I show an image with its real size (no scale!) and still get the view to report the screen available space? I can change the layout as needed as well, of course.
. Amplexos.
Are you asking to get the dimensions of the ImageView? If so then you can get that using getLocalVisibleRect. Here's roughly how it's done:
ImageView yourImageView;
public void onCreate(...){
setContentView(...)
yourImageView = (ImageView) findViewById(...);
(...)
}
getImageViewSize(){
Rect imageViewSize = new Rect();
yourImageView.getLocalVisibleRect(imageViewSize);
// imageViewSize now has all the values you need
Log.d("Your log tag", "ImageView width = " + (imageViewSize.right -
imageViewSize.left));
}
There is however a catch. You have to make sure that you don't try to get the size of the view until after view is finished being laid out on the screen. In other words, if you try to get its size in onCreate, its size will be 0. You have to get it afterwards, for example at the same time as you resize your image, assuming that's done with a button. (If you're using a SurfaceHolder you can also call it during the surfaceCreated callback, but I doubt you're using one of those...)

Categories

Resources