Android Open Source - AsymmetricGridView Asymmetric Grid View Adapter






From Project

Back to project page AsymmetricGridView.

License

The source code is released under:

MIT License

If you think the Android project AsymmetricGridView listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.felipecsl.asymmetricgridview.library.widget;
/*w w  w .  j  ava2 s.  com*/
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;

import com.felipecsl.asymmetricgridview.library.AsymmetricGridViewAdapterContract;
import com.felipecsl.asymmetricgridview.library.AsyncTaskCompat;
import com.felipecsl.asymmetricgridview.library.R;
import com.felipecsl.asymmetricgridview.library.Utils;
import com.felipecsl.asymmetricgridview.library.model.AsymmetricItem;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class AsymmetricGridViewAdapter<T extends AsymmetricItem>
        extends BaseAdapter
        implements View.OnClickListener,
        View.OnLongClickListener,
        AsymmetricGridViewAdapterContract {

    private static final String TAG = "AsymmetricGridViewAdapter";
    protected final AsymmetricGridView listView;
    protected final Context context;
    protected final List<T> items;

    private Map<Integer, RowInfo<T>> itemsPerRow = new HashMap<>();
    private final ViewPool<IcsLinearLayout> linearLayoutPool;
    private final ViewPool<View> viewPool = new ViewPool<>();
    private ProcessRowsTask asyncTask;

    public AsymmetricGridViewAdapter(final Context context,
                                     final AsymmetricGridView listView,
                                     final List<T> items) {

        this.linearLayoutPool = new ViewPool<>(new LinearLayoutPoolObjectFactory(context));
        this.items = items;
        this.context = context;
        this.listView = listView;
    }

    public abstract View getActualView(final int position, final View convertView, final ViewGroup parent);

    protected int getRowHeight(final AsymmetricItem item) {
        return getRowHeight(item.getRowSpan());
    }

    public T getItem(int position) {
        return items.get(position);
    }

    public long getItemId(int position) {
        return position;
    }

    protected int getRowHeight(int rowSpan) {
        final int rowHeight = listView.getColumnWidth() * rowSpan;
        // when the item spans multiple rows, we need to account for the vertical padding
        // and add that to the total final height
        return rowHeight + ((rowSpan - 1) * listView.getDividerHeight());
    }

    protected int getRowWidth(final AsymmetricItem item) {
        return getRowWidth(item.getColumnSpan());
    }

    protected int getRowWidth(int columnSpan) {
        final int rowWidth = listView.getColumnWidth() * columnSpan;
        // when the item spans multiple columns, we need to account for the horizontal padding
        // and add that to the total final width
        return Math.min(rowWidth + ((columnSpan - 1) * listView.getRequestedHorizontalSpacing()), Utils.getScreenWidth(context));
    }

    @Override
    public View getView(final int position, final View convertView, final ViewGroup parent) {
        if (listView.isDebugging())
            Log.d(TAG, "getView(" + String.valueOf(position) + ")");

        LinearLayout layout = findOrInitializeLayout(convertView);

        final RowInfo<T> rowInfo = itemsPerRow.get(position);
        final List<T> rowItems = new ArrayList<>();
        rowItems.addAll(rowInfo.getItems());

        // Index to control the current position
        // of the current column in this row
        int columnIndex = 0;

        // Index to control the current position
        // in the array of all the items available for this row
        int currentIndex = 0;

        int spaceLeftInColumn = rowInfo.getRowHeight();

        while (!rowItems.isEmpty() && columnIndex < listView.getNumColumns()) {
            final T currentItem = rowItems.get(currentIndex);

            if (spaceLeftInColumn == 0) {
                // No more space in this column. Move to next one
                columnIndex++;
                currentIndex = 0;
                spaceLeftInColumn = rowInfo.getRowHeight();
                continue;
            }

            // Is there enough space in this column to accommodate currentItem?
            if (spaceLeftInColumn >= currentItem.getRowSpan()) {
                rowItems.remove(currentItem);

                int index = items.indexOf(currentItem);
                final LinearLayout childLayout = findOrInitializeChildLayout(layout, columnIndex);
                final View childConvertView = viewPool.get();
                final View v = getActualView(index, childConvertView, parent);
                v.setTag(currentItem);
                v.setOnClickListener(this);
                v.setOnLongClickListener(this);

                spaceLeftInColumn -= currentItem.getRowSpan();
                currentIndex = 0;

                v.setLayoutParams(new LinearLayout.LayoutParams(getRowWidth(currentItem),
                        getRowHeight(currentItem)));

                childLayout.addView(v);
            } else if (currentIndex < rowItems.size() - 1) {
                // Try again with next item
                currentIndex++;
            } else {
                break;
            }
        }

        if (listView.isDebugging() && position % 20 == 0) {
            Log.d(TAG, linearLayoutPool.getStats("LinearLayout"));
            Log.d(TAG, viewPool.getStats("Views"));
        }

        return layout;
    }

    public Parcelable saveState() {
        final Bundle bundle = new Bundle();
        bundle.putInt("totalItems", items.size());

        for (int i = 0; i < items.size(); i++)
            bundle.putParcelable("item_" + i, items.get(i));

        return bundle;
    }

    @SuppressWarnings("unchecked")
    public void restoreState(final Parcelable state) {
        final Bundle bundle = (Bundle) state;

        if (bundle != null) {
            bundle.setClassLoader(getClass().getClassLoader());

            final int totalItems = bundle.getInt("totalItems");
            final List<T> tmpItems = new ArrayList<>();

            for (int i = 0; i < totalItems; i++)
                tmpItems.add((T) bundle.getParcelable("item_" + i));

            // will trigger recalculateItemsPerRow()
            setItems(tmpItems);
        }
    }

    @SuppressWarnings("MagicConstant")
    private IcsLinearLayout findOrInitializeLayout(final View convertView) {
        IcsLinearLayout layout;

        if (convertView == null || !(convertView instanceof IcsLinearLayout)) {
            layout = new IcsLinearLayout(context, null);
            if (listView.isDebugging())
                layout.setBackgroundColor(Color.parseColor("#83F27B"));

            layout.setShowDividers(IcsLinearLayout.SHOW_DIVIDER_MIDDLE);
            layout.setDividerDrawable(context.getResources().getDrawable(R.drawable.item_divider_horizontal));

            layout.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
                    AbsListView.LayoutParams.WRAP_CONTENT));
        } else
            layout = (IcsLinearLayout) convertView;

        // Clear all layout children before starting
        for (int j = 0; j < layout.getChildCount(); j++) {
            IcsLinearLayout tempChild = (IcsLinearLayout) layout.getChildAt(j);
            linearLayoutPool.put(tempChild);
            for (int k = 0; k < tempChild.getChildCount(); k++)
                viewPool.put(tempChild.getChildAt(k));
            tempChild.removeAllViews();
        }
        layout.removeAllViews();

        return layout;
    }

    @SuppressWarnings("MagicConstant")
    private IcsLinearLayout findOrInitializeChildLayout(final LinearLayout parentLayout, final int childIndex) {
        IcsLinearLayout childLayout = (IcsLinearLayout) parentLayout.getChildAt(childIndex);

        if (childLayout == null) {
            childLayout = linearLayoutPool.get();
            childLayout.setOrientation(LinearLayout.VERTICAL);

            if (listView.isDebugging())
                childLayout.setBackgroundColor(Color.parseColor("#837BF2"));

            childLayout.setShowDividers(IcsLinearLayout.SHOW_DIVIDER_MIDDLE);
            childLayout.setDividerDrawable(context.getResources().getDrawable(R.drawable.item_divider_vertical));

            childLayout.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT,
                    AbsListView.LayoutParams.MATCH_PARENT));
            parentLayout.addView(childLayout);
        }

        return childLayout;
    }

    public void setItems(List<T> newItems) {
        linearLayoutPool.clear();
        viewPool.clear();
        items.clear();
        items.addAll(newItems);
        recalculateItemsPerRow();
        notifyDataSetChanged();
    }

    @SuppressWarnings("unchecked")
    public void appendItems(List<T> newItems) {
        items.addAll(newItems);

        RowInfo<T> rowInfo = null;
        final int lastRow = getRowCount() - 1;
        if (lastRow >= 0)
            rowInfo = itemsPerRow.get(lastRow);

        if (rowInfo != null) {
            final float spaceLeftInLastRow = rowInfo.getSpaceLeft();

            if (listView.isDebugging())
                Log.d(TAG, "Space left in last row: " + spaceLeftInLastRow);

            // Try to add new items into the last row, if there is any space left
            if (spaceLeftInLastRow > 0) {

                for (final T i : rowInfo.getItems())
                    newItems.add(0, i);

                final RowInfo<T> stuffThatFit = calculateItemsForRow(newItems);
                final List<T> itemsThatFit = stuffThatFit.getItems();

                if (!itemsThatFit.isEmpty()) {
                    for (T anItemsThatFit : itemsThatFit) newItems.remove(anItemsThatFit);

                    itemsPerRow.put(lastRow, stuffThatFit);
                    notifyDataSetChanged();
                }
            }
        }

        asyncTask = new ProcessRowsTask();
        asyncTask.executeSerially(newItems);
    }

    @Override
    @SuppressWarnings("unchecked")
    public void onClick(final View v) {
        final T item = (T) v.getTag();
        listView.fireOnItemClick(items.indexOf(item), v);
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean onLongClick(View v) {
        final T item = (T) v.getTag();
        return listView.fireOnItemLongClick(items.indexOf(item), v);
    }

    @Override
    public int getCount() {
        // Returns the row count for ListView display purposes
        return getRowCount();
    }

    public int getRowCount() {
        return itemsPerRow.size();
    }

    @SuppressWarnings("unchecked")
    public void recalculateItemsPerRow() {
        if (asyncTask != null)
            asyncTask.cancel(true);

        linearLayoutPool.clear();
        viewPool.clear();
        itemsPerRow.clear();

        final List<T> itemsToAdd = new ArrayList<>();
        itemsToAdd.addAll(items);

        asyncTask = new ProcessRowsTask();
        asyncTask.executeSerially(itemsToAdd);
    }

    private RowInfo<T> calculateItemsForRow(final List<T> items) {
        return calculateItemsForRow(items, listView.getNumColumns());
    }

    private RowInfo<T> calculateItemsForRow(final List<T> items, final float initialSpaceLeft) {
        final List<T> itemsThatFit = new ArrayList<>();
        int currentItem = 0;
        int rowHeight = 1;
        float areaLeft = initialSpaceLeft;

        while (areaLeft > 0 && currentItem < items.size()) {
            final T item = items.get(currentItem++);
            float itemArea = item.getRowSpan() * item.getColumnSpan();

            if (listView.isDebugging())
                Log.d(TAG, String.format("item %s in row with height %s consumes %s area", item, rowHeight, itemArea));

            if (rowHeight < item.getRowSpan()) {
                // restart with double height
                itemsThatFit.clear();
                rowHeight = item.getRowSpan();
                currentItem = 0;
                areaLeft = initialSpaceLeft * item.getRowSpan();
            } else if (areaLeft >= itemArea) {
                areaLeft -= itemArea;
                itemsThatFit.add(item);
            } else if (!listView.isAllowReordering()) {
                break;
            }
        }

        return new RowInfo<>(rowHeight, itemsThatFit, areaLeft);
    }

    class ProcessRowsTask extends AsyncTaskCompat<List<T>, Void, List<RowInfo<T>>> {

        @Override
        @SafeVarargs
        protected final List<RowInfo<T>> doInBackground(final List<T>... params) {
            return calculateItemsPerRow(0, params[0]);
        }

        @Override
        protected void onPostExecute(List<RowInfo<T>> rows) {
            for (RowInfo<T> row : rows)
                itemsPerRow.put(getRowCount(), row);

            if (listView.isDebugging()) {
                for (Map.Entry<Integer, RowInfo<T>> e : itemsPerRow.entrySet())
                    Log.d(TAG, "row: " + e.getKey() + ", items: " + e.getValue().getItems().size());
            }

            notifyDataSetChanged();
        }

        private List<RowInfo<T>> calculateItemsPerRow(int currentRow, final List<T> itemsToAdd) {
            List<RowInfo<T>> rows = new ArrayList<>();

            while (!itemsToAdd.isEmpty()) {
                final RowInfo<T> stuffThatFit = calculateItemsForRow(itemsToAdd);

                final List<T> itemsThatFit = stuffThatFit.getItems();
                if (itemsThatFit.isEmpty()) {
                    // we can't fit a single item inside a row.
                    // bail out.
                    break;
                }

                for (T anItemsThatFit : itemsThatFit)
                    itemsToAdd.remove(anItemsThatFit);

                rows.add(stuffThatFit);
                currentRow++;
            }

            return rows;
        }
    }

}




Java Source Code List

com.felipecsl.asymmetricgridview.app.MainActivity.java
com.felipecsl.asymmetricgridview.app.model.DemoItem.java
com.felipecsl.asymmetricgridview.app.widget.DefaultListAdapter.java
com.felipecsl.asymmetricgridview.app.widget.ListAdapterWrapper.java
com.felipecsl.asymmetricgridview.library.AsymmetricGridViewAdapterContract.java
com.felipecsl.asymmetricgridview.library.AsyncTaskCompat.java
com.felipecsl.asymmetricgridview.library.Utils.java
com.felipecsl.asymmetricgridview.library.model.AsymmetricItem.java
com.felipecsl.asymmetricgridview.library.widget.AnimatorProxy.java
com.felipecsl.asymmetricgridview.library.widget.AsymmetricGridViewAdapter.java
com.felipecsl.asymmetricgridview.library.widget.AsymmetricGridView.java
com.felipecsl.asymmetricgridview.library.widget.IcsColorDrawable.java
com.felipecsl.asymmetricgridview.library.widget.IcsLinearLayout.java
com.felipecsl.asymmetricgridview.library.widget.LinearLayoutPoolObjectFactory.java
com.felipecsl.asymmetricgridview.library.widget.NineLinearLayout.java
com.felipecsl.asymmetricgridview.library.widget.PoolObjectFactory.java
com.felipecsl.asymmetricgridview.library.widget.RowInfo.java
com.felipecsl.asymmetricgridview.library.widget.ViewPool.java