package com.bigdo.controls; import java.math.BigDecimal; import com.bigdo.app.R; import android.annotation.SuppressLint; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.animation.DecelerateInterpolator; import android.widget.AbsListView; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.Scroller; import android.widget.AbsListView.OnScrollListener; public class XListView extends ListView implements OnScrollListener { private float mLastY = 0, mFrist = 0; // save event y private Scroller mScroller, mFooterScroller; // used for scroll back private OnScrollListener mScrollListener; // user's scroll listener // the interface to trigger refresh and load more. private IXListViewListener mListViewListener; private int mTouchSlop = 0; // -- header view private XListViewHeader mHeaderView; // header view content, use it to calculate the Header's height. And hide it // when disable pull refresh. private RelativeLayout mHeaderViewContent, mFooterViewContent; private boolean isPostInvali = false; private int mHeaderViewHeight, mFooterViewHeight; // header view's height private boolean mEnablePullRefresh = true, mPullRefreshing = false; private boolean mIsRefreshingOrLoading = false; // -- footer view private XListViewFooter mFooterView; private boolean mEnablePullLoad, oldMEnablePullLoad; private boolean mPullLoading; // private boolean mIsFooterReady = false; // total list items, used to detect is at the bottom of listview. private int mTotalItemCount, mVisibleItemCount = 0, mLastVisibleItemCount = 0; // for mScroller, scroll back from header or footer. // private int mScrollBack; private final static int SCROLLBACK_HEADER = 0, SCROLLBACK_FOOTER = 1; // scroll back duration private final static int SCROLL_DURATION = 200; // when pull up >= 50px at bottom, trigger load more. // private final static int PULL_LOAD_MORE_DELTA = 50; // support iOS like pull feature. private final static float OFFSET_RADIO = 1.8f; public int tag; public final static int user_Hand_Operation = -1000000; ListAdapter adapter; /** * @param context */ public XListView(Context context) { super(context); initWithContext(context); } public XListView(Context context, AttributeSet attrs) { super(context, attrs); // Log.w("headerDividersEnabled", headerDividersEnabled + ""); initWithContext(context); } public XListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initWithContext(context); } private void initWithContext(Context context) { try { mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); mScroller = new Scroller(context, new DecelerateInterpolator()); mFooterScroller = new Scroller(context, new DecelerateInterpolator()); // XListView need the scroll event, and it will dispatch the event // to // user's listener (as a proxy). super.setOnScrollListener(this); // init header view mHeaderView = new XListViewHeader(context); mHeaderViewContent = (RelativeLayout) mHeaderView .findViewById(R.id.xlistview_header_content); addHeaderView(mHeaderView, null, false); // init footer view mFooterView = new XListViewFooter(context); mFooterViewContent = (RelativeLayout) mFooterView .findViewById(R.id.xlistview_footer_content); addFooterView(mFooterView, null, false); // init header height mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); getViewTreeObserver().removeGlobalOnLayoutListener( this); } }); // init footer height mFooterView.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { public void onGlobalLayout() { mFooterViewHeight = mFooterViewContent.getHeight(); getViewTreeObserver().removeGlobalOnLayoutListener( this); } }); setFooterDividersEnabled(false); setHeaderDividersEnabled(false); } catch (Exception e) { } // this.setSelector(0); // this.setSelector(null); } @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); this.adapter = adapter; setFooterDividersEnabled(false); setHeaderDividersEnabled(false); } public boolean isRefreshing() { return mPullRefreshing; } public boolean isPullLoading() { return mPullLoading; } /** * enable or disable pull down refresh feature. * * @param enable */ public void setPullRefreshEnable(boolean enable) { mEnablePullRefresh = enable; if (!mEnablePullRefresh) { mHeaderViewContent.setVisibility(View.GONE); } else { mHeaderViewContent.setVisibility(View.VISIBLE); } } /** * enable or disable pull up load more feature. * * @param enable */ public void setPullLoadEnable(boolean enable) { mEnablePullLoad = enable; oldMEnablePullLoad = enable; if (!mEnablePullLoad) { mFooterViewContent.setVisibility(View.GONE); } else { mFooterViewContent.setVisibility(View.VISIBLE); } } /** * stop refresh, reset header view. */ public void stopRefresh() { if (mPullRefreshing) { mPullRefreshing = false; mHeaderView.setVisiableHeight(0); mHeaderView.setState(XListViewHeader.STATE_NORMAL); postInvalidate(); // this.scrollBy(0, mHeaderView.getWindowTop() - getWindowTop()); // Log.e("stopRefresh", "true"); } } /** * stop load more, reset footer view. */ public void stopLoadMore() { if (mPullLoading) { mPullLoading = false; mFooterView.setVisiableHeight(0); mFooterView.setState(XListViewFooter.STATE_NORMAL); // this.scrollBy(0, -(getWindowTop() - mFooterView.getWindowTop())); postInvalidate(); } } public void startLoadMore(int requestCode) { if (!mPullLoading) { mPullLoading = true; mFooterView.setState(XListViewFooter.STATE_LOADING); if (mListViewListener != null) { mListViewListener.onLoadMore(this, tag, requestCode); } else { stopLoadMore(); } } } public void startRefresh(int requestCode) { if (!mPullRefreshing) { mPullRefreshing = true; mHeaderView.setState(XListViewHeader.STATE_REFRESHING); if (mListViewListener != null) { mListViewListener.onRefresh(this, tag, requestCode); } else { stopRefresh(); } } } public void startAutoHeightRefresh(int requestCode) { if (!mPullRefreshing) { // 160 / OFFSET_RADIO mHeaderView.setVisiableHeight(0); this.scrollTo(0, 0); isFristHeaderSelection = true; // resetHeaderHeight(); setSelection(0); mHeaderView.setVisiableHeight((int) (dip2px(getContext(), 72))); // updateHeaderHeight(dip2px(getContext(), 50) * OFFSET_RADIO); mPullRefreshing = true; mHeaderView.setState(XListViewHeader.STATE_REFRESHING); // resetHeaderHeight(); if (mListViewListener != null) { mListViewListener.onRefresh(this, tag, requestCode); } else { stopRefresh(); } // HiddenLoadMore(); } } public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } public static int px2dip(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); } public void setEmptyView(View emptyView) { super.setEmptyView(emptyView); // this.getHitRect(outRect) } public boolean isHeaderHidden() { if (mHeaderView.getVisibility() == XListViewHeader.GONE || mHeaderView.getVisibility() == XListViewHeader.INVISIBLE || mHeaderView.getVisiableHeight() <= 0) { return true; } return false; } public void showLoadMore() { if (mFooterView != null) { mEnablePullLoad = oldMEnablePullLoad; mFooterView.setVisibility(XListViewFooter.VISIBLE); postInvalidate(); } } public void hiddenLoadMore() { if (mFooterView != null) { mEnablePullLoad = false; mFooterView.setVisiableHeight(0); mFooterView.setVisibility(XListViewFooter.GONE); postInvalidate(); } } private void hidFooter() { if (!isFristFooterSelection && mFooterView != null) { // mFooterView.setState(XListViewFooter.STATE_NORMAL); isFristFooterSelection = true; mFooterView.setVisiableHeight(0); requestLayout(); postInvalidate(); } } private void hidHeader() { if (!isFristHeaderSelection && mHeaderView != null) { // mHeaderView.setState(XListViewHeader.STATE_NORMAL); isFristHeaderSelection = true; mHeaderView.setVisiableHeight(0); requestLayout(); postInvalidate(); } } /** * set last refresh time * * @param time */ boolean isFristHeaderSelection = true; int header_height_temp = 0; private void updateHeaderHeight(float delta) { if (mEnablePullRefresh) { if (isFristHeaderSelection && _getCount() >= 0) { isFristHeaderSelection = false; // setVerticalScrollBarEnabled(false); // setSelection(0); // if (!mPullRefreshing) { mHeaderView.requestFocusFromTouch(); delta = f_sum(delta, (mHeaderView.getWindowTop() - getWindowTop())); // } // mHeaderView.requestFocus(); // mHeaderView.performClick(); } header_height_temp = Math.round(delta / OFFSET_RADIO); if (mPullRefreshing) { if (header_height_temp < mHeaderViewHeight) { header_height_temp = mHeaderViewHeight; } } mHeaderView.setVisiableHeight(header_height_temp);// OFFSET_RADIO // + mHeaderView.getVisiableHeight()); if (!mPullRefreshing) { if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) { mHeaderView.setState(XListViewHeader.STATE_READY); } else { mHeaderView.setState(XListViewHeader.STATE_NORMAL); } } } } /** * reset header view's height. */ private void resetHeaderHeight() { isFristHeaderSelection = true; int height = mHeaderView.getVisiableHeight(); if (height == 0) { return; } this.scrollBy(0, mHeaderView.getWindowTop() - getWindowTop()); if (height < 0) { height = 0; } int finalHeight = 0; if (mPullRefreshing) { if (height > mHeaderViewHeight) { finalHeight = mHeaderViewHeight - height; } else if (height < mHeaderViewHeight) { finalHeight = mHeaderViewHeight - height; } else { return; } } else { finalHeight = 0 - height; } if (finalHeight != 0) { // mScrollBack = SCROLLBACK_HEADER; mScroller.startScroll(0, height, 0, finalHeight, SCROLL_DURATION); } postInvalidate(); } boolean isFristFooterSelection = true; int footer_height_temp = 0, _footer_c = 0; private void updateFooterHeight(float delta) { if (mEnablePullLoad) { if (isFristFooterSelection && (_footer_c = _getCount()) >= 0) { isFristFooterSelection = false; // setVerticalScrollBarEnabled(false); setSelection(_footer_c); if (_footer_c > 0) { delta = f_sum(delta, (getWindowBottom() - mFooterView.getWindowBottom())); } } footer_height_temp = Math.round(delta / OFFSET_RADIO); if (mPullLoading) { if (footer_height_temp < mFooterViewHeight) { footer_height_temp = mFooterViewHeight; } } mFooterView.setVisiableHeight(footer_height_temp);// OFFSET_RADIO if (!mPullLoading) { if (mFooterView.getVisiableHeight() > mFooterViewHeight) { mFooterView.setState(XListViewFooter.STATE_READY); } else { mFooterView.setState(XListViewFooter.STATE_NORMAL); } } } } private void resetFooterHeight() { isFristFooterSelection = true; int height = mFooterView.getVisiableHeight(); if (height == 0) { return; } if (height < 0) { height = 0; } int finalHeight = 0; if (mPullLoading) { if (height > mFooterViewHeight) { finalHeight = mFooterViewHeight - height; } else if (height < mFooterViewHeight) { finalHeight = mFooterViewHeight - height; } else { return; } } else { finalHeight = 0 - height; } if (finalHeight != 0) { mFooterScroller.startScroll(0, height, 0, finalHeight, SCROLL_DURATION); } postInvalidate(); } @Override public void setFooterDividersEnabled(boolean footerDividersEnabled) { super.setFooterDividersEnabled(footerDividersEnabled); } @Override public void setHeaderDividersEnabled(boolean headerDividersEnabled) { super.setHeaderDividersEnabled(headerDividersEnabled); } public int getWindowTop() { int[] location = { 0, 0 }; getLocationInWindow(location); return location[1]; } public int getWindowBottom() { int[] location = { 0, 0 }; getLocationInWindow(location); return location[1] + getHeight(); } /* * public int getScreenBottom() { final int[] location = { 0, 0 }; * getLocationOnScreen(location); return location[1] + this.getHeight(); } */ float deltaY; int fHeaderHeight, fFooterHeight; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: // resetHeaderAndFooterHeight(); mIsRefreshingOrLoading = false; mLastY = ev.getY(); mFrist = mLastY; fHeaderHeight = mHeaderView.getVisiableHeight(); fFooterHeight = mFooterView.getVisiableHeight(); break; case MotionEvent.ACTION_MOVE: break; default: resetHeaderAndFooterHeight(); break; } return super.onInterceptTouchEvent(ev); } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_MOVE: deltaY = f_sub(ev.getY(), mLastY); if (deltaY != 0) { if (deltaY > 0) { if (mEnablePullRefresh) { if (!mPullLoading && startPullRefresh()) { deltaY = f_sum(deltaY, fHeaderHeight); updateHeaderHeight(deltaY); mIsRefreshingOrLoading = true; if (!mPullLoading) { hidFooter(); } } } } else { if (mEnablePullLoad) { if (!mPullRefreshing && startPullLoad()) { if (fFooterHeight <= 0) { fFooterHeight = mFooterView.getVisiableHeight(); } deltaY = f_sum(-deltaY, fFooterHeight); updateFooterHeight(deltaY); mIsRefreshingOrLoading = true; if (!mPullRefreshing) { hidHeader(); } } } } } break; default: resetHeaderAndFooterHeight(); } return super.onTouchEvent(ev); } public float f_sum(float d1, float d2) { BigDecimal bd1 = new BigDecimal(Float.toString(d1)); BigDecimal bd2 = new BigDecimal(Float.toString(d2)); return bd1.add(bd2).floatValue(); } public float f_sub(float d1, float d2) { BigDecimal bd1 = new BigDecimal(Float.toString(d1)); BigDecimal bd2 = new BigDecimal(Float.toString(d2)); return bd1.subtract(bd2).floatValue(); } private void resetHeaderAndFooterHeight() { if (mIsRefreshingOrLoading) { mLastY = -1; // reset if (mEnablePullRefresh) { if (!mPullRefreshing) { // invoke refresh if (mHeaderViewHeight <= 0) { mHeaderViewHeight = mHeaderViewContent.getHeight(); Log.e("mHeaderViewHeight", mHeaderViewHeight + ""); } int hh = mHeaderView.getVisiableHeight(); if (hh > 0) { if (hh >= mHeaderViewHeight) { if (!mPullLoading) { hidFooter(); } startRefresh(user_Hand_Operation); } } } Log.e("resetHeaderAndFooterHeight", "Header"); resetHeaderHeight(); } // invoke load more. if (mEnablePullLoad) { if (!mPullLoading) { if (mFooterViewHeight <= 0) { mFooterViewHeight = mFooterViewContent.getHeight(); Log.e("mFooterViewHeight", mFooterViewHeight + ""); } int fh = mFooterView.getVisiableHeight(); if (fh > 0) { if (fh >= mFooterViewHeight) { if (!mPullRefreshing) { hidHeader(); } startLoadMore(user_Hand_Operation); } } } Log.e("resetHeaderAndFooterHeight", "Footer"); resetFooterHeight(); // setSelection(getCount()); } // setVerticalScrollBarEnabled(true); mIsRefreshingOrLoading = false; } } private int _getCount() { int c = 0; if (adapter != null) { c = adapter.getCount(); // c--; if (c < 0) { c = 0; } } return c; } private boolean startPullRefresh() { int f = getFirstVisiblePosition(); if (f < 0) { f = 0; } Log.e("startPullRefresh", mHeaderView.getWindowTop() + "," + getWindowTop()); if (f <= 0) { if (mHeaderView.getWindowTop() < getWindowTop()) { // setVerticalScrollBarEnabled(false); return false; } return true; } return false; } private boolean startPullLoad() { int f = getLastVisiblePosition(); f--; if (f < 0) { f = 0; } Log.e("startPullLoad", mFooterView.getWindowTop() + "," + getWindowBottom()); if (f >= _getCount()) { if (mFooterView.getWindowTop() <= getWindowBottom()) { Log.e("startPullLoad", "true"); // setVerticalScrollBarEnabled(false); return true; } return false; } return false; } @Override public void computeScroll() { isPostInvali = false; if (mScroller.computeScrollOffset()) { if (mHeaderView.getVisiableHeight() != 0) { mHeaderView.setVisiableHeight(mScroller.getCurrY()); if (!mPullRefreshing && mHeaderView.getVisiableHeight() != 0 && !mScroller.computeScrollOffset()) { // Log.e("computeScroll", "Header_compute"); mHeaderView.setVisiableHeight(0); // this.requestLayout(); } isPostInvali = true; } } if (mFooterScroller.computeScrollOffset()) { if (mFooterView.getVisiableHeight() != 0) { mFooterView.setVisiableHeight(mFooterScroller.getCurrY()); if (!mPullLoading && mFooterView.getVisiableHeight() != 0 && !mFooterScroller.computeScrollOffset()) { // Log.e("computeScroll", "Footer_compute"); mFooterView.setVisiableHeight(0); // this.requestLayout(); } isPostInvali = true; } } if (isPostInvali) { postInvalidate(); } super.computeScroll(); } @Override public void setOnScrollListener(OnScrollListener listener) { mScrollListener = listener; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (mScrollListener != null) { mScrollListener.onScrollStateChanged(view, scrollState); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // send to user's listener mVisibleItemCount = visibleItemCount; mTotalItemCount = totalItemCount; if (mScrollListener != null) { mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } } public void setXListViewListener(IXListViewListener listener) { mListViewListener = listener; } public void setHeaderExtensionView(View v) { if (mHeaderView != null) { mHeaderView.addExtensionView(v); } } }