/******************************************************************************* * 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.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