package czd.lib.view.waterfall; import android.app.Activity; import android.content.Context; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; import android.widget.ScrollView; import java.lang.ref.WeakReference; import java.util.ArrayList; public abstract class AbstractWaterfallContainer extends ScrollView { protected static final int DEFAULT_ADVANCE_GAP = 300; protected static final int DEFAULT_COLUMN_COUNT = 2; protected static final int DEFAULT_SAVE_GAP = 300; public static final int MOVE_UP = 1, MOVE_DOWN = 2; protected int direction=MOVE_DOWN; protected int height = 0, width = 0, column_width = 0; protected int lastY = 0; protected boolean onTop = true, onBottom = true, onStop = true; protected int advance_gap = DEFAULT_ADVANCE_GAP; protected int column_gap = 10; protected int column_count = DEFAULT_COLUMN_COUNT; protected boolean done_init = false; protected boolean pulling = false; protected LinearLayout container; protected View header = null, footer = null; protected Context context; protected int[] column_heights; protected int min_column = 0, min_height = 0, max_height = 0; protected ArrayList<Integer>[] item_positions; protected int[] display_position, recycle_position; protected int item_count = 0; protected int header_height = 0; protected int tall = 0; protected int m = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); protected OnClickListener click_listener = null; protected WaterfallScrollListener scroll_listener = null; protected WaterfallHandler handler = null; protected OnInitListener init_listener = null; protected boolean overscroll = false; public AbstractWaterfallContainer(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.context = context; } public AbstractWaterfallContainer(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; } public AbstractWaterfallContainer(Context context) { super(context); this.context = context; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (height == 0) { height = getHeight(); advance_gap = Math.min(advance_gap, height / 5); } if (width == 0) { width = getWidth(); column_width = (width - getPaddingLeft() - getPaddingRight() - column_gap * (column_count - 1)) / column_count; } if (height != 0 && width != 0) { if (!done_init || container == null) init(); else if (tall != getChildAt(0).getHeight()) { tall = getChildAt(0).getHeight(); handleView(); } } } @Override public boolean onTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_MOVE) onStop = false; return super.onTouchEvent(ev); } protected void init() { done_init = true; this.setScrollbarFadingEnabled(true); initScroll(); handler = new WaterfallHandler(this); column_heights = new int[column_count]; item_positions = new ArrayList[column_count]; display_position = new int[column_count]; recycle_position = new int[column_count]; for (int i = 0; i < column_count; i++) { LinearLayout column = new LinearLayout(context); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(column_width, LayoutParams.WRAP_CONTENT); if (i == 0) { params.rightMargin = column_gap / 2; params.leftMargin = 0; } else if (i == column_count - 1) { params.leftMargin = column_gap / 2; params.rightMargin = 0; } else params.leftMargin = params.rightMargin = column_gap / 2; column.setLayoutParams(params); column.setOrientation(LinearLayout.VERTICAL); container.addView(column); item_positions[i] = new ArrayList<Integer>(); display_position[i] = 0; recycle_position[i] = 0; column_heights[i] = header_height + getPaddingTop(); } if (init_listener != null) init_listener.sendEmptyMessage(0); } protected void initScroll() { if (header == null && footer == null) { container = new LinearLayout(context); container.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); container.setOrientation(LinearLayout.HORIZONTAL); container.setGravity(Gravity.CENTER_HORIZONTAL); this.addView(container); } else { LinearLayout top_container = new LinearLayout(context); top_container.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); top_container.setOrientation(LinearLayout.VERTICAL); top_container.setGravity(Gravity.CENTER_HORIZONTAL); this.addView(top_container); if (header != null) { header.measure(m, m); top_container.addView(header); header_height = header.getMeasuredHeight(); } container = new LinearLayout(context); container.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); container.setOrientation(LinearLayout.HORIZONTAL); container.setGravity(Gravity.CENTER_HORIZONTAL); top_container.addView(container); if (footer != null) { footer.measure(m, m); top_container.addView(footer); } } } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if (onStop) onStop = false; if (item_count > 0) { if (scroll_listener != null) scroll_listener.onScroll(this, l, t, oldl, oldt); handler.sendMessageDelayed(handler.obtainMessage(0, t, oldt), 150); } } @Override protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { super.onOverScrolled(scrollX, scrollY, clampedX, clampedY); if (clampedY) this.overscroll = true; } public void handleView() { if (item_count > 0 && onStop) { switch (this.direction) { case MOVE_DOWN: //下滑 ((Activity)context).runOnUiThread(new Runnable() { @Override public void run() { for (int i = 0; i < column_count; i++) { int tmp_item_count = item_positions[i].size(); if (tmp_item_count > 0) { //recycle for (int j = recycle_position[i]; j < tmp_item_count; j++) { WaterfallItem rec_item = (WaterfallItem)((LinearLayout)container.getChildAt(i)).getChildAt(j); if (item_positions[i].get(j) < lastY - rec_item.getItemHeight() - DEFAULT_SAVE_GAP) { rec_item.recycle(); if (recycle_position[i] < tmp_item_count - 1) recycle_position[i]++; } else break; } //load for (int k = display_position[i]; k < tmp_item_count; k++) { int top_pos = item_positions[i].get(k); WaterfallItem item = (WaterfallItem)((LinearLayout)container.getChildAt(i)).getChildAt(k); int bottom_pos = top_pos + item.getItemHeight(); if (bottom_pos < lastY - DEFAULT_SAVE_GAP) { if (display_position[i] < tmp_item_count - 1) display_position[i]++; } else if (top_pos > lastY + height + DEFAULT_SAVE_GAP) break; else { item.load(); if (display_position[i] < tmp_item_count - 1) display_position[i]++; } } } } System.gc(); } }); break; case MOVE_UP: //上滑 ((Activity)context).runOnUiThread(new Runnable() { @Override public void run() { for (int i = 0; i < column_count; i++) { if (item_positions[i].size() > 0) { //recycle for (int j = display_position[i]; j >= 0; j--) { WaterfallItem rec_item = (WaterfallItem)((LinearLayout)container.getChildAt(i)).getChildAt(j); if (item_positions[i].get(j) > lastY + height + DEFAULT_SAVE_GAP) { rec_item.recycle(); if (display_position[i] > 0) display_position[i]--; } else break; } //load for (int k = recycle_position[i]; k >= 0; k--) { int top_pos = item_positions[i].get(k); WaterfallItem item = (WaterfallItem)((LinearLayout)container.getChildAt(i)).getChildAt(k); int bottom_pos = top_pos + item.getItemHeight(); if (top_pos > lastY + height + DEFAULT_SAVE_GAP) { if (recycle_position[i] > 0) recycle_position[i]--; } else if (bottom_pos < lastY - DEFAULT_SAVE_GAP) break; else { item.load(); if (recycle_position[i] > 0) recycle_position[i]--; } } } } System.gc(); } }); break; default: break; } } } protected void whereami(int t) { if (t <= advance_gap) { if (!onTop && scroll_listener != null) { onTop = true; scroll_listener.onTop(t); } } else { if (onTop && scroll_listener != null) { onTop = false; scroll_listener.outTop(t); } } if (tall - t - height <= advance_gap) { if (!onBottom && scroll_listener != null) { onBottom = true; scroll_listener.onBottom(t); } } else { if (onBottom && scroll_listener != null) { onBottom = false; scroll_listener.outBottom(t); } } } public void addItem(WaterfallItem item) { if (!done_init || container == null) { init(); } if (item != null) { if (click_listener != null) { item.setOnClickListener(click_listener); } item.setLayoutParams(); ((LinearLayout)container.getChildAt(min_column)).addView((View)item); requestLayout(); item_positions[min_column].add(column_heights[min_column]); column_heights[min_column] = column_heights[min_column] + item.getItemHeight(); item_count++; for (int i = 0; i < column_count; i++) { if (i != min_column) { min_height = column_heights[min_column]; max_height = column_heights[i]; if (min_height >= max_height) { min_column = i; max_height = min_height; } } } } } public void cleanItems() { for (int i = 0; i < column_count; i++) { ((LinearLayout)container.getChildAt(i)).removeAllViews(); item_positions[i].clear(); display_position[i] = 0; recycle_position[i] = 0; column_heights[i] = 0; } min_column = 0; min_height = 0; max_height = 0; item_count = 0; lastY = 0; onTop = true; onBottom = true; onStop = true; height = 0; requestLayout(); } public void setAdvanceGap(int gap) { this.advance_gap = gap; } public int getAdvanceGap() { return this.advance_gap; } public void setColumnCount(int count) { this.column_count = count; } public int getColumnCount() { return this.column_count; } public int getColumnWidth() { return column_width; } public int getColumnGap() { return column_gap; } public void setColumnGap(int gap) { this.column_gap = gap; } public int getItemCount() { return this.item_count; } public int getTall() { return this.tall; } public void setItemClickListener(OnClickListener listener) { this.click_listener = listener; } public void setHeader(View header) { this.header = header; } public View getHeader() { return this.header; } public void setFooter(View footer) { this.footer = footer; } public View getFooter() { return this.footer; } public void setScrollListener(WaterfallScrollListener listener) { this.scroll_listener = listener; } public void setOnInitListener(OnInitListener listener) { this.init_listener = listener; } protected static class WaterfallHandler extends Handler { WeakReference<AbstractWaterfallContainer> waterfall_weak; public WaterfallHandler(AbstractWaterfallContainer waterfall) { this.waterfall_weak = new WeakReference<AbstractWaterfallContainer>(waterfall); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); final AbstractWaterfallContainer waterfall = waterfall_weak.get(); if (waterfall != null && !waterfall.pulling) { //msg.arg1 -> t //msg.arg2 -> oldt if (!waterfall.onStop && msg.arg1 == waterfall.lastY) { //stop waterfall.onStop = true; if (msg.arg2 <= 0) { waterfall.direction=MOVE_UP; waterfall.handleView(); } else if (msg.arg2 + waterfall.height >= waterfall.tall) { waterfall.direction =MOVE_DOWN; waterfall.handleView(); } else if (msg.arg1 < msg.arg2) { if (waterfall.overscroll) { waterfall.direction =MOVE_DOWN; waterfall.handleView(); } else { waterfall.direction =MOVE_UP; waterfall.handleView(); } } else if (msg.arg1 >= msg.arg2) { waterfall.direction =MOVE_DOWN; waterfall.handleView(); } if (waterfall.scroll_listener != null) waterfall.scroll_listener.onStop(msg.arg1); waterfall.overscroll = false; } waterfall.lastY = waterfall.getScrollY(); waterfall.whereami(msg.arg1); } } } public static class OnInitListener extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); onInit(); } public void onInit() { } } }