/* * Copyright (C) 2014 Lucas Rocha * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.marshalchen.common.uimodule.twowayview.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.Recycler; import android.support.v7.widget.RecyclerView.State; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import com.marshalchen.common.uimodule.R; import com.marshalchen.common.uimodule.twowayview.widget.Lanes.LaneInfo; public class StaggeredGridLayoutManager extends GridLayoutManager { private static final String LOGTAG = "StaggeredGridLayoutManager"; private static final int DEFAULT_NUM_COLS = 2; private static final int DEFAULT_NUM_ROWS = 2; protected static class StaggeredItemEntry extends BaseLayoutManager.ItemEntry { private final int span; private int width; private int height; public StaggeredItemEntry(int startLane, int anchorLane, int span) { super(startLane, anchorLane); this.span = span; } public StaggeredItemEntry(Parcel in) { super(in); this.span = in.readInt(); this.width = in.readInt(); this.height = in.readInt(); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(span); out.writeInt(width); out.writeInt(height); } public static final Creator<StaggeredItemEntry> CREATOR = new Creator<StaggeredItemEntry>() { @Override public StaggeredItemEntry createFromParcel(Parcel in) { return new StaggeredItemEntry(in); } @Override public StaggeredItemEntry[] newArray(int size) { return new StaggeredItemEntry[size]; } }; } public StaggeredGridLayoutManager(Context context) { this(context, null); } public StaggeredGridLayoutManager(Context context, AttributeSet attrs) { this(context, attrs, 0); } public StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle, DEFAULT_NUM_COLS, DEFAULT_NUM_ROWS); } public StaggeredGridLayoutManager(Orientation orientation, int numColumns, int numRows) { super(orientation, numColumns, numRows); } @Override int getLaneSpanForChild(View child) { LayoutParams lp = (LayoutParams) child.getLayoutParams(); return lp.span; } @Override int getLaneSpanForPosition(int position) { final StaggeredItemEntry entry = (StaggeredItemEntry) getItemEntryForPosition(position); if (entry == null) { throw new IllegalStateException("Could not find span for position " + position); } return entry.span; } @Override void getLaneForPosition(LaneInfo outInfo, int position, Direction direction) { final StaggeredItemEntry entry = (StaggeredItemEntry) getItemEntryForPosition(position); if (entry != null) { outInfo.set(entry.startLane, entry.anchorLane); return; } outInfo.setUndefined(); } @Override void getLaneForChild(LaneInfo outInfo, View child, Direction direction) { super.getLaneForChild(outInfo, child, direction); if (outInfo.isUndefined()) { getLanes().findLane(outInfo, getLaneSpanForChild(child), direction); } } @Override void moveLayoutToPosition(int position, int offset, Recycler recycler, State state) { final boolean isVertical = isVertical(); final Lanes lanes = getLanes(); lanes.reset(0); for (int i = 0; i <= position; i++) { StaggeredItemEntry entry = (StaggeredItemEntry) getItemEntryForPosition(i); if (entry != null) { mTempLaneInfo.set(entry.startLane, entry.anchorLane); // The lanes might have been invalidated because an added or // removed item. See BaseLayoutManager.invalidateItemLanes(). if (mTempLaneInfo.isUndefined()) { lanes.findLane(mTempLaneInfo, getLaneSpanForPosition(i), Direction.END); entry.setLane(mTempLaneInfo); } lanes.getChildFrame(mTempRect, entry.width, entry.height, mTempLaneInfo, Direction.END); } else { final View child = recycler.getViewForPosition(i); // XXX: This might potentially cause stalls in the main // thread if the layout ends up having to measure tons of // child views. We might need to add different policies based // on known assumptions regarding certain layouts e.g. child // views have stable aspect ratio, lane size is fixed, etc. measureChild(child, Direction.END); // The measureChild() call ensures an entry is created for // this position. entry = (StaggeredItemEntry) getItemEntryForPosition(i); mTempLaneInfo.set(entry.startLane, entry.anchorLane); lanes.getChildFrame(mTempRect, getDecoratedMeasuredWidth(child), getDecoratedMeasuredHeight(child), mTempLaneInfo, Direction.END); cacheItemFrame(entry, mTempRect); } if (i != position) { pushChildFrame(entry, mTempRect, entry.startLane, entry.span, Direction.END); } } lanes.getLane(mTempLaneInfo.startLane, mTempRect); lanes.reset(Direction.END); lanes.offset(offset - (isVertical ? mTempRect.bottom : mTempRect.right)); } @Override ItemEntry cacheChildLaneAndSpan(View child, Direction direction) { final int position = getPosition(child); mTempLaneInfo.setUndefined(); StaggeredItemEntry entry = (StaggeredItemEntry) getItemEntryForPosition(position); if (entry != null) { mTempLaneInfo.set(entry.startLane, entry.anchorLane); } if (mTempLaneInfo.isUndefined()) { getLaneForChild(mTempLaneInfo, child, direction); } if (entry == null) { entry = new StaggeredItemEntry(mTempLaneInfo.startLane, mTempLaneInfo.anchorLane, getLaneSpanForChild(child)); setItemEntryForPosition(position, entry); } else { entry.setLane(mTempLaneInfo); } return entry; } void cacheItemFrame(StaggeredItemEntry entry, Rect childFrame) { entry.width = childFrame.right - childFrame.left; entry.height = childFrame.bottom - childFrame.top; } @Override ItemEntry cacheChildFrame(View child, Rect childFrame) { StaggeredItemEntry entry = (StaggeredItemEntry) getItemEntryForPosition(getPosition(child)); if (entry == null) { throw new IllegalStateException("Tried to cache frame on undefined item"); } cacheItemFrame(entry, childFrame); return entry; } @Override public boolean checkLayoutParams(RecyclerView.LayoutParams lp) { boolean result = super.checkLayoutParams(lp); if (lp instanceof LayoutParams) { final LayoutParams staggeredLp = (LayoutParams) lp; result &= (staggeredLp.span >= 1 && staggeredLp.span <= getLaneCount()); } return result; } @Override public LayoutParams generateDefaultLayoutParams() { if (isVertical()) { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); } else { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); } } @Override public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { final LayoutParams staggeredLp = new LayoutParams((ViewGroup.MarginLayoutParams) lp); if (isVertical()) { staggeredLp.width = LayoutParams.MATCH_PARENT; staggeredLp.height = lp.height; } else { staggeredLp.width = lp.width; staggeredLp.height = LayoutParams.MATCH_PARENT; } if (lp instanceof LayoutParams) { final LayoutParams other = (LayoutParams) lp; staggeredLp.span = Math.max(1, Math.min(other.span, getLaneCount())); } return staggeredLp; } @Override public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) { return new LayoutParams(c, attrs); } public static class LayoutParams extends TwoWayView.LayoutParams { private static final int DEFAULT_SPAN = 1; public int span; public LayoutParams(int width, int height) { super(width, height); span = DEFAULT_SPAN; } public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.twowayview_StaggeredGridViewChild); span = Math.max(DEFAULT_SPAN, a.getInt(R.styleable.twowayview_StaggeredGridViewChild_twowayview_span, -1)); a.recycle(); } public LayoutParams(ViewGroup.LayoutParams other) { super(other); init(other); } public LayoutParams(ViewGroup.MarginLayoutParams other) { super(other); init(other); } private void init(ViewGroup.LayoutParams other) { if (other instanceof LayoutParams) { final LayoutParams lp = (LayoutParams) other; span = lp.span; } else { span = DEFAULT_SPAN; } } } }