/* * 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.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 android.view.ViewGroup.MarginLayoutParams; import com.marshalchen.common.uimodule.R; import com.marshalchen.common.uimodule.twowayview.widget.Lanes.LaneInfo; public class SpannableGridLayoutManager extends GridLayoutManager { private static final String LOGTAG = "SpannableGridLayoutManager"; private static final int DEFAULT_NUM_COLS = 3; private static final int DEFAULT_NUM_ROWS = 3; protected static class SpannableItemEntry extends BaseLayoutManager.ItemEntry { private final int colSpan; private final int rowSpan; public SpannableItemEntry(int startLane, int anchorLane, int colSpan, int rowSpan) { super(startLane, anchorLane); this.colSpan = colSpan; this.rowSpan = rowSpan; } public SpannableItemEntry(Parcel in) { super(in); this.colSpan = in.readInt(); this.rowSpan = in.readInt(); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(colSpan); out.writeInt(rowSpan); } public static final Creator<SpannableItemEntry> CREATOR = new Creator<SpannableItemEntry>() { @Override public SpannableItemEntry createFromParcel(Parcel in) { return new SpannableItemEntry(in); } @Override public SpannableItemEntry[] newArray(int size) { return new SpannableItemEntry[size]; } }; } private boolean mMeasuring; public SpannableGridLayoutManager(Context context) { this(context, null); } public SpannableGridLayoutManager(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SpannableGridLayoutManager(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle, DEFAULT_NUM_COLS, DEFAULT_NUM_ROWS); } public SpannableGridLayoutManager(Orientation orientation, int numColumns, int numRows) { super(orientation, numColumns, numRows); } private int getChildWidth(int colSpan) { return getLanes().getLaneSize() * colSpan; } private int getChildHeight(int rowSpan) { return getLanes().getLaneSize() * rowSpan; } private static int getLaneSpan(LayoutParams lp, boolean isVertical) { return (isVertical ? lp.colSpan : lp.rowSpan); } private static int getLaneSpan(SpannableItemEntry entry, boolean isVertical) { return (isVertical ? entry.colSpan : entry.rowSpan); } @Override public boolean canScrollHorizontally() { return super.canScrollHorizontally() && !mMeasuring; } @Override public boolean canScrollVertically() { return super.canScrollVertically() && !mMeasuring; } @Override int getLaneSpanForChild(View child) { return getLaneSpan((LayoutParams) child.getLayoutParams(), isVertical()); } @Override int getLaneSpanForPosition(int position) { final SpannableItemEntry entry = (SpannableItemEntry) getItemEntryForPosition(position); if (entry == null) { throw new IllegalStateException("Could not find span for position " + position); } return getLaneSpan(entry, isVertical()); } @Override void getLaneForPosition(LaneInfo outInfo, int position, Direction direction) { final SpannableItemEntry entry = (SpannableItemEntry) 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); } } private int getWidthUsed(View child) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); return getWidth() - getPaddingLeft() - getPaddingRight() - getChildWidth(lp.colSpan); } private int getHeightUsed(View child) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); return getHeight() - getPaddingTop() - getPaddingBottom() - getChildHeight(lp.rowSpan); } @Override void measureChildWithMargins(View child) { // XXX: This will disable scrolling while measuring this child to ensure that // both width and height can use MATCH_PARENT properly. mMeasuring = true; measureChildWithMargins(child, getWidthUsed(child), getHeightUsed(child)); mMeasuring = false; } @Override protected 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++) { SpannableItemEntry entry = (SpannableItemEntry) getItemEntryForPosition(i); if (entry == null) { final View child = recycler.getViewForPosition(i); entry = (SpannableItemEntry) cacheChildLaneAndSpan(child, Direction.END); } 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, getChildWidth(entry.colSpan), getChildHeight(entry.rowSpan), mTempLaneInfo, Direction.END); if (i != position) { pushChildFrame(entry, mTempRect, entry.startLane, getLaneSpan(entry, isVertical), 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(); SpannableItemEntry entry = (SpannableItemEntry) getItemEntryForPosition(position); if (entry != null) { mTempLaneInfo.set(entry.startLane, entry.anchorLane); } if (mTempLaneInfo.isUndefined()) { getLaneForChild(mTempLaneInfo, child, direction); } if (entry == null) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); entry = new SpannableItemEntry(mTempLaneInfo.startLane, mTempLaneInfo.anchorLane, lp.colSpan, lp.rowSpan); setItemEntryForPosition(position, entry); } else { entry.setLane(mTempLaneInfo); } return entry; } @Override public boolean checkLayoutParams(RecyclerView.LayoutParams lp) { if (lp.width != LayoutParams.MATCH_PARENT || lp.height != LayoutParams.MATCH_PARENT) { return false; } if (lp instanceof LayoutParams) { final LayoutParams spannableLp = (LayoutParams) lp; if (isVertical()) { return (spannableLp.rowSpan >= 1 && spannableLp.colSpan >= 1 && spannableLp.colSpan <= getLaneCount()); } else { return (spannableLp.colSpan >= 1 && spannableLp.rowSpan >= 1 && spannableLp.rowSpan <= getLaneCount()); } } return false; } @Override public LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } @Override public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { final LayoutParams spannableLp = new LayoutParams((MarginLayoutParams) lp); spannableLp.width = LayoutParams.MATCH_PARENT; spannableLp.height = LayoutParams.MATCH_PARENT; if (lp instanceof LayoutParams) { final LayoutParams other = (LayoutParams) lp; if (isVertical()) { spannableLp.colSpan = Math.max(1, Math.min(other.colSpan, getLaneCount())); spannableLp.rowSpan = Math.max(1, other.rowSpan); } else { spannableLp.colSpan = Math.max(1, other.colSpan); spannableLp.rowSpan = Math.max(1, Math.min(other.rowSpan, getLaneCount())); } } return spannableLp; } @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 rowSpan; public int colSpan; public LayoutParams(int width, int height) { super(width, height); rowSpan = DEFAULT_SPAN; colSpan = DEFAULT_SPAN; } public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.twowayview_SpannableGridViewChild); colSpan = Math.max( DEFAULT_SPAN, a.getInt(R.styleable.twowayview_SpannableGridViewChild_twowayview_colSpan, -1)); rowSpan = Math.max( DEFAULT_SPAN, a.getInt(R.styleable.twowayview_SpannableGridViewChild_twowayview_rowSpan, -1)); a.recycle(); } public LayoutParams(ViewGroup.LayoutParams other) { super(other); init(other); } public LayoutParams(MarginLayoutParams other) { super(other); init(other); } private void init(ViewGroup.LayoutParams other) { if (other instanceof LayoutParams) { final LayoutParams lp = (LayoutParams) other; rowSpan = lp.rowSpan; colSpan = lp.colSpan; } else { rowSpan = DEFAULT_SPAN; colSpan = DEFAULT_SPAN; } } } }