MrTorgue MrTorgue - 1 month ago 15
Android Question

How to set a dynamically sized placeholder in a recyclerview with gridlayout?

I'm working on a piece of code that allows the user to select one of their photos from Facebook and use it in our app as a profile picture. The logic is done, but the layout gives me a bit of a headache.

I have this layout for my images

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="5dp"
>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/imageView"
/>
</android.support.v7.widget.CardView>


and I'm using a RecyclerView, which loads each image of album into a 2 column grid (courtesy of GridLayoutManager). This is done using Picasso:

mImageLoader.load(image.getUrl()).resize(480, 480).centerCrop().into(holder.imageView);


Because loading an album which contains hundreds of pictures will take a while, I want to display a placeholder. This can be achieved using the .placeholder operator in Picasso.

My problem is that I have no idea, how high and wide one of my ViewHolders is.

GridLayoutManager will calculate how much space is available and assign a width value to each item, so as many as I assigned will fit, which is the reason, why I can't use a predefined drawable for my placeholder, because I can never be sure that my 240x240 drawable will fit on every device.




So, my question: How can I get the width of each card in my recyclerview, so I apply a bitmap, I created at runtime, as a placeholder?

Answer

While this isn't exactly the answer to the question, what I really needed was an ImageView that was always square. That way, I can simply load anything I want into that thing and resize it to fit using Picasso.

mImageLoader.load(image.getUrl()).fit().into(holder.imageView);

In order to achieve that, I subclassed ImageView and in the onMeasure method, I assign the width-value to the View's height-parameter.

public class SquareImageView extends ImageView {

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int edgeLen = getMeasuredWidth();
        setMeasuredDimension(edgeLen, edgeLen);
    }
}

Problem is, if I used a horizontally scrolling Grid-Layout, it would stretch each element abnormally long, so we need to add an attribute to determine its dominant side and because I'm lazy, I'll simply add a default-parameter.

Because it would be nice to simply define it as a view-parameter, we implement a stylable-declaration in our attr file

<declare-styleable name="SquareImageView">
    <attr name="dominantSide" format="enum">
        <enum name="width" value="0"/>
        <enum name="height" value="1"/>
    </attr>
</declare-styleable>

Now, we can simply add it to our SquareImageView

<com.example.android.ui.custom.SquareImageView
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:dominantSide="width" />

since we declared that our dominant side is the width, we can do whatever the hell we want with height. You could assign it as 0dp, as match_parent, as 3.14159dp or whatever else you want. You need to assign it something though, otherwise IntelliJ will cuss you out.

In our custom class, we simply get the parameter dominantSide

public class SquareImageView extends ImageView {

    private int dominantSide;

    public SquareImageView(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SquareImageView);
        dominantSide = a.getInt(R.styleable.SquareImageView_dominantSide, 0);
        a.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int edgeLen = dominantSide == 0 ? getMeasuredWidth() : getMeasuredHeight();
        setMeasuredDimension(edgeLen, edgeLen);
    }
}