I have a ListView with a custom adapter. Every row has a Pokémon sprite that I download to a bitmap, the sprites are always 96 x 96. I manually downloaded a dummy image and put it in my drawables folder, and it renders fine:
How it should be
But when I actually download the images programmatically, it gives me this result:
How it is
Why are then suddenly so small? When I sysout the height and width of the downloaded bitmaps, it says 96 x 96, but it clearly doesn't render as 96 x 96.
Here's the code of the task that downloads the image
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
#Override
protected Bitmap doInBackground(String... urls) {
String urldisplay = urls[0];
Bitmap bm = null;
try {
InputStream in = new java.net.URL(urldisplay).openStream();
bm = BitmapFactory.decodeStream(in);
} catch (Exception e) {
e.printStackTrace();
}
return bm;
}
}
How I call the task
try {
Bitmap bm = new DownloadImageTask().execute(dataModel.getSpriteUrl()).get();
viewHolder.sprite.setImageBitmap(bm);
} catch (Exception e) {
e.printStackTrace();
}
Pokemon row XML
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="#+id/img_sprite"
android:layout_width="96dp"
android:layout_height="96dp"
android:scaleType="fitCenter" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="left|center_vertical"
android:orientation="vertical">
<TextView
android:id="#+id/text_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#android:color/black"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="#+id/text_cp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp" />
<TextView
android:id="#+id/text_gendershiny"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="end"
android:orientation="vertical">
<Button
android:id="#+id/btn_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:backgroundTint="#color/colorPrimary"
android:text="DELETE"
android:textColor="#ffffff"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
You might want to take a look at this website. I gives some helpful tips of how to use the scaleType attribute with the ImageView:
https://robots.thoughtbot.com/android-imageview-scaletype-a-visual-guide
Please see the edit at the bottom of this answer.
Now to the issue with using get() method of an AsyncTask
Use an interface to let you know when the AsyncTask is complete:
public interface BitmapLoaderListener {
void onCompletedBitmapLoaded(Bitmap bm);
}
Your AsyncTask could look like this:
public class BitmapLoader extends AsyncTask<String, Integer, Bitmap> {
private static final String TAG = BitmapLoader.class.getSimpleName();
private BitmapLoaderListener mListener;
private String imageUrl = "";
public BitmapLoader(String imageUrl, BitmapLoaderListener listener){
this.imageUrl = imageUrl;
this.mListener = listener;
this.selectedSource = source;
}
#Override
protected Bitmap doInBackground(String... params) {
Bitmap bm = null;
try{
// Your code here !!!
}
catch (Exception ex){
Log.e(TAG, ex.getMessage());
}
return bm;
}
protected void onPostExecute(Bitmap bm) {
mListener.onCompletedBitmapLoaded(bm);
}
}
Call the AsyncTask from your RecyclerView from the onBindViewHolder:
#Override
public void onBindViewHolder(final MyViewHolder holder, final int position){
MyData data = mData.get(position);
// Add whatever code you need here
BitmapLoaderListener listener = new BitmapLoaderListener() {
#Override
public void onCompletedBitmapLoaded(Bitmap bm) {
if (bm == null) return;
holder.myImageView.setImageBitmap(bm);
}
};
BitmapLoader loader = new BitmapLoader(imageUrl, listener);
loader.execute();
}
You could implement a cache type system where you persist the images to the device storage and load them from the internet only when needed. I do this with a lot of my apps. This is possible, but it requires experience with dealing with threads and resources... As Angelina pointed out you can always use Glide or Picasso libraries. Although I very rarely use third party libraries in this case you might want to consider it--they are well designed and well maintained libraries.
Edit:
Using a scaling method createScaledBitmap()for every downloaded image seems a bit heavy weight.
You might want to make this simple change to your layout file:
<ImageView
android:id="#+id/img_sprite"
android:layout_width="96dp"
android:layout_height="96dp"
android:scaleType="fitCenter"/>
There are many ways to achieve the result you want by making some changes to your layout file. I just pick the easiest with regard to the image size you are downloading (96x96).
This example takes the image and forces it into an ImageView 96dpx96dp so that the image is centered and scaled to fit the view bounds maintaining the original aspect ratio.
This is much easier to maintain and modify as well as much more light weight then using createScaledBitmap() method for every image--rather it needs it or not, ouch!
Using AsyncTask to download an image is not the common thing. Usually this is done with a library called Glide. It downloads the image in background thread and caches it. You can also set ScaleType of the image there.
Related
As the title suggest, I am having a lot of issues maintaining the quality and aspect ratio of my images on my android application.
Basically I have an image view and I am trying to get images to fit the image view and maintain it's image quality. I cant use fitxy because that will stretch the image to fill the entire imageview, but I only desire the image to automatically scale it's self and show as crisp as possible like your phone gallery.
In my code, I use fitCenter as the scale type and the size of the image seems alright, however the image is terrible distorted. This is the code.
Ive also included a snapshot of the android and iphone version to show how the exact same image is showing differently on the same image.
<?xml version="1.0" encoding="utf-8"?>
<!-- This controls layout for the slider in Gallery -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="match_parent">
<ImageView
android:id="#+id/pagerimage"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_gravity="center_horizontal"
/>
</RelativeLayout>
</LinearLayout>
iPhone
Android
public void handleGalleryImages() {
try {
for (int j = 0; j < imagesNaturalItemlist.size(); j++) {
Log.e("ImagePATH>>>>>", imagesNaturalItemlist.get(j).getImage());
String imageid = imagesNaturalItemlist.get(j).getUniqueId();
downgalleryimage = new DownloadImage(imagesNaturalItemlist.get(j).getImage(), imageid);
downgallerylist.add(downgalleryimage);
beachimages.add(imagesNaturalItemlist.get(j).getImage());
}
} catch (NullPointerException e) {
e.printStackTrace();
}
adapter
public Object instantiateItem(ViewGroup container, int position) {
View itemView = LayoutInflater.from(context).inflate(R.layout.sliding_pager_row, container, false);
ImageView imageView = (ImageView) itemView.findViewById(R.id.pagerimage);
if (images.get(position) != null && !images.get(position).equals("")) {
//Picasso.with(context).load(images.get(position)).into(imageView);
Glide.with(context)
.load(images.get(position))
.apply(new RequestOptions().diskCacheStrategy(DiskCacheStrategy.ALL).override(600, 250))
.into(imageView);
}
container.addView(itemView);
return itemView;
}
I am trying to download an image from the network and display in the ImageView with Glide using scaleType="centerInside" option.
For some reason, the image, when downloaded from the network, looks much smaller on the screen than when the same image is put into the ImageView from resources.
Example:
Both images can be found here. I would argue that even those images that have been set from resources look smaller than they could actually be when compared to what I see on my laptop. I understand that there is something related to the screen density in play, but how can I make these images be of "user-friendly size", e.g., a bit larger?
Even a different image of 600x250 px size is ridiculously small on the phone (with ImageView's layout_height and layout_width set to "wrap_content").
Code from the Activity:
public class DisplayImagesActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.display_image_activity);
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
setTitle("Hello StackOverflow!");
ImageView top_left = (ImageView) findViewById(R.id.top_left);
ImageView top_right = (ImageView) findViewById(R.id.top_right);
ImageView bottom_left = (ImageView) findViewById(R.id.bottom_left);
ImageView bottom_right = (ImageView) findViewById(R.id.bottom_right);
String[] urls = new String[] {
"http://imgur.com/6jMOdg0.png",
"http://imgur.com/AhIziYr.png"
};
top_left.setImageResource(R.drawable.top_left);
top_right.setImageResource(R.drawable.top_right);
Glide.with(this)
.load(urls[0])
.signature(new StringSignature(new Date().toString()))
.into(bottom_left);
Glide.with(this)
.load(urls[1])
.signature(new StringSignature(new Date().toString()))
.into(bottom_right);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
this.finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
display_image_activity.xml file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
style="#style/match_parent"
android:orientation="vertical">
<include layout="#layout/_toolbar" />
<ScrollView
style="#style/match_parent">
<RelativeLayout
style="#style/match_parent"
android:padding="16dp">
<TextView
style="#style/wrap_content"
android:id="#+id/text_resources"
android:layout_marginBottom="10dp"
android:text="From Resources"/>
<ImageView
android:id="#+id/top_left"
android:background="#color/Linen"
android:layout_width="150dp"
android:layout_height="120dp"
android:layout_marginBottom="20dp"
android:layout_below="#id/text_resources"
android:scaleType="centerInside"/>
<ImageView
android:id="#+id/top_right"
android:background="#color/Linen"
android:layout_width="150dp"
android:layout_height="120dp"
android:layout_toRightOf="#id/top_left"
android:layout_toEndOf="#id/top_left"
android:layout_below="#id/text_resources"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:scaleType="centerInside"/>
<TextView
style="#style/wrap_content"
android:id="#+id/text_network"
android:layout_below="#id/top_left"
android:layout_marginBottom="10dp"
android:text="From Network"/>
<ImageView
android:id="#+id/bottom_left"
android:background="#color/Linen"
android:layout_width="150dp"
android:layout_height="120dp"
android:layout_below="#id/text_network"
android:scaleType="centerInside" />
<ImageView
android:id="#+id/bottom_right"
android:background="#color/Linen"
android:layout_width="150dp"
android:layout_height="120dp"
android:layout_toRightOf="#id/bottom_left"
android:layout_toEndOf="#id/bottom_left"
android:layout_below="#id/text_network"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:scaleType="centerInside" />
</RelativeLayout>
</ScrollView>
</LinearLayout>
I faced the same problem. Glide tries to interpret what my app needs and transforms the images accordingly, resulting in too small images in some places. In my case the ImageViews use adjustViewBounds="true" and MaxWdth/Height leading to problems
While I am not anything close to being a Glide Expert, I found a quick fix working for me.
I simply added a .dontTransform() mehod call, which in my case is OK since I use thumbnails that already have been pre-scaled.
GlideApp.with(context).load(fireStorage).dontTransform().into(imgView);
(Using a Placeholder would probably also have helped, but again, for me this was the easiest way)
Your images cannot be bigger than what you defined:
android:layout_width="150dp"
android:layout_height="120dp"
Try
android:layout_width="match_parent"
android:layout_height="wrap_content"
Your ImageViews can be that fixed size if you want although they should be flexible with match_parent/wrap_content.
I don't know what Glide does for sure but it looks like the resolution of the images from the network is smaller than the ones from resources. The android:scaleType="centerInside" gives you the behaviour that the image will be SHRUNK until both dimensions of the image fit in the ImageView and it's aspect ratio is maintained. If you want the images to expand to fit the ImageView you probably want android:scaleType="fitCenter" instead. You might also want android:adjustViewBounds to be true/false depending on how you want it to behave if you decide to make the dimensions flexible.
The documentation for scaleType is useful here:
https://developer.android.com/reference/android/widget/ImageView.ScaleType.html
https://developer.android.com/reference/android/graphics/Matrix.ScaleToFit.html#CENTER
This code saved my time. It works for me!
//Get actual width and height of image
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
Bitmap bitmap = null;
try {
URL url = new URL(imgUrl);
bitmap = BitmapFactory.decodeStream((InputStream)url.getContent());
} catch (IOException e) {
Timber.e("Image Loading Error %s", e.getLocalizedMessage());
}
if (bitmap != null) {
final float scale = resources.getDisplayMetrics().density;
final int dpWidthInPx = (int) (bitmap.getWidth() * scale + 0.5f);
final int dpHeightInPx = (int) (bitmap.getHeight() * scale + 0.5f);
//Set result width and height to image
GlideApp.with(imgAns.getContext())
.load(imgUrl)
.override(dpWidthInPx, dpHeightInPx)
.into(imgAns);
}
In my app I have an imageview on screen "A" and a webview on screen "B".
On screen "A" I start an sync task in order to download and show a png file into the imageview.
On screen "B" I load some HTML info. in which the link to the same png on screen "A" is included.
The problem is that the same image looks different on both screens. When loaded in the imageview, the png looks way smaller than when it is loaded in the webview.
The height and the width of the imageview are set to wrap_content. There is no additional scaling or something similar.
Why is this happening?
---EDIT---
screen "A" layout
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/background_repeat">
<ImageView
android:id="#+id/logo_image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_gravity="center_horizontal"/>
</RelativeLayout>
screen "B" layout
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<WebView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/about_us_web_view"
/>
</RelativeLayout>
screen "A" asyncTask
private class DownloadLogoTask extends AsyncTask<ImageView, String, Bitmap> {
private String urlString;
public DownloadLogoTask(String urlString) {
this.urlString = urlString;
}
#Override
protected Bitmap doInBackground(ImageView... imageViews) {
try {
URL url = new URL(urlString);
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
InputStream is = urlConnection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(is);
if(bitmap != null) {
return bitmap;
}
} catch (Exception e) {
}
return null;
}
#Override
protected void onPostExecute(Bitmap result) {
if(result != null) {
logoImageView.setImageBitmap(result);
}
}
}
screen "B" usage of the webView
aboutUsWebView.loadData(descriptionStr, "text/html", "UTF-8");
Loading an image that is larger than the width of a Galaxy Tab 2 P5100 (running 4.1.2) into an ImageView adds some sort of top/bottom padding to the loaded image.
Here's a screenshot with Show layout boundaries turned on:
Here's how it should look (from a Nexus 10 running 4.4.2):
The code I use (for both examples above) is
public class ImageBugActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bug);
// This bug is still reproducible if I use the
// Universal-Image-Loader library or if I change the dimensions of
// the image to a different width
loadImage("http://placehold.it/1600x1000", (ImageView)findViewById(R.id.image));
}
private void loadImage(final String url, final ImageView view) {
new Thread(new Runnable() {
#Override
public void run() {
try {
final Bitmap bitmap = BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream());
runOnUiThread(new Runnable() {
#Override
public void run() {
view.setImageBitmap(bitmap);
}
});
} catch (Exception e) {
Log.e("loadImage", e.getMessage(), e);
}
}
}).start();
}
}
And the layout file is
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="On a Galaxy Tab 2 the image below it is pushed to the center of the remaining space." />
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top" />
</LinearLayout>
Does this seem to be an Android/Samsung bug or am I making a dumb mistake?
Setting the android:scaleType of the ImageView to "fitStart" should do the trick.
You should use "match_parent" for layout_height of the ImageView.
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top" />
I know there's already tons of similar questions, but I checked over twenty answers and none of them solves my problem.
First, I have an adapter extending SimpleCursorAdapter. I use custom view in it, including an ImageView. The layout is:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="fill_parent"
android:layout_height="#dimen/listitem_height" android:gravity="center_vertical">
<ImageView android:id="#+id/row_image" android:layout_width="#dimen/listitem_height"
android:layout_height="fill_parent" android:paddingRight="#dimen/spaceLarge"
android:layout_centerVertical="true" android:layout_marginLeft="#dimen/spaceMedium"
android:src="#drawable/unknown_album" />
<LinearLayout android:layout_width="fill_parent"
android:layout_height="fill_parent" android:orientation="vertical"
android:gravity="center_vertical|left">
<TextView android:id="#+id/row_text" android:layout_width="fill_parent"
android:textAppearance="?android:attr/textAppearanceMedium"
android:gravity="center_vertical|left" android:layout_height="wrap_content" />
<TextView android:id="#+id/title" android:layout_width="fill_parent"
android:textAppearance="?android:attr/textAppearanceSmall"
android:gravity="center_vertical|left" android:layout_height="wrap_content"
android:textColor="#95D0B4" />
</LinearLayout>
Then, the ImageView needs some time to load image, so if I do it in main thread, the scrolling will become horrible. Thus I want to use an AsyncTask to load images, but now I face the problem of ListView, which reuses views when scrolling.
That is, code below does not work:
class ImageSetter extends AsyncTask<String, Void, Bitmap> {
private ImageView iv;
ImageSetter(ImageView iv) {
this.iv = iv;
}
#Override
protected Bitmap doInBackground(String... params) {
String path = (String) params[0];
try {
return BitmapFactory.decodeFile(path);
} catch (OutOfMemoryError e) {
Log.w(Utils.TAG, "", e);
return null;
}
}
#Override
protected void onPostExecute(Bitmap result) {
iv.setImageBitmap(result);
super.onPostExecute(result);
}
}
which I call execute() in bindView() in the adapter. The ImageView changes while scrolling, and the result Bitmap is set into wrong position.
Does anyone how to resolve this problem? Thank you very much!
P.S. I also meet serious OutOfMemory problem when scrolling the list fast. I quite have no idea about how to avoid the bitmap memory leak problem, since I can't recycle them as long as they are in the list.
Adapter reuses Views inside ListView. As a result you are never sure if
private ImageView iv;
contains ImageView object you wanted to set. You should use id for finding proper cell in the list.
Reconsider using thumbnails or decrease size of images. You can read more here:
Lazy load of images in ListView