Center a CardView in a RecyclerView with only one element

I’ve implemented simple HelloWorld app, which shows list of cities and based on how many museums it has – shows full-sized city-card or the centered, wrapped version of it.

enter image description here

(Yes, I’m not exactly good at arts 🙂 )

Here’s how I did it.

TL;DR:

The crucial part is ItemDecoration: set proper items offset and you’ll get what you need; Here’s how I’ve done it:

    RecyclerView recyclerViewMuseum = (RecyclerView)findViewById(R.id.recyclerViewMuseum);
    recyclerViewMuseum.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
    recyclerViewMuseum.setAdapter(adapter);
    recyclerViewMuseum.addItemDecoration(new RecyclerView.ItemDecoration() {

        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            if (view instanceof CityWithOneMuseumCardView) {
                int totalWidth = parent.getWidth();
                int cardWidth = getResources().getDimensionPixelOffset(R.dimen.small_card_width);
                int sidePadding = (totalWidth - cardWidth) / 2;
                sidePadding = Math.max(0, sidePadding);
                outRect.set(sidePadding, 0, sidePadding, 0);
            }
        }
    });

Here’s my model – City and Museum classes:

public class Museum {
    public String title;
    public Museum(String title) {
        this.title = title;
    }
}

public class City {
    public String title;
    public int imageRes;
    public List<Museum> museums = new ArrayList<>();

    public City(String title, int imageRes) {
        this.title = title;
        this.imageRes = imageRes;
    }
}

Then Views: CityWithManyMuseumsCardView and CityWithOneMuseumCardView. Both of them are using helper-interface IItemDisplayer.

public class CityWithOneMuseumCardView extends CardView implements IItemDisplayer<City> {

    public CityWithOneMuseumCardView(Context context) {
        super(context);
        LayoutInflater.from(context).inflate(R.layout.one_museum_layout, this);
    }

    @Override
    public void displayItem(City city) {
        TextView cityTitleTextView = (TextView)findViewById(R.id.cityTitleTextView);
        cityTitleTextView.setText(city.title);
    }
}

public class CityWithManyMuseumsCardView extends CardView implements IItemDisplayer<City> {

    public CityWithManyMuseumsCardView(Context context) {
        super(context);
        LayoutInflater.from(context).inflate(R.layout.many_museums_layout, this);
    }

    @Override
    public void displayItem(City city) {
        ImageView cityBackgroundImageView = (ImageView)findViewById(R.id.cityBackgroundImageView);
        cityBackgroundImageView.setImageResource(city.imageRes);
        TextView cityTitleTextView = (TextView)findViewById(R.id.cityTitleTextView);
        cityTitleTextView.setText(city.title);
        TextView cityNumberOrMuseumsTextView = (TextView)findViewById(R.id.cityNumberOrMuseumsTextView);
        cityNumberOrMuseumsTextView.setText(String.valueOf(city.museums.size()));
    }
}

public interface IItemDisplayer<TItem> {
    public void displayItem(TItem item);
}

And their layouts :

<!-- One Museum card -->
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="#BB2050AB"
    android:layout_width="@dimen/small_card_width"
    android:layout_height="200dp">

    <TextView
        android:background="#AA000000"
        android:textColor="#FFFFFF"
        android:text="Only one museum available"
        android:textSize="16sp"
        android:padding="4dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/cityTitleTextView"
        android:layout_gravity="bottom"
        android:background="#AAFFFFFF"
        android:textColor="#000000"
        android:textSize="24sp"
        android:paddingStart="16dp"
        android:paddingEnd="16dp"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="48dp" />
</FrameLayout>

<!-- Many museums card -->
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="240dp">
    <ImageView
        android:id="@+id/cityBackgroundImageView"
        android:scaleType="fitXY"
        android:layout_width="500dp"
        android:layout_height="match_parent" />

    <TextView
        android:id="@+id/cityNumberOrMuseumsTextView"
        android:layout_gravity="top|end"
        android:background="#AA000000"
        android:textColor="#FFFFFF"
        android:textSize="16sp"
        android:padding="4dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/cityTitleTextView"
        android:layout_gravity="bottom"
        android:background="#AA000000"
        android:textColor="#FFFFFF"
        android:textSize="24sp"
        android:paddingStart="16dp"
        android:paddingEnd="16dp"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="48dp" />
</FrameLayout>

Then we need to create an adapter for our RecyclerView
CityAdapter.java

public class CityAdapter  extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    final static int ITEM_TYPE_MANY_MUSEUMS = 0;
    final static int ITEM_TYPE_ONE_MUSEUM = 1;

    private List<City> items;

    public CityAdapter(List<City> items) {
        this.items = items;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        switch (viewType) {
            case ITEM_TYPE_MANY_MUSEUMS:
                return new ViewHolder(new CityWithManyMuseumsCardView(viewGroup.getContext()));
            case ITEM_TYPE_ONE_MUSEUM:
                return new ViewHolder(new CityWithOneMuseumCardView(viewGroup.getContext()));
            default:
                throw new IllegalArgumentException(String.format("Unexpected viewType: %d", viewType));
        }
    }

    @Override
    public int getItemViewType(int position) {
        if (items == null || items.size() < position) {
            throw new IllegalArgumentException("Wrong position!");
        }

        if (items.get(position).museums.size() > 1) {
            return ITEM_TYPE_MANY_MUSEUMS;
        } else if (items.get(position).museums.size() == 1){
            return ITEM_TYPE_ONE_MUSEUM;
        }

        throw new IllegalArgumentException("Wrong number of museums!");
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ((IItemDisplayer<City>) holder.itemView).displayItem(items.get(position));
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        public ViewHolder(View itemView) {
            super(itemView);
        }
    }
}

I’ve uploaded this project to my dropbox – feel free to check it out! Hope, it helps.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)