2

I am using a getView in an adapter where I am creating an imageview and making that equal to convertView where the view has already been initialized before. It contains image thumbnails, some of which represent videos.

    @Override
    public View getView(int position, View convertView, ViewGroup container) {
        // First check if this is the top row

        if (position < mNumColumns) {
            if (convertView == null) {
                convertView = new View(mContext);
            }
            // Set empty view with height of ActionBar
            //convertView.setLayoutParams(new AbsListView.LayoutParams(
            //      LayoutParams.MATCH_PARENT, mActionBarHeight));
            return convertView;
        }

        // Now handle the main ImageView thumbnails
        ImageView imageView;
        if (convertView == null) { // if it's not recycled, instantiate and initialize
            imageView = new RecyclingImageView(mContext);
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            imageView.setLayoutParams(mImageViewLayoutParams);
        } else { // Otherwise re-use the converted view
            imageView = (ImageView) convertView;
        }

        // Check the height matches our calculated column width
        if (imageView.getLayoutParams().height != mItemHeight) {
            imageView.setLayoutParams(mImageViewLayoutParams);
        }

        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);

        if(images.get(position - mNumColumns).getUriString().contains("video")){
            //display video icon
        }
        else
        {
            //don't display video icon
        }

        // Finally load the image asynchronously into the ImageView, this also takes care of
        // setting a placeholder image while the background thread runs
        if (images != null && !images.isEmpty())
            mImageFetcher.loadImage(images.get(position - mNumColumns).getUriString()/*.imageUrls[position - mNumColumns]*/, imageView);
        return imageView;
    }

The thumbnails do not have a "play" button on them to designate that they are videos, so in those cases I need to add a play button, programmatically.

Typically I use a viewholder pattern with an inflated layout, I am not doing that in this case because I actually don't want some things in memory.

So instead I want to programmatically make a RelativeLayout as the root view of each cell (mRelativeLayout = (RelativeLayout)convertView) and add the imageview and playbutton imageview into that convertview

How do I do that? It requires modification of this statement but I'm not sure how to initialize all the re-used views

   } else { // Otherwise re-use the converted view
            imageView = (ImageView) convertView;
        }
CQM
  • 42,592
  • 75
  • 224
  • 366

2 Answers2

3

I think the best approach here would be to use an Adapter that returns different types of views (by overriding getViewTypeCount() and getItemViewType()), for example as described in this answer.

That way you do not need to programmatically alter the returned views, at all. Just define two XML layouts and inflate/reuse either one or the other according to whether the item at that position has a video or not.

Not only would this be clearer, you wouldn't have the performance penalty of "transforming" one view into the other whenever a video-row is supplied as convertView for another item without one, or vice-versa

Community
  • 1
  • 1
matiash
  • 54,791
  • 16
  • 125
  • 154
  • sure, so this is more about how to programmatically make the second view. This question doesn't involve defining an XML layout at all :) I know how to do this with XML – CQM Oct 15 '14 at 16:31
2

I would make your getView() always return a RelativeLayout object (which I call containerView below) to which you add your ImageView(s) as children.

The only complication here is that you need to give these children identifiers so that you can retrieve them from a recycled convertView later. Note that I used the built-in, static View.generateViewId() for this, which is API level 17. If you need it to work pre-API-level-17 you can create your own ids using unique integers (such as 1, 2, etc.) -- just make sure they aren't greater than 0x0FFFFFF. Update: I added code that I use for this below.

See the comments I added in several points below.

@Override
public View getView(int position, View convertView, ViewGroup container) {
    // First check if this is the top row
    if (position < mNumColumns) {
        if (convertView == null) {
            convertView = new View(mContext);
        }
        // Set empty view with height of ActionBar
        //convertView.setLayoutParams(new AbsListView.LayoutParams(
        //      LayoutParams.MATCH_PARENT, mActionBarHeight));
        return convertView;
    }

    // Now handle the main ImageView thumbnails
    RelativeLayout containerView;
    ImageView imageView;
    ImageView videoIconView;   // TODO:  or whatever type you want to use for this...
    if (convertView == null) { // if it's not recycled, instantiate and initialize
        containerView = new RelativeLayout(mContext);
        // TODO:  The layout params that you used for the image view you probably 
        // now want to use for the container view instead...
        imageView.setLayoutParams(mImageViewLayoutParams);   // If so, you can change their name...

        imageView = new RecyclingImageView(mContext);
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        //imageView.setLayoutParams(mImageViewLayoutParams);   // This probably isn't needed any more.

        // Generate an Id to use for later retrieval of the imageView...
        // This assumes it was initialized to -1 in the constructor to mark it being unset.
        // Note, this could be done elsewhere in this adapter class (such as in
        // the constructor when mImageId is initialized, since it only
        // needs to be done once (not once per view) -- I'm just doing it here
        // to avoid having to show any other functions.
        if (mImageId == -1) {
            mImageId = View.generateViewId();
        }
        imageView.setId(mImageId);

        containerView.addView(imageView, RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);

        // NOTE:  At this point, I would personally always just add the video icon
        // as a child of containerView here no matter what (generating another unique 
        // view Id for it, mVideoIconId, similar to how was shown above for the imageView) 
        // and then set it to either VISIBLE or INVISIBLE/GONE below depending on whether
        // the URL contains the word "video" or not.  
        // For example:
        vidoIconView = new <whatever>;
        // TODO:  setup videoIconView with the proper drawable, scaling, etc. here...
        if (mVideoIconId == -1) {
            mVideoIconId = View.generateViewId();
        }
        videoIconView.setId(mVideoIconId);
        containerView.addView(videoIconView, RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
        final RelativeLayout.LayoutParams layout = ((RelativeLayout.LayoutParams)containerView.getLayoutParams());
        layout.addRule(RelativeLayout.LayoutParams.CENTER_HORIZONTAL);   // ... or whatever else you want
        layout.addRule(RelativeLayout.LayoutParams.ALIGN_PARENT_BOTTOM);  // ... or whatever else you want
    } else {
        // Otherwise re-use the converted view
        containerView = (RelativeLayout) convertView;
        imageView = containerView.findViewById(mImageId);
        videoIconView = containerView.findViewById(mVideoIconId);  // see comment above
    }


    // Check the height matches our calculated column width
    if (containerView.getLayoutParams().height != mItemHeight) {
        containerView.setLayoutParams(mImageViewLayoutParams);
    }

    if(images.get(position - mNumColumns).getUriString().contains("video")){
        //display video icon
        // see comment above, here you can probably just do something like:
        videoIconView.setVisibility(View.VISIBLE);
    }
    else
    {
        //don't display video icon
        videoIconView.setVisibility(View.GONE);  // could also use INVISIBLE here... up to you.
    }

    // Finally load the image asynchronously into the ImageView, this also takes care of
    // setting a placeholder image while the background thread runs
    if (images != null && !images.isEmpty())
        mImageFetcher.loadImage(images.get(position - mNumColumns).getUriString()/*.imageUrls[position - mNumColumns]*/, imageView);
    return containerView;
}

Update:
In response the question in the comments, I use something like this (in my custom "ViewController" base class):

private static int s_nextGeneratedId = 1;

/** 
 * Try to do the same thing as View.generateViewId() when using API level < 17.
 * @return Unique integer that can be used with setId() on a View.
 */
protected static int generateViewId() {
    // AAPT-generated IDs have the high byte nonzero; clamp to the range under that.
    if (++s_nextGeneratedId > 0x00FFFFFF)
        s_nextGeneratedId = 1;  // Roll over to 1, not 0.
    return s_nextGeneratedId;
}

Note that you do not need a unique view id for every single cell in your grid. Rather, you just need it for each type of child view that you might want to access using findViewById(). So in your case, you're probably going to need just two unique ids. Since the view ids auto-generated from your xml layout files into your R.java typically are very large, I've found it convenient just to use low numbers for my hand-generated ids (as shown above).

Turix
  • 4,470
  • 2
  • 19
  • 27
  • thanks, can you elaborate on how the View.generateViewId() would work if I had to make my own ids? I feel like I'd have to use a random number generator here since I won't be able to reference other cells – CQM Oct 21 '14 at 14:27
  • @CQM I just updated my answer to address that. In particular, I don't think you'll need to use a random number generator. You can probably simply use `1` and `2`, say `1` for `mImageId` and `2` for `mVideoIconId`. (I'm assuming you aren't hand-generating view ids anywhere else in your app.) – Turix Oct 21 '14 at 18:52
  • I was trying to make this one work before awarding the bounty, but was/am still having difficulties before StackOverflow autoselected. It is a better answer than the XML one – CQM Oct 22 '14 at 16:33
  • @CQM thanks. let me know if you're still having troubles with it. I'm happy to try to help more. – Turix Oct 22 '14 at 22:13