3

Gallery Activity:

==================
    TextView (tv)
------------------
 | tv | tv | tv |
------------------
    GalleryFragment: dynamically created; replaces FrameLayout
        FrameLayout: W&H: match_parent


    imageView: W&H are match_parent; scaleType: fitCenter; 
        layout_below: the tv's above;
        layout_above: the TextView below;
            result: imageView fits snuggly between.
        Parent: RelativeLayout W&H: match_parent        


------------------
    TextView
==================

Gallery Activity description: This activity shows images, one at a time, belonging to a particular collection. User clicks image: new fragment shows next image.

I have been having several days worth of problems figuring out how to:

  • A. get the size of the imageView so that I can properly scale the bitmap to fit.
  • B. Now I've got an infinite loop happening. And I think I know why, I mean logically the moment I stuff a bitmap into the imageView that probably fires off the onPreDraw() again and then my code is off to the races. ...but I don't know how to fix it.
  • C. I thought synchronization might be the answer but I have never used it before and I don't think I'm using it correctly.
  • D. I thought maybe putting an if(workerThread == null) check might work ... but it only slowed it down for a brief moment.

At this point I am lost. I don't know how to obtain the dimensions of an ImageView tucked inside a dynamically added fragment while using those dimensions to down-size a bitmap and then after resizing loading that bitmap into the fragment's ImageView.

If you would like any clarifications please ask.

Included below is the code for GalleryFragment's onCreateView and BitmapWorkerTask. Then below that is a paste of my filtered logcat.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    // inflate view
    View view = inflater.inflate(R.layout.frag_gallery_image, container, false);

    // get view handles
    imageView = (ImageView)view.findViewById(R.id.gallery_image);   

    // TESTING
    ViewTreeObserver vto = imageView.getViewTreeObserver();
    vto.addOnPreDrawListener(new OnPreDrawListener() {

        @Override
        public boolean onPreDraw() {
            // TESTING
            viewWidth = imageView.getMeasuredWidth();
            viewHeight = imageView.getMeasuredHeight();

            Log.d(TAG, SCOPE +"onPreDraw viewWidth: " +viewWidth +", viewHeight: " +viewHeight);

            // TESTING: Added because "synchronization" attempt didn't work 
            if(bmwt == null){
                loadImage(viewWidth, viewHeight);                   
            }

            return true;
        }
    });

    // set view actions     
    imageView.setOnClickListener(GalleryFragment.this);     

    return view;
}

/**
 * A synchronized method for loading bitmaps in background thread.
 * Synchronization: http://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html
 * ...something isn't working as intended...
 */
private synchronized void loadImage(int viewWidth, int viewHeight){
    // TESTING
    Log.d(TAG, SCOPE +"loadImage viewWidth: " +viewWidth +", viewHeight: " +viewHeight);

    bmwt = new BitmapWorkerTask(imageView, viewWidth, viewHeight);
    bmwt.execute(imageUri);
}


/**
 * BitmapWorkerTask is a subclass of Asynctask for the purpose of loading
 * images off of the UI thread. 
 */
private class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap>{
    private String uri;
    private int imageViewWidth;
    private int imageViewHeight;

    // Constructor.
    public BitmapWorkerTask(ImageView imageView, int width, int height){
        imageViewWidth = width;
        imageViewHeight = height;
    }

    // Decode image in background.
    @Override
    protected Bitmap doInBackground(String... params) {
        uri = params[0];
        final Bitmap bitmap =  ImageUtils.decodeSampledBitmapFromUri(uri, imageViewWidth, imageViewHeight);

        return bitmap;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null; // use some default bitmap
        }
        if (bitmap != null) {
            Log.d(TAG, SCOPE +"bitmap kilobyte count: "+ bitmap.getByteCount() / 1024);

            imageView.setImageBitmap(bitmap);

            // make BitmapWorkerTask reference null again.
            bmwt = null;
        }// else do nothing.
    }       
}

Logcat. Note "bitmap kilobyte count" is called in asyncTask's onPostExecute. ...and is kind of where I had hoped the logging would stop.

06-13 01:50:41.442: D/ROSS(12982): GalleryFragment: imageUri: /mnt/sdcard/So so so beautiful.jpg
06-13 01:50:41.522: D/ROSS(12982): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 01:50:41.522: D/ROSS(12982): GalleryFragment: loadImage viewWidth: 480, viewHeight: 692
06-13 01:50:41.632: D/ROSS(12982): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 01:50:41.827: D/ROSS(12982): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 01:50:43.053: D/ROSS(12982): GalleryFragment: bitmap kilobyte count: 1183
06-13 01:50:43.142: D/ROSS(12982): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 01:50:43.142: D/ROSS(12982): GalleryFragment: loadImage viewWidth: 480, viewHeight: 692
06-13 01:50:43.632: D/ROSS(12982): GalleryFragment: bitmap kilobyte count: 1183
06-13 01:50:43.713: D/ROSS(12982): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 01:50:43.713: D/ROSS(12982): GalleryFragment: loadImage viewWidth: 480, viewHeight: 692
06-13 01:50:44.023: D/ROSS(12982): GalleryFragment: bitmap kilobyte count: 1183
06-13 01:50:44.101: D/ROSS(12982): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 01:50:44.101: D/ROSS(12982): GalleryFragment: loadImage viewWidth: 480, viewHeight: 692
06-13 01:50:44.332: D/ROSS(12982): GalleryFragment: bitmap kilobyte count: 1183
06-13 01:50:44.414: D/ROSS(12982): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 01:50:44.414: D/ROSS(12982): GalleryFragment: loadImage viewWidth: 480, viewHeight: 692
06-13 01:50:44.681: D/ROSS(12982): GalleryFragment: bitmap kilobyte count: 1183
06-13 01:50:44.761: D/ROSS(12982): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 01:50:44.761: D/ROSS(12982): GalleryFragment: loadImage viewWidth: 480, viewHeight: 692
06-13 01:50:45.151: D/ROSS(12982): GalleryFragment: bitmap kilobyte count: 1183
06-13 01:50:45.227: D/ROSS(12982): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 01:50:45.231: D/ROSS(12982): GalleryFragment: loadImage viewWidth: 480, viewHeight: 692
06-13 01:50:45.521: D/ROSS(12982): GalleryFragment: bitmap kilobyte count: 1183
06-13 01:50:45.593: D/ROSS(12982): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 01:50:45.593: D/ROSS(12982): GalleryFragment: loadImage viewWidth: 480, viewHeight: 692
06-13 01:50:45.962: D/ROSS(12982): GalleryFragment: bitmap kilobyte count: 1183

EDIT:

After implementing Nicholas' suggestion here's the logcat in total:

06-13 14:51:44.976: D/ROSS(15465): GalleryFragment: imageUri: /mnt/sdcard/RossAndClay - Copy (12).JPG
06-13 14:51:45.146: D/ROSS(15465): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 14:51:45.146: D/ROSS(15465): GalleryFragment: loadImage viewWidth: 480, viewHeight: 692
06-13 14:51:45.376: D/ROSS(15465): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 14:51:45.422: D/ROSS(15465): GalleryFragment: loadImage viewWidth: 480, viewHeight: 692
06-13 14:51:45.626: D/ROSS(15465): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 14:51:45.626: D/ROSS(15465): GalleryFragment: loadImage viewWidth: 480, viewHeight: 692
06-13 14:51:49.336: D/ROSS(15465): GalleryFragment: bitmap kilobyte count: 1183
06-13 14:51:49.356: D/ROSS(15465): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 14:51:51.327: D/ROSS(15465): GalleryFragment: bitmap kilobyte count: 1183
06-13 14:51:51.336: D/ROSS(15465): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 14:51:53.395: D/ROSS(15465): GalleryFragment: bitmap kilobyte count: 1183
06-13 14:51:53.469: D/ROSS(15465): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692

And after clicking the image, in the fragment, this is the logcat in total for that event:

06-13 14:54:41.315: D/ROSS(15465): GalleryFragment: imageUri: /mnt/sdcard/RossAndClay - Copy (11).JPG
06-13 14:54:41.402: D/ROSS(15465): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 14:54:41.406: D/ROSS(15465): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 14:54:41.406: D/ROSS(15465): GalleryFragment: loadImage viewWidth: 480, viewHeight: 692
06-13 14:54:41.655: D/ROSS(15465): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 14:54:41.665: D/ROSS(15465): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 14:54:41.665: D/ROSS(15465): GalleryFragment: loadImage viewWidth: 480, viewHeight: 692
06-13 14:54:44.285: D/ROSS(15465): GalleryFragment: bitmap kilobyte count: 1183
06-13 14:54:44.305: D/ROSS(15465): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 14:54:44.305: D/ROSS(15465): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 14:54:46.965: D/ROSS(15465): GalleryFragment: bitmap kilobyte count: 1183
06-13 14:54:47.036: D/ROSS(15465): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692
06-13 14:54:47.036: D/ROSS(15465): GalleryFragment: onPreDraw viewWidth: 480, viewHeight: 692

Every time I click an image the number of entries in logcat grows longer and longer. My fragment transactions look like:

/*
 * Create the fragment that holds the collection image.
 * @param imageUri
 */
private void createImageFragment(String imageUri) {
    // With each click wipe previous entry, ie: there's no going back.
    getFragmentManager().popBackStack();

    // create new fragment
    GalleryFragment galleryFrag = GalleryFragment.newInstance(imageUri);

    // programmatically add new fragment
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    ft.replace(R.id.gallery_imageFrame, galleryFrag, GALLERY_FRAG);
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
    ft.commit();        
}

The moment I wrote that here I tried changing popBackStack() to popBackStackImmediate() and that stopped the ever growing list of logcat entries. Now, after having done some testing loading various image sizes, it appears like however large the image is determines how many times loadImage() is called, which makes sense (though I'd like to stop that from happening) because (for instance) three asyntasks are being fired off before the imageView's image is actually set. So now the task is to figure out how to ensure loadImage() is called only once.

Edit 2:

Sometimes a particular problem can freeze dry our brains. Solved the multiple calls to the method simply with class field private boolean thisMethodCalled = false; and in onPreDraw:

            if(!thisMethodCalled){
                loadImage(viewWidth, viewHeight);
                thisMethodCalled = true;
            }

...though this doesn't stop onPreDraw from being called an increasing number of times with each fragment replacement, but not sure I can do anything about that.

Final Edit - the best solution:

With notion gleaned from one of the comments in this answer, just remove the listener near the end with imageView.getViewTreeObserver().removeOnPreDrawListener(this);, with no listener, no method being called multiple times; and obviously no additional onPreDraw() calls. Finally, working exactly as I intended it to.

    ViewTreeObserver vto = imageView.getViewTreeObserver();
    vto.addOnPreDrawListener(new OnPreDrawListener() {

        @Override
        public boolean onPreDraw() {
            // TESTING
            viewWidth = imageView.getMeasuredWidth();
            viewHeight = imageView.getMeasuredHeight();

            loadImage(viewWidth, viewHeight);

            imageView.getViewTreeObserver().removeOnPreDrawListener(this);
            return true;
        }
    });
Community
  • 1
  • 1
ross studtman
  • 936
  • 1
  • 8
  • 22

1 Answers1

1

Maybe try set the image views tag to either false or true. True meaning that it has already been drawing. It might work, not sure how it would do with your application though.

Then you can have something like

imageView = (ImageView)view.findViewById(R.id.gallery_image);
// we have not loaded the image yet
imageView.setTag(false);

then on your predraw

boolean hasLoaded = ((Boolean) imageView.getTag());
// if we have not loaded the image yet
// we want to load it
if(!hasLoaded){
            loadImage(viewWidth, viewHeight);                   
}

then in post execute

    if (bitmap != null) {
        Log.d(TAG, SCOPE +"bitmap kilobyte count: "+ bitmap.getByteCount() / 1024);
        // we succesfully loaded bitmap
        imageView.setTag(true);
        imageView.setImageBitmap(bitmap);

        // make BitmapWorkerTask reference null again.
        bmwt = null;
    }

Edit: just fixed up some code to make it easier to read

Nicholas
  • 723
  • 5
  • 11
  • This definitely stopped the infinite loop, thank you very (very) much. I am going to add an edit to the question with a new logcat; I would like to find a way to only have the BitmapWorkerTask only run once; I'm thinking maybe the `synchronization` didn't work because it only synchronizes that method, not the method chain (?); so maybe use an anonymous asynctask inside the loadImage method? However that turns out for me...thank you for helping! – ross studtman Jun 13 '13 at 14:44
  • The code I wound up using to solve the problems surrounding this issue has been added as "Final Edit" to the original question. Thank you once again for nudging me along. – ross studtman Jun 13 '13 at 19:26