好库网
/*******************************************************************************
 * Copyright 2012 huewu.yang <hueuw.yang@链接已屏蔽>
 *
 * 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
 *
 * 链接已屏蔽
 *
 * 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.huewu.pla.lib;

import com.youxiachai.onexlistview.R;
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 com.huewu.pla.lib.internal.PLA_AbsListView;
import com.huewu.pla.lib.internal.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;
	
	private int speedSlow = 1;

	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,
							-1);
			int defColNumber = a.getInteger(
					R.styleable.PinterestLikeAdapterView_plaColumnNumber, -1);

			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[mColumnNumber];
		for (int i = 0; i < mColumnNumber; ++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...
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		// int width = (getMeasuredWidth() - mListPadding.left -
		// mListPadding.right) / mColumnNumber;
		int width = (getMeasuredWidth() - mListPadding.left
				- mListPadding.right - mColumnPaddingLeft - mColumnPaddingRight)
				/ mColumnNumber;

		for (int index = 0; index < mColumnNumber; ++index) {
			mColumns[index].mColumnWidth = width;
			mColumns[index].mColumnLeft = mListPadding.left
					+ mColumnPaddingLeft + width * 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);
	}
	
	public void setSpeed(int speed){
		this.speedSlow = speed;
	}

	@Override
	protected int modifyFlingInitialVelocity(int initialVelocity) {
		return initialVelocity / mColumnNumber / speedSlow;
	}

	@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);
	}

	public int getColumnCount() {
		return mColumnNumber;
	}

	@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) {

		// position = Math.max(0, position - getHeaderViewsCount());
		// we already have this item...
		int colIndex = mItems.get(position, -1);
		if (colIndex != -1) {
			return mColumns[colIndex];
		}

		final int lastVisiblePos = Math.max(0, position);
		if (lastVisiblePos < mColumnNumber)
			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.
	// /////////////////////////////////////////////////////////////

	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

	private boolean loadingMoreComplete = true;

	public void onLoadMoreComplete() {
		loadingMoreComplete = true;
	}

	public interface OnLoadMoreListener {
		/**
		 * Method to be called when scroll to buttom is requested
		 */
		void onLoadMore();
	}

	public OnScrollListener scroller = new OnScrollListener() {
		private int visibleLastIndex = 0;
		private static final int OFFSET = 2;

		@Override
		public void onScrollStateChanged(PLA_AbsListView view, int scrollState) {
			int lastIndex = getAdapter().getCount() - OFFSET;
			if (scrollState == OnScrollListener.SCROLL_STATE_IDLE
					&& visibleLastIndex == lastIndex && loadingMoreComplete) {

				loadMoreListener.onLoadMore();
				loadingMoreComplete = false;

			}
		}

		@Override
		public void onScroll(PLA_AbsListView view, int firstVisibleItem,
				int visibleItemCount, int totalItemCount) {
			// visibleLastIndex = firstVisibleItem + visibleItemCount - 1;
			visibleLastIndex = firstVisibleItem + visibleItemCount - OFFSET;
		}
	};
	OnLoadMoreListener loadMoreListener;

	/** it is prepare item loadmore default 2
	 * @param listener
	 */
	public void setOnLoadMoreListener(OnLoadMoreListener listener) {

		if (listener != null) {
			this.loadMoreListener = listener;
			this.setOnScrollListener(scroller);
		}
	}

}// end of class