I am using Viewpager2 which displays a different instance of the same fragment in each page.
each fragment has a recyclerview, whose items are made of a (Constraint)layout with a couple edittext and an ImageView
I am calculating height of the element (layout) of a recyclerview in order to have exactly N items displayed. For the width I am simply setting colSpan of recyclerview GridLayoutManager and it works.
When setting the number of rows (of the layout of the single view in OnBindViewHolder of the Adapter), the height is updated, but the picture loaded with Picasso is not refitted to stretch the whole height, until when I change viewpager page and come back... adding/removing elements to the adapter does NOT trigger any update of the image.
The problem is more visible when decreasing the rows number, as the previously fitted image is kept the same, thus not covering the whole ImageView.
placing the image like this, in debug I can see that the if is entered when changing N rows:
String drPath = localData[position].getDrawablePath();
if (!drPath.equals(""))
{
var f = new File(filesDir, drPath);
try
{
Picasso.get()
.load(f)
.fit()
.centerCrop()
.into(holder.itemImage);
}
catch (Exception e)
{
e.printStackTrace();
}
}
I tried:
after changing N rows, notifyDataSetChanged()
in OnBindViewHolder:
public void onBindViewHolder(#NonNull ViewHolder holder, int position)
{
if (holder.heightChanged(parentHeight / numberRows))
{
holder.updateLayoutHeight(parentHeight / numberRows);
}
holder.itemLayout.requestLayout();
holder.itemLayout.invalidate();
holder.itemLayout.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
holder.itemImage.requestLayout();
holder.itemImage.invalidate();
holder.itemImage.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
// ...picasso loading
and debugging I see the height of layout and imageView has been actually recalculated.
Since gLM.setSpanCount(nCols) works, seeing that it calls requestLayout(), I am calling the same on recyclerview after the I called updateHeight on the adapter:
cmdEditVM.getnRowsRecyView().observe(
getViewLifecycleOwner(), (nRows) ->
{
if (!firstUpdRows)
{
cmdObjectsAdapter.updateNRows(nRows);
**recyView.requestLayout();**
}
firstUpdRows = false;
});
I don't have any more ideas, so any suggestion would be appreciated :)
I want to do an image slideshow on Android with only full sceen Images. No thumbnail (like in gallery), no zoom, no pinch. Only left/right slide to move from one picture to the other.
Pcitures are saved on the data folder and there are ~2000 pictures.
I use a full screen ViewPager for displaying the picures. Of course I can't load all the 2000 at once so I only load the needed picture when the user swipes to the corresponding view.
Each time the user swipes to a new viewPager position I call this code
public void onNewViewToDisplay(Integer newPosition, Integer previousPosition) {
File file = functionToGetTheCorrespondingFile(newPosition);
Bitmap bp;
bp = BitmapFactory.decodeFile(file.getPath());
((ImageView)imageAdapter.views.get(newPosition)).setImageBitmap(bp);
if (previousPosition != null && previousPosition != newPosition) {
BitmapDrawable bitmapDrawable =
((BitmapDrawable)((ImageView)imageAdapter.views.get(previousPosition)).getDrawable());
// Set the previous imageView to empty view
((ImageView) imageAdapter.views.get(previousPosition)).setImageResource(0);
// Then free previous bitmap memory
if (bitmapDrawable != null && bitmapDrawable.getBitmap() != null) {
bitmapDrawable.getBitmap().recycle();
}
}
}
So I expect my code to always free the memory used by the previous displayed imageview.But when I look at my memory occupation
Leading to a memory leakage. What am I doing wrong?
Your approach is not efficient, because you create each bitmap every time the user swipes.
You should use LruCache to store the bitmaps so you don't have to re-create one that was already decoded.
OK I found the reason why I add these memory leaks. This was because I had a reference to the views in my PagerAdapter.
You need to have no reference to the views and implement the destroyItem like this
public void destroyItem (ViewGroup container, int position, Object object)
{
((ImageView)object).setImageResource(0);
BitmapDrawable bmd = (BitmapDrawable) ((ImageView) object).getDrawable();
if (bmd != null) bmd.getBitmap().recycle();
View view = (View)object;
((ViewPager) container).removeView(view);
view = null;
}
I generate Picture in my code like this:
try {
SVG svg = SVG.getFromResource(this, R.raw.splatter);
SVGImageView svgImageView = new SVGImageView(this);
svgImageView.setSVG(svg);
svgImageView.setLayoutParams(
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
RelativeLayout layout =
(RelativeLayout) findViewById(R.id.main_layout);
layout.addView(svgImageView);
//svg.renderViewToPicture(Integer.toString(R.id.map), layout.getWidth(), layout.getHeight());
svg.renderToPicture();
} catch (SVGParseException e) {
e.printStackTrace();
}
Now I need set this Picture to my ImageView. How can I do this?
Do you really need to put the svg in res/raw? You can just reference it directly from res/drawable as you would a normal image (ie. imageView.setImageResource(R.drawable.splatter)). This site has a pretty good SVG to Android readable SVG tool.
Bitmap PicturetoDrawable(Picture picture) {
return Bitmap.createBitmap(picture.getWidth(), picture.getHeight(), Bitmap.Config.ARGB_8888);
}
I want to store images in my database. Also I want to check that if the image and title is already in the database. If so, it will not add them to the database. This is my class.
Attractions
public class Attractions extends ListActivity {
DataBaseHandler db = new DataBaseHandler(this);
ArrayList<Contact> imageArry = new ArrayList<Contact>();
List<Contact> contacts;
ContactImageAdapter adapter;
int ctr, loaded;
int [] landmarkImages={R.drawable.oblation,R.drawable.eastwood,R.drawable.ecopark,R.drawable.circle};
String []landmarkDetails = { "Oblation", "Eastwood", "Ecopark", "QC Circle"};
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_attractions);
ctr = db.checkContact(landmarkDetails[loaded]);
// get image from drawable
/**
* CRUD Operations
* */
// Inserting Contacts
Log.d("Insert: ", "Inserting ..");
for(loaded=0; loaded <landmarkDetails.length;loaded++){
Bitmap image = BitmapFactory.decodeResource(getResources(),
landmarkImages[loaded]);
// convert bitmap to byte
ByteArrayOutputStream stream = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte imageInByte[] = stream.toByteArray();
Log.d("Going to load images", "Image "+ loaded);
Log.d("Goind to load objects", "loading");
if(ctr == 0){
Log.d("Nothing Loaded", "Loading Now");
db.addContact(new Contact(landmarkDetails[loaded], imageInByte));}
Log.d(landmarkDetails[loaded], "Loaded!");
image.recycle();
}
loadFromDb();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.attractions, menu);
return true;
}
public void loadFromDb(){
// Reading all contacts from database
contacts = db.getAllContacts();
for (Contact cn : contacts) {
String log = "ID:" + cn.getID() + " Name: " + cn.getName()
+ " ,Image: " + cn.getImage();
// Writing Contacts to log
Log.d("Result: ", log);
//add contacts data in arrayList
imageArry.add(cn);
}
adapter = new ContactImageAdapter(this, R.layout.screen_list,
imageArry);
ListView dataList = (ListView) findViewById(android.R.id.list);
dataList.setAdapter(adapter);
}
public void onPause(){
super.onPause();
}
public void onResume(){
super.onResume();
}
}
It works fine on the emulator, but I tried testing on my S4 and then after 3 tries of going to this class, it forced stop. I tried it with usb debugging and the logcat showed java.lang.outofmemoryerror . The logcat pointed the error in my contactimageadapter.
ContactImageAdapter
public class ContactImageAdapter extends ArrayAdapter<Contact>{
Context context;
int layoutResourceId;
// BcardImage data[] = null;
ArrayList<Contact> data=new ArrayList<Contact>();
public ContactImageAdapter(Context context, int layoutResourceId, ArrayList<Contact> data) {
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
ImageHolder holder = null;
if(row == null)
{
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new ImageHolder();
holder.txtTitle = (TextView)row.findViewById(R.id.txtTitle);
holder.imgIcon = (ImageView)row.findViewById(R.id.imgIcon);
row.setTag(holder);
}
else
{
holder = (ImageHolder)row.getTag();
}
Contact picture = data.get(position);
holder.txtTitle.setText(picture._name);
//convert byte to bitmap take from contact class
byte[] outImage=picture._image;
ByteArrayInputStream imageStream = new ByteArrayInputStream(outImage);
Bitmap theImage = BitmapFactory.decodeStream(imageStream);
holder.imgIcon.setImageBitmap(theImage);
return row;
}
static class ImageHolder
{
ImageView imgIcon;
TextView txtTitle;
}
}
And pointed to this line Bitmap theImage = BitmapFactory.decodeStream(imageStream);
I have little (almost none) knowledge on managing images and storing them. I also enable android:largeHeap but still force closes on multiple tries. I hope someone can help me solving this issue, or at least show me a different way of storing text and images to sqlite db. Many thanks!
You have multiple places where whole image (assuming it is big) keeps in memory:
Contact object has it. All loaded images are in imageArry which is instance level variable.
public class Attractions extends ListActivity {
DataBaseHandler db = new DataBaseHandler(this);
ArrayList<Contact> imageArry = new ArrayList<Contact>();
in ContactImageAdapter.getView method you create another copy of image as BMP in holder object and pass it out of method.
So, at some point you do not have enough memory to keep all of them. Also I sure that decodeStream needs some more memory to perform.
After all it is not predictable when each new holder created in getView will be cleaned by GC.
Usually for such situation when object created as new in some method, then passed back to the calling method, that object will be collected only by Full GC.
So, as "Software Sainath" said, do not store images in database…
and do not keep them in memory either.
P.S. Then provide to the view a link to the external image file. That also will save time to load a view. Image will be in cache and if user at least once got it, it will not pass through the network again.
I guess images there are not frequently change them self. another image of Contact will be another file…
I wrote an answer to the somewhat similar problem some while ago, here is the link that you can check. The problem is in the approach of saving the images into the database, you should not be doing this. Instead, write the images as files on the phone memory and use it further.
Don't store Image to Sqlite Database eventually, you will ran into out of memory error after three or five image saved to database. It's not the best practice, maximum memory allocated for field in a row in sqlite is less than 3mb, be aware of this.
Instead of saving Images to database, Keep the images inside your app folder, save the path to the Database.
Your are loading your image as it is to your Image adapter. Let's say your image is 1280x720 resolution and 2mb in size, it will take the same space in your memory Heap.
You can either scaledown your image and load it as bitmap to your Adapter ImageView like this.
Before loading your image as Bitmap get it height and width.
//Code read the image and give you image height and width.it won't load your bitmap.
BitmapFactory.Options option = new BitmapFactory.Options();
option.inJustDecodeBounds = true;
BitmapFactory.decodeFile(your_image_url,option);
int image_original_width = option.outHeight;
int image_original_height = option.outWidth;
Now to scale down your Image you have to know the ImageView width and height. This is because we are going to scale down the image matching the imageview with pixel perfection.
int image_view_width = image_view.getWidht();
int image_view_height = image_view.getHeight();
int new_width;
int new_height;
float scaled_width;
if(image_original_width>image_view_width)
{ //if the image_view width is lesser than original_image_width ,you have to scaled down the image.
scale_value =(float)image_original_width/(float)image_view_width;
new_width = image_original_width/scaled_value;
new_height = image_orignal_height/scale_value
}
else
{
// use the image_view width and height as sacling value widht and height;
new_width = image_view_width;
new_height = image_view_height;
}
Now Scale Down your bitmap and load it like this.
// this will load a bitmap with 1/4 the size of the original one.
// this to lower your bitmap memory occupation in heap.
BitmapFactory.Options option = new BitmapFactory.Options();
option.inSampleSize = 4;
Bitmap current_bitmap = BitmapFactory.decodeFile(image_url,option);
Bitmap scaled_bitmap = Bitmap.createScaledBitmap(current_bitmap,new_width,new_height,true);
holder.imgIcon.setImageBitmap(scaled_bitmap);
//release memory occupied by current_bitmap in heap, as we are no longer using it.
current_bitmap.recycle();
If you want to understand a little more about Bitmap and memory view this link.
If you don't want to handle rescaling bitmap by yourself. you can use Glide or Picasso library which does the same.
I have written an article about using Picasso to load image in listview, which will help you to start, if you are looking to use picasso.
http://codex2android.blogspot.in/2015/11/picasso-android-example.html
Please make sure to use the quick garbage collection eligible reference type while loading the images from the network
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import android.graphics.Bitmap;
public class MemoryCache {
private Map<String, SoftReference<Bitmap>> cache=Collections.synchronizedMap(new HashMap<String, SoftReference<Bitmap>>());
public Bitmap get(String id){
if(!cache.containsKey(id))
return null;
SoftReference<Bitmap> ref=cache.get(id);
return ref.get();
}
public void put(String id, Bitmap bitmap){
cache.put(id, new SoftReference<Bitmap>(bitmap));
}
public void clear() {
cache.clear();
}
}
Don't store Image to Sqlite Database. It's not the best practice.
Instead of saving Images to database, Keep the images in a storage, but if you want to keep them private then keep them inside your app folder and save the path to the Database.
Use one of the well known libraries like http://square.github.io/picasso/ or https://github.com/bumptech/glide, they offer great help with memory issues and also some cool transition effects.
I recommend using Glide because it works very well on device with low memory restrictions
Hi guys i am stuck with image caching. i need to populate Linear Layout with images from web.
I was searching examples how to cache images in android and found these examples:
http://developer.android.com/resources/samples/XmlAdapters/src/com/example/android/xmladapters/ImageDownloader.html
Lazy load of images in ListView
I tried it implement for my code.
String picPath = "https://www.google.lt/logos/classicplus.png";
try {
/*
View v = null;//new View(this);
ImageLoader loader = new ImageLoader(this);
ImageView im = (ImageView)this.findViewById(R.id.image);
loader.DisplayImage(picPath, im);
addImage( im);*/
LayoutInflater inflater =(LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View convertView = inflater.inflate(R.layout.item, null);
ImageView image = (ImageView)convertView.findViewById(R.id.image);
ImageDownloader down = new ImageDownloader();
down.download(picPath, image);
addImage(image);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
public void addImage( View view){
LinearLayout pubLayout = (LinearLayout)findViewById( R.id.scrollerLinearlayout);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
//setting layout_margin
params.setMargins(15, 30, 15, 30);
pubLayout.addView(view,params);
}
However this didn't worked. I believe because i add view to linear layout and when image is downloaded it can't repaint it ?
There are examples how to do image caching using ListView. But what to do if i am not using List View.
I would write my own caching but how to make callback to image downloaded as i want first add image to layout and on callback update it ?
Thanks.
you have to call invalidate on parent view
pubLayout.getParent().invalidate();