/*******************************************************************************
* Copyright 2012 huewu.yang <hueuw.yang@gmail.com>
*
* 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.lan.nicehair.waterfall.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.View;
import android.view.View.MeasureSpec;
import com.lan.nicehair.R;
import com.lan.nicehair.waterfall.base.PLA_ListView;
/**
* @author huewu.ynag
* @date 2012-11-06
*/
public class MultiColumnListView extends PLA_ListView {
@SuppressWarnings("unused")
private static final String TAG = "MultiColumnListView";
private static final int DEFAULT_COLUMN_NUMBER = 2;
private int mColumnNumber = 2;
private Column[] mColumns = null;
private Column mFixedColumn = null; // column for footers & headers.
private SparseIntArray mItems = new SparseIntArray();
private int mColumnPaddingLeft = 0;
private int mColumnPaddingRight = 0;
public MultiColumnListView(Context context) {
super(context);
init(null);
}
public MultiColumnListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public MultiColumnListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
private Rect mFrameRect = new Rect();
private void init(AttributeSet attrs) {
getWindowVisibleDisplayFrame(mFrameRect);
if (attrs == null) {
mColumnNumber = (DEFAULT_COLUMN_NUMBER); // default column number is
// 2.
} else {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.PinterestLikeAdapterView);
int landColNumber = a.getInteger(R.styleable.PinterestLikeAdapterView_plaLandscapeColumnNumber, 3);
int defColNumber = a.getInteger(R.styleable.PinterestLikeAdapterView_plaColumnNumber, 2);
if (mFrameRect.width() > mFrameRect.height() && landColNumber != -1) {
mColumnNumber = (landColNumber);
} else if (defColNumber != -1) {
mColumnNumber = (defColNumber);
} else {
mColumnNumber = (DEFAULT_COLUMN_NUMBER);
}
mColumnPaddingLeft = a.getDimensionPixelSize(R.styleable.PinterestLikeAdapterView_plaColumnPaddingLeft, 0);
mColumnPaddingRight = a.getDimensionPixelSize(R.styleable.PinterestLikeAdapterView_plaColumnPaddingRight, 0);
a.recycle();
}
mColumns = new Column[getColumnNumber()];
for (int i = 0; i < getColumnNumber(); ++i)
mColumns[i] = new Column(i);
mFixedColumn = new FixedColumn();
}
// /////////////////////////////////////////////////////////////////////
// Override Methods...
// /////////////////////////////////////////////////////////////////////
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
// TODO the adapter status may be changed. what should i do here...
}
private int columnWidth;
public int getColumnWidth() {
return columnWidth;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
columnWidth = (getMeasuredWidth() - mListPadding.left - mListPadding.right - mColumnPaddingLeft - mColumnPaddingRight)
/ getColumnNumber();
for (int index = 0; index < getColumnNumber(); ++index) {
mColumns[index].mColumnWidth = columnWidth;
mColumns[index].mColumnLeft = mListPadding.left + mColumnPaddingLeft + columnWidth * index;
}
mFixedColumn.mColumnLeft = mListPadding.left;
mFixedColumn.mColumnWidth = getMeasuredWidth();
}
@Override
protected void onMeasureChild(View child, int position, int widthMeasureSpec, int heightMeasureSpec) {
if (isFixedView(child))
child.measure(widthMeasureSpec, heightMeasureSpec);
else
child.measure(MeasureSpec.EXACTLY | getColumnWidth(position), heightMeasureSpec);
}
@Override
protected int modifyFlingInitialVelocity(int initialVelocity) {
return initialVelocity / getColumnNumber();
}
@Override
protected void onItemAddedToList(int position, boolean flow) {
super.onItemAddedToList(position, flow);
if (isHeaderOrFooterPosition(position) == false) {
Column col = getNextColumn(flow, position);
mItems.append(position, col.getIndex());
}
}
@Override
protected void onLayoutSync(int syncPos) {
for (Column c : mColumns) {
c.save();
}
}
@Override
protected void onLayoutSyncFinished(int syncPos) {
for (Column c : mColumns) {
c.clear();
}
}
@Override
protected void onAdjustChildViews(boolean down) {
int firstItem = getFirstVisiblePosition();
if (down == false && firstItem == 0) {
final int firstColumnTop = mColumns[0].getTop();
for (Column c : mColumns) {
final int top = c.getTop();
// align all column's top to 0's column.
c.offsetTopAndBottom(firstColumnTop - top);
}
}
super.onAdjustChildViews(down);
}
@Override
protected int getFillChildBottom() {
// return smallest bottom value.
// in order to determine fill down or not... (calculate below space)
int result = Integer.MAX_VALUE;
for (Column c : mColumns) {
int bottom = c.getBottom();
result = result > bottom ? bottom : result;
}
return result;
}
@Override
protected int getFillChildTop() {
// find largest column.
int result = Integer.MIN_VALUE;
for (Column c : mColumns) {
int top = c.getTop();
result = result < top ? top : result;
}
return result;
}
@Override
protected int getScrollChildBottom() {
// return largest bottom value.
// for checking scrolling region...
int result = Integer.MIN_VALUE;
for (Column c : mColumns) {
int bottom = c.getBottom();
result = result < bottom ? bottom : result;
}
return result;
}
@Override
protected int getScrollChildTop() {
// find largest column.
int result = Integer.MAX_VALUE;
for (Column c : mColumns) {
int top = c.getTop();
result = result > top ? top : result;
}
return result;
}
@Override
protected int getItemLeft(int pos) {
if (isHeaderOrFooterPosition(pos))
return mFixedColumn.getColumnLeft();
return getColumnLeft(pos);
}
@Override
protected int getItemTop(int pos) {
if (isHeaderOrFooterPosition(pos))
return mFixedColumn.getBottom(); // footer view should be placed
// below the last column.
int colIndex = mItems.get(pos, -1);
if (colIndex == -1)
return getFillChildBottom();
return mColumns[colIndex].getBottom();
}
@Override
protected int getItemBottom(int pos) {
if (isHeaderOrFooterPosition(pos))
return mFixedColumn.getTop(); // header view should be place above
// the first column item.
int colIndex = mItems.get(pos, -1);
if (colIndex == -1)
return getFillChildTop();
return mColumns[colIndex].getTop();
}
// ////////////////////////////////////////////////////////////////////////////
// Private Methods...
// ////////////////////////////////////////////////////////////////////////////
// flow If flow is true, align top edge to y. If false, align bottom edge to
// y.
private Column getNextColumn(boolean flow, int position) {
// we already have this item...
int colIndex = mItems.get(position, -1);
if (colIndex != -1) {
return mColumns[colIndex];
}
// adjust position (exclude headers...)
position = Math.max(0, position - getHeaderViewsCount());
final int lastVisiblePos = Math.max(0, position);
if (lastVisiblePos < getColumnNumber())
return mColumns[lastVisiblePos];
if (flow) {
// find column which has the smallest bottom value.
return gettBottomColumn();
} else {
// find column which has the smallest top value.
return getTopColumn();
}
}
private boolean isHeaderOrFooterPosition(int pos) {
int type = mAdapter.getItemViewType(pos);
return type == ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
}
private Column getTopColumn() {
Column result = mColumns[0];
for (Column c : mColumns) {
result = result.getTop() > c.getTop() ? c : result;
}
return result;
}
private Column gettBottomColumn() {
Column result = mColumns[0];
for (Column c : mColumns) {
result = result.getBottom() > c.getBottom() ? c : result;
}
if (DEBUG)
Log.d("Column", "get Shortest Bottom Column: " + result.getIndex());
return result;
}
private int getColumnLeft(int pos) {
int colIndex = mItems.get(pos, -1);
if (colIndex == -1)
return 0;
return mColumns[colIndex].getColumnLeft();
}
private int getColumnWidth(int pos) {
int colIndex = mItems.get(pos, -1);
if (colIndex == -1)
return 0;
return mColumns[colIndex].getColumnWidth();
}
// /////////////////////////////////////////////////////////////
// Inner Class.
// /////////////////////////////////////////////////////////////
public int getColumnNumber() {
return mColumnNumber;
}
private class Column {
private int mIndex;
private int mColumnWidth;
private int mColumnLeft;
private int mSynchedTop = 0;
private int mSynchedBottom = 0;
// TODO is it ok to use item position info to identify item??
public Column(int index) {
mIndex = index;
}
public int getColumnLeft() {
return mColumnLeft;
}
public int getColumnWidth() {
return mColumnWidth;
}
public int getIndex() {
return mIndex;
}
public int getBottom() {
// find biggest value.
int bottom = Integer.MIN_VALUE;
int childCount = getChildCount();
for (int index = 0; index < childCount; ++index) {
View v = getChildAt(index);
if (v.getLeft() != mColumnLeft && isFixedView(v) == false)
continue;
bottom = bottom < v.getBottom() ? v.getBottom() : bottom;
}
if (bottom == Integer.MIN_VALUE)
return mSynchedBottom; // no child for this column..
return bottom;
}
public void offsetTopAndBottom(int offset) {
if (offset == 0)
return;
// find biggest value.
int childCount = getChildCount();
for (int index = 0; index < childCount; ++index) {
View v = getChildAt(index);
if (v.getLeft() != mColumnLeft && isFixedView(v) == false)
continue;
v.offsetTopAndBottom(offset);
}
}
public int getTop() {
// find smallest value.
int top = Integer.MAX_VALUE;
int childCount = getChildCount();
for (int index = 0; index < childCount; ++index) {
View v = getChildAt(index);
if (v.getLeft() != mColumnLeft && isFixedView(v) == false)
continue;
top = top > v.getTop() ? v.getTop() : top;
}
if (top == Integer.MAX_VALUE)
return mSynchedTop; // no child for this column. just return
// saved sync top..
return top;
}
public void save() {
mSynchedTop = 0;
mSynchedBottom = getTop(); // getBottom();
}
public void clear() {
mSynchedTop = 0;
mSynchedBottom = 0;
}
}// end of inner class Column
private class FixedColumn extends Column {
public FixedColumn() {
super(Integer.MAX_VALUE);
}
@Override
public int getBottom() {
return getScrollChildBottom();
}
@Override
public int getTop() {
return getScrollChildTop();
}
}// end of class
}// end of class