package com.threeH.MyExhibition.widget; import android.content.Context; import android.graphics.drawable.AnimationDrawable; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; import com.threeH.MyExhibition.R; import com.threeH.MyExhibition.cache.XmlDB; import com.threeH.MyExhibition.tools.PixelDpHelper; import com.threeH.MyExhibition.tools.TimeHelper; import com.threeH.MyExhibition.ui.MyApplication; public class PullToRefreshView extends ListView implements OnScrollListener, OnClickListener { private Context mCon; private LayoutInflater mInflater; private MyApplication mApp; public PullToRefreshView(Context context) { super(context); init(context); } public PullToRefreshView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } private final int RELEASE_To_REFRESH = 0; /** 下拉刷新. */ private final int PULL_To_REFRESH = 1; private final int REFRESHING = 2; /** 刷新完毕状态 */ private final int DONE = 3; // 实际的padding的距离与界面上偏移距离的比例 private final int RATIO = 1; /** ListView顶部的HeaderView. */ private LinearLayout mHeadView; /** ListView刷新状态显示,比如:正在刷新...;放手吧; */ private TextView mListStatus; /** ListView上次刷新时间显示,比如:最近更新:06-01 12:00 */ private TextView mListUpdateTime; /** ListView变换箭头. */ private ImageView mListHeadArrow; /** 顶部Loading的View. */ private ImageView mListHeadLoading; /** 顶部AnimationDrawable. */ private AnimationDrawable mHeaderAnim; /** ListView顶部高度. */ private int mHeadHeight; /** ListView底部的FooterView. */ public RelativeLayout mFootView; /** 底部Footer布局的Layout */ private RelativeLayout mFootBgLayout; /** 底部Loading的View. */ private ImageView mListFootLoading; /** 底部AnimationDrawable. */ private AnimationDrawable mFooterAnim; // /** 当ListView为空时显示的View. */ // private View emptyFootView; /** ListView滑动状态. */ private int mListState; /** 是否显示回到顶端的标示位. */ private boolean isShowBackToTop = false; private final int HIDEBACKTOTOP = 0; /** 目前ListView的状态(正在刷新中....;刷新完毕;) */ private int mRefreshState; private OnRefreshListener mOnRefreshListener; /** 由松开进行刷新状态变为下拉进行刷新状态. */ private boolean isBack; private RotateAnimation animation; private RotateAnimation reverseAnimation; /** 用于保证startY的值在一个完整的touch事件中只被记录一次. */ private boolean isRecored; private int startY; private int mFirstVisibleItem; private boolean isRefreshable; private boolean mBanRefresh = true; Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case HIDEBACKTOTOP: if (!isShowBackToTop) { if (mBackToTopView != null) { mBackToTopView.setVisibility(View.GONE); } } break; } } }; private void init(Context context) { this.mCon = context; /** 防止滑动变黑. */ setCacheColorHint(mCon.getResources().getColor(R.color.transparent)); mInflater = LayoutInflater.from(mCon); mApp = (MyApplication) mCon.getApplicationContext(); mHeadView = (LinearLayout) mInflater .inflate(R.layout.list_header, null); mListHeadArrow = (ImageView) mHeadView .findViewById(R.id.mListHeadArrow); mListHeadArrow.setMinimumHeight(50); mListHeadLoading = (ImageView) mHeadView .findViewById(R.id.mListHeadLoading); mHeaderAnim = (AnimationDrawable) mListHeadLoading.getDrawable(); mListStatus = (TextView) mHeadView.findViewById(R.id.mListStatus); mListUpdateTime = (TextView) mHeadView .findViewById(R.id.mListUpdateTime); measureView(mHeadView); mHeadHeight = mHeadView.getMeasuredHeight(); mHeadView.setPadding(0, -1 * mHeadHeight, 0, 0); mHeadView.invalidate(); addHeaderView(mHeadView, null, false); /** 底部View. */ mFootView = (RelativeLayout) mInflater.inflate(R.layout.list_footer, null); mFootView.setOnClickListener(this); mFootBgLayout = (RelativeLayout) mFootView .findViewById(R.id.mFootBgLayout); mListFootLoading = (ImageView) mFootView .findViewById(R.id.mListFootLoading); mFooterAnim = (AnimationDrawable) mListFootLoading.getDrawable(); mRefreshFooterText = (TextView) mFootView .findViewById(R.id.refresh_tv_message); // emptyFootView = mInflater.inflate(R.layout.empty_foot, null); setOnScrollListener(this); animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); animation.setInterpolator(new LinearInterpolator()); animation.setDuration(200); animation.setFillAfter(true); reverseAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); reverseAnimation.setInterpolator(new LinearInterpolator()); reverseAnimation.setDuration(200); reverseAnimation.setFillAfter(true); mRefreshState = DONE; isRefreshable = false; } private void measureView(View child) { ViewGroup.LayoutParams p = child.getLayoutParams(); if (p == null) { p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } public void onScrollStateChanged(AbsListView arg0, int scrollState) { mListState = scrollState; } public void onScroll(AbsListView arg0, int firstVisibleItem, int arg2, int arg3) { mFirstVisibleItem = firstVisibleItem; switch (mListState) { case SCROLL_STATE_FLING: if (mFirstVisibleItem == 0) { isShowBackToTop = false; mHandler.sendEmptyMessageDelayed(HIDEBACKTOTOP, 1000); } else { isShowBackToTop = true; } break; case SCROLL_STATE_TOUCH_SCROLL: if (mBackToTopView != null) { mBackToTopView.setVisibility(View.VISIBLE); isShowBackToTop = true; } break; case SCROLL_STATE_IDLE: isShowBackToTop = false; mHandler.sendEmptyMessageDelayed(HIDEBACKTOTOP, 3000); break; } } public boolean onTouchEvent(MotionEvent event) { if (isRefreshable && mBanRefresh) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // Trace.e("action down called"); if (mFirstVisibleItem == 0 && !isRecored) { isRecored = true; startY = (int) event.getY(); } break; case MotionEvent.ACTION_CANCEL: // Trace.e("action cancel called"); if (mRefreshState != REFRESHING) { if (mRefreshState == RELEASE_To_REFRESH) { mRefreshState = REFRESHING; changeHeadStatus(); onRefresh(HEADER); } else { mRefreshState = DONE; changeHeadStatus(); } } isRecored = false; isBack = false; break; case MotionEvent.ACTION_UP: // Trace.e("action up called"); if (mRefreshState != REFRESHING) { if (mRefreshState == RELEASE_To_REFRESH) { mRefreshState = REFRESHING; changeHeadStatus(); onRefresh(HEADER); } else { mRefreshState = DONE; changeHeadStatus(); } } isRecored = false; isBack = false; break; case MotionEvent.ACTION_MOVE: // Trace.d("action move called"); int tempY = (int) event.getY(); if (!isRecored && mFirstVisibleItem == 0) { isRecored = true; startY = tempY; } if (mRefreshState != REFRESHING && isRecored) { // 保证在设置padding的过程中,当前的位置一直是在head,否则如果当列表超出屏幕的话,当在上推的时候,列表会同时进行滚动 if (mRefreshState == RELEASE_To_REFRESH) { setSelection(0); if (((tempY - startY) / PixelDpHelper.dip2px(getContext(), RATIO) < mHeadHeight) && (tempY - startY) > 0) { /** 往上推了,推到了屏幕足够掩盖head的程度,但是还没有推到全部掩盖的地步 */ mRefreshState = PULL_To_REFRESH; changeHeadStatus(); } else if (tempY - startY <= 0) { mRefreshState = DONE; changeHeadStatus(); } } if (mRefreshState == PULL_To_REFRESH) { /** 还没有到达显示松开刷新的时候,DONE或者是PULL_To_REFRESH状态 */ setSelection(0); if ((tempY - startY) / PixelDpHelper.dip2px(getContext(), RATIO) >= mHeadHeight) { /** 下拉到可以进入RELEASE_TO_REFRESH的状态 */ mRefreshState = RELEASE_To_REFRESH; isBack = true; changeHeadStatus(); } else if (tempY - startY <= 0) { /** 上推到顶了 */ mRefreshState = DONE; changeHeadStatus(); } } if (mRefreshState == DONE) { if (tempY - startY > 0) { mRefreshState = PULL_To_REFRESH; changeHeadStatus(); } } if (mRefreshState == PULL_To_REFRESH) { mHeadView.setPadding( 0, -1 * mHeadHeight + (tempY - startY) / PixelDpHelper.dip2px(getContext(), RATIO), 0, 0); } if (mRefreshState == RELEASE_To_REFRESH) { mHeadView.setPadding(0, (tempY - startY) / PixelDpHelper.dip2px(getContext(), RATIO) - mHeadHeight, 0, 0); } } break; } } return super.onTouchEvent(event); } // 当状态改变时候,调用该方法,以更新界面 private void changeHeadStatus() { switch (mRefreshState) { case RELEASE_To_REFRESH: /** 当前状态,松开刷新 */ /** 播放松开刷新的提示音 */ // mApp.playSound(R.raw.xiala); mListHeadArrow.setVisibility(View.VISIBLE); mListHeadLoading.setVisibility(View.GONE); mHeaderAnim.stop(); mListStatus.setVisibility(View.VISIBLE); mListUpdateTime.setVisibility(View.VISIBLE); mListHeadArrow.clearAnimation(); mListHeadArrow.startAnimation(animation); mListStatus.setText("放手吧"); break; case PULL_To_REFRESH: mListHeadLoading.setVisibility(View.GONE); mHeaderAnim.stop(); mListStatus.setVisibility(View.VISIBLE); mListUpdateTime.setVisibility(View.VISIBLE); mListHeadArrow.clearAnimation(); mListHeadArrow.setVisibility(View.VISIBLE); if (isBack) { /** 是由RELEASE_To_REFRESH状态转变来的 */ isBack = false; mListHeadArrow.clearAnimation(); mListHeadArrow.startAnimation(reverseAnimation); mListStatus.setText("下拉刷新"); // mApp.playSound(R.raw.pushup); } else { mListStatus.setText("下拉刷新"); } break; case REFRESHING: /** 当前状态,正在刷新... */ mHeadView.setPadding(0, 0, 0, 0); mListHeadLoading.setVisibility(View.VISIBLE); mHeaderAnim.start(); mListHeadArrow.setVisibility(View.VISIBLE); mListHeadArrow.clearAnimation(); mListHeadArrow.setVisibility(View.GONE); mListStatus.setText("正在刷新..."); mListUpdateTime.setVisibility(View.VISIBLE); break; case DONE: smoothScrollTo(mHeadView.getPaddingTop(), -1 * mHeadHeight, -mHeadHeight); mListHeadLoading.setVisibility(View.GONE); mHeaderAnim.stop(); mListHeadArrow.setVisibility(View.VISIBLE); mListHeadArrow.clearAnimation(); mListHeadArrow.setImageResource(R.drawable.ic_refresh_droparrow); mListStatus.setText("下拉刷新"); mListUpdateTime.setVisibility(View.VISIBLE); break; } } private SmoothScrollRunnable mSmoothScrollRunnable; private final Handler handler = new Handler(); protected final void smoothScrollTo(int mFromY, int mToY, int mEndPos) { if (null != mSmoothScrollRunnable) { mSmoothScrollRunnable.stop(); } if (this.getScrollY() != mToY) { this.mSmoothScrollRunnable = new SmoothScrollRunnable(handler, mFromY, mToY, mEndPos); handler.post(mSmoothScrollRunnable); } } final class SmoothScrollRunnable implements Runnable { static final int ANIMATION_DURATION_MS = 250; static final int ANIMATION_FPS = 1000 / 100; private final Interpolator interpolator; private final int scrollToY; private final int scrollFromY; private final int mEndPos; private final Handler handler; private boolean continueRunning = true; private long startTime = -1; private int currentY = -1; public SmoothScrollRunnable(Handler handler, int fromY, int toY, int mEndPos) { this.handler = handler; this.scrollFromY = fromY; this.scrollToY = toY; this.mEndPos = mEndPos; this.interpolator = new AccelerateDecelerateInterpolator(); } @Override public void run() { /** * Only set startTime if this is the first time we're starting, else * actually calculate the Y delta */ if (startTime == -1) { startTime = System.currentTimeMillis(); } else { /** * We do do all calculations in long to reduce software float * calculations. We use 1000 as it gives us good accuracy and * small rounding errors */ long normalizedTime = (1000 * (System.currentTimeMillis() - startTime)) / ANIMATION_DURATION_MS; normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0); final int deltaY = Math .round((scrollFromY - scrollToY) * interpolator .getInterpolation(normalizedTime / 1000f)); this.currentY = scrollFromY - deltaY; setHeaderScroll(currentY); } // If we're not at the target Y, keep going... if (continueRunning && scrollToY != currentY) { handler.postDelayed(this, ANIMATION_FPS); } else { mHeadView.setPadding(0, mEndPos, 0, 0); } } public void stop() { this.continueRunning = false; this.handler.removeCallbacks(this); } }; protected final void setHeaderScroll(int y) { mHeadView.setPadding(0, y, 0, 0); } /** ListView数据刷新或加载更多时回调的注册函数. */ public void setOnRefreshListener(OnRefreshListener onRefreshListener) { this.mOnRefreshListener = onRefreshListener; isRefreshable = true; } public interface OnRefreshListener { public void onRefresh(int which); } public void setRefreshAble(boolean enable) { mBanRefresh = enable; } /** * Resets the list to a normal mRefreshState after a refresh. */ public void onRefreshComplete(int which) { switch (which) { case HEADER: mRefreshState = DONE; initRefreshTime(); changeHeadStatus(); invalidateViews(); break; case FOOTER: mFootBgLayout.setBackgroundResource(R.drawable.sy01_2x); mListFootLoading.setVisibility(View.GONE); mFooterAnim.stop(); mRefreshFooterText.setText("查看更多"); mRefreshState = DONE; changeHeadStatus(); invalidateViews(); break; } } public void initRefreshTime() { if (this.getTag() == null) { mListUpdateTime.setText("最近更新:" + TimeHelper.getCurrentMinute()); return; } if ("ChatList".equalsIgnoreCase(this.getTag().toString())) { XmlDB.getInstance(mCon).saveKey("list_ChatList", TimeHelper.getCurrentMinute()); mListUpdateTime.setText("最近更新:" + XmlDB.getInstance(mCon).getKeyStringValue( "list_ChatList", TimeHelper.getCurrentMinute())); } else if ("Inbox".equalsIgnoreCase(this.getTag().toString())) { XmlDB.getInstance(mCon).saveKey("list_Inbox", TimeHelper.getCurrentMinute()); mListUpdateTime.setText("最近更新:" + XmlDB.getInstance(mCon).getKeyStringValue("list_Inbox", TimeHelper.getCurrentMinute())); } else if ("OnLine".equalsIgnoreCase(this.getTag().toString())) { XmlDB.getInstance(mCon).saveKey("list_OnLine", TimeHelper.getCurrentMinute()); mListUpdateTime.setText("最近更新:" + XmlDB.getInstance(mCon).getKeyStringValue("list_OnLine", TimeHelper.getCurrentMinute())); } else if ("Nearby".equalsIgnoreCase(this.getTag().toString())) { XmlDB.getInstance(mCon).saveKey("list_Nearby", TimeHelper.getCurrentMinute()); mListUpdateTime.setText("最近更新:" + XmlDB.getInstance(mCon).getKeyStringValue("list_Nearby", TimeHelper.getCurrentMinute())); } else if ("dynamic".equalsIgnoreCase(this.getTag().toString())) { XmlDB.getInstance(mCon).saveKey("list_dynamic", TimeHelper.getCurrentMinute()); mListUpdateTime.setText("最近更新:" + XmlDB.getInstance(mCon).getKeyStringValue("list_dynamic", TimeHelper.getCurrentMinute())); } } /** ListView刷新时调用的方法(可能由下拉ListView或点击"更多"触发该方法). */ public void initRefresh(int which) { prepareForRefresh(which); onRefresh(which); } public final static int HEADER = 0; public final static int FOOTER = 1; private void prepareForRefresh(int which) { switch (which) { case HEADER: mRefreshState = REFRESHING; changeHeadStatus(); break; case FOOTER: mFootBgLayout.setBackgroundDrawable(null); mListFootLoading.setVisibility(View.VISIBLE); mFooterAnim.start(); mRefreshFooterText.setText("加载中..."); mRefreshState = REFRESHING; break; } } public void onRefresh(int which) { if (mOnRefreshListener != null) { mOnRefreshListener.onRefresh(which); } } public void setAdapter(BaseAdapter adapter) { initRefreshTime1(); super.setAdapter(adapter); } public void initRefreshTime1() { if (this.getTag() == null) { mListUpdateTime.setText("最近更新:" + TimeHelper.getCurrentMinute()); return; } if ("ChatList".equalsIgnoreCase(this.getTag().toString())) { XmlDB.getInstance(mCon).saveKey("list_ChatList", TimeHelper.getCurrentMinute()); mListUpdateTime.setText("最近更新:" + XmlDB.getInstance(mCon).getKeyStringValue( "list_ChatList", TimeHelper.getCurrentMinute())); } else if ("Inbox".equalsIgnoreCase(this.getTag().toString())) { mListUpdateTime.setText("最近更新:" + XmlDB.getInstance(mCon).getKeyStringValue("list_Inbox", TimeHelper.getCurrentMinute())); } else if ("OnLine".equalsIgnoreCase(this.getTag().toString())) { mListUpdateTime.setText("最近更新:" + XmlDB.getInstance(mCon).getKeyStringValue("list_OnLine", TimeHelper.getCurrentMinute())); } else if ("Nearby".equalsIgnoreCase(this.getTag().toString())) { mListUpdateTime.setText("最近更新:" + XmlDB.getInstance(mCon).getKeyStringValue("list_Nearby", TimeHelper.getCurrentMinute())); } else if ("dynamic".equalsIgnoreCase(this.getTag().toString())) { mListUpdateTime.setText("最近更新:" + XmlDB.getInstance(mCon).getKeyStringValue("list_dynamic", TimeHelper.getCurrentMinute())); } } private ImageView mBackToTopView; public void setTopViewImage(ImageView mTopViewImage) { this.mBackToTopView = mTopViewImage; mTopViewImage.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { setSelection(0); } }); } private View mTempFootView = null; private TextView mRefreshFooterText; public void addFooterView() { // if (!hasFooter) { removeFooterView(); mTempFootView = mFootView; addFooterView(mFootView); // hasFooter = true; // } } // public void addEmptyView(int drawableId) { // removeFooterView(); // emptyFootView.findViewById(R.id.empty_imageview).setBackgroundResource( // drawableId); // mTempFootView = emptyFootView; // addFooterView(mTempFootView); // } public void removeFooterView() { if (mTempFootView != null) { removeFooterView(mTempFootView); } } @Override public void onClick(View arg0) { if (mRefreshState == DONE) { initRefresh(FOOTER); } } }