package com.huewu.pla.lib;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.yahoolee.library.R;
/**
* A generic, customizable Android ListView implementation that has 'Pull to
* Refresh' functionality.
* <p/>
* This ListView can be used in place of the normal Android
* android.widget.ListView class.
* <p/>
* Users of this class should implement OnRefreshListener and call
* setOnRefreshListener(..) to get notified on refresh events. The using class
* should call onRefreshComplete() when refreshing is finished.
* <p/>
* The using class can call setRefreshing() to set the state explicitly to
* refreshing. This is useful when you want to show the spinner and 'Refreshing'
* text when the refresh was not triggered by 'Pull to Refresh', for example on
* start.
* <p/>
* For more information, visit the project page:
* https://github.com/erikwt/PullToRefresh-ListView
*
* @author Erik Wallentinsen <dev+ptr@erikw.eu>
* @version 1.0.0
*/
public class MultiColumnPullToRefreshListView extends MultiColumnListView {
private static final float PULL_RESISTANCE = 3.0f;
private static final int BOUNCE_ANIMATION_DURATION = 215;
private static final int BOUNCE_ANIMATION_DELAY = 20;
private static final int ROTATE_ARROW_ANIMATION_DURATION = 250;
// Loading...
private LoadingThread mLoadingThread = null;
final static int LOADINGBUFFER = 400;
final static int LOADINGZERO = 100;
final static int LOADINGONE = 101;
final static int LOADINGTWO = 102;
final static int LOADINGTHREE = 103;
private static enum State {
PULL_TO_REFRESH,
RELEASE_TO_REFRESH,
REFRESHING
}
/**
* Interface to implement when you want to get notified of 'pull to refresh'
* events. Call setOnRefreshListener(..) to activate an OnRefreshListener.
*/
public interface OnRefreshListener {
/**
* Method to be called when a refresh is requested
*/
public void onRefresh();
}
private static int measuredHeaderHeight;
private boolean scrollbarEnabled;
private boolean bounceBackHeader;
private boolean lockScrollWhileRefreshing;
private boolean showLastUpdatedText;
private String pullToRefreshText;
private String releaseToRefreshText;
private String refreshingText;
private String lastUpdatedText;
private SimpleDateFormat lastUpdatedDateFormat = new SimpleDateFormat("dd/MM HH:mm");
private float previousY;
private int headerPadding;
private boolean hasResetHeader;
private long lastUpdated = -1;
private State state;
private LinearLayout headerContainer;
private RelativeLayout header;
private RotateAnimation flipAnimation;
private RotateAnimation reverseFlipAnimation;
private ImageView image;
private ProgressBar spinner;
private TextView text;
// private TextView loadingText;
private TextView lastUpdatedTextView;
private OnRefreshListener onRefreshListener;
private TranslateAnimation bounceAnimation;
private boolean isHeaderRefreshing = false;
private boolean isHeaderShowing = false;
public MultiColumnPullToRefreshListView(Context context) {
super(context);
init(context, null);
}
public MultiColumnPullToRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public MultiColumnPullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
/**
* Activate an OnRefreshListener to get notified on 'pull to refresh'
* events.
*
* @param onRefreshListener The OnRefreshListener to get notified
*/
public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
this.onRefreshListener = onRefreshListener;
}
/**
* @return If the list is in 'Refreshing' state
*/
public boolean isRefreshing() {
return state == State.REFRESHING;
}
/**
* Default is false. When lockScrollWhileRefreshing is set to true, the list
* cannot scroll when in 'refreshing' mode. It's 'locked' on refreshing.
*
* @param lockScrollWhileRefreshing
*/
public void setLockScrollWhileRefreshing(boolean lockScrollWhileRefreshing) {
this.lockScrollWhileRefreshing = lockScrollWhileRefreshing;
}
/**
* Default is false. Show the last-updated date/time in the 'Pull ro
* Refresh' header. See 'setLastUpdatedDateFormat' to set the date/time
* formatting.
*
* @param showLastUpdatedText
*/
public void setShowLastUpdatedText(boolean showLastUpdatedText) {
this.showLastUpdatedText = showLastUpdatedText;
if (!showLastUpdatedText)
lastUpdatedTextView.setVisibility(View.GONE);
}
/**
* Default: "dd/MM HH:mm". Set the format in which the last-updated
* date/time is shown. Meaningless if 'showLastUpdatedText == false
* (default)'. See 'setShowLastUpdatedText'.
*
* @param lastUpdatedDateFormat
*/
public void setLastUpdatedDateFormat(SimpleDateFormat lastUpdatedDateFormat) {
this.lastUpdatedDateFormat = lastUpdatedDateFormat;
}
/**
* Explicitly set the state to refreshing. This is useful when you want to
* show the spinner and 'Refreshing' text when the refresh was not triggered
* by 'pull to refresh', for example on start.
*/
public void setRefreshing() {
state = State.REFRESHING;
setUiRefreshing();
// setHeaderPadding(0);
// scrollTo(0, 0);
}
/**
* Set the state back to 'pull to refresh'. Call this method when refreshing
* the data is finished.
*/
public void onRefreshComplete() {
state = State.PULL_TO_REFRESH;
resetHeader();
lastUpdated = System.currentTimeMillis();
}
/**
* Change the label text on state 'Pull to Refresh'
*
* @param pullToRefreshText Text
*/
public void setTextPullToRefresh(String pullToRefreshText) {
this.pullToRefreshText = pullToRefreshText;
if (state == State.PULL_TO_REFRESH) {
text.setText(pullToRefreshText);
image.setVisibility(VISIBLE);
if (mLoadingThread != null) {
mLoadingThread.interrupt();
mLoadingThread = null;
}
isHeaderRefreshing = false;
// loadingText.setVisibility(View.GONE);
}
}
/**
* Change the label text on state 'Release to Refresh'
*
* @param releaseToRefreshText Text
*/
public void setTextReleaseToRefresh(String releaseToRefreshText) {
this.releaseToRefreshText = releaseToRefreshText;
if (state == State.RELEASE_TO_REFRESH) {
text.setText(releaseToRefreshText);
image.setVisibility(VISIBLE);
if (mLoadingThread != null) {
mLoadingThread.interrupt();
mLoadingThread = null;
}
isHeaderRefreshing = false;
// loadingText.setVisibility(View.GONE);
}
}
/**
* Change the label text on state 'Refreshing'
*
* @param refreshingText Text
*/
public void setTextRefreshing(String refreshingText) {
this.refreshingText = refreshingText;
if (state == State.REFRESHING) {
text.setText(refreshingText);
image.setVisibility(INVISIBLE);
mLoadingThread = new LoadingThread(mLoadingHandler);
mLoadingThread.start();
isHeaderRefreshing = true;
// loadingText.setVisibility(View.VISIBLE);
}
}
public static float getDimensionDpSize(int id, Context context, AttributeSet attrs) {
TypedArray typedArray = context
.obtainStyledAttributes(attrs, R.styleable.PullToRefreshView);
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float dp = typedArray.getDimension(id, -1) / (metrics.densityDpi / 160f);
return dp;
}
private void init(Context context, AttributeSet attrs) {
setVerticalFadingEdgeEnabled(false);
headerContainer = (LinearLayout) LayoutInflater.from(getContext()).inflate(
R.layout.pull_to_refresh_header, null);
header = (RelativeLayout) headerContainer.findViewById(R.id.ptr_id_header);
text = (TextView) header.findViewById(R.id.ptr_id_text);
// loadingText = (TextView)
// header.findViewById(R.id.ptr_id_loading_text);
lastUpdatedTextView = (TextView) header.findViewById(R.id.ptr_id_last_updated);
image = (ImageView) header.findViewById(R.id.ptr_id_arrow);
spinner = (ProgressBar) header.findViewById(R.id.ptr_id_spinner);
if (attrs == null) {
text.setTextSize(15);
// loadingText.setTextSize(15);
// loadingText.setLayoutParams(new
// android.view.ViewGroup.LayoutParams(
// (int)
// getDimensionDpSize(R.styleable.PullToRefreshView_ptrTextSize,
// context,
// attrs), android.view.ViewGroup.LayoutParams.MATCH_PARENT));
lastUpdatedTextView.setTextSize(12);
image.setPadding(0, 0, 5, 0);
spinner.setPadding(0, 0, 5, 0);
} else {
text.setTextSize(getDimensionDpSize(R.styleable.PullToRefreshView_ptrTextSize, context,
attrs));
// loadingText.setTextSize(getDimensionDpSize(R.styleable.PullToRefreshView_ptrTextSize,
// context, attrs));
// loadingText.setLayoutParams(new
// android.view.ViewGroup.LayoutParams(
// (int)
// getDimensionDpSize(R.styleable.PullToRefreshView_ptrTextSize,
// context,
// attrs), android.view.ViewGroup.LayoutParams.MATCH_PARENT));
lastUpdatedTextView.setTextSize(getDimensionDpSize(
R.styleable.PullToRefreshView_ptrLastUpdateTextSize, context, attrs));
image.setPadding(
0,
0,
(int) getDimensionDpSize(R.styleable.PullToRefreshView_ptrArrowMarginRight,
context, attrs), 0);
spinner.setPadding(
0,
0,
(int) getDimensionDpSize(R.styleable.PullToRefreshView_ptrSpinnerMarginRight,
context, attrs), 0);
}
TextView tv = new TextView(context);
tv.setText("Loading");
tv.setTypeface(Typeface.DEFAULT_BOLD);
tv.setTextSize(getDimensionDpSize(R.styleable.PullToRefreshView_ptrTextSize, context, attrs));
pullToRefreshText = getContext().getString(R.string.ptr_pull_to_refresh);
releaseToRefreshText = getContext().getString(R.string.ptr_release_to_refresh);
refreshingText = getContext().getString(R.string.ptr_loading);
lastUpdatedText = getContext().getString(R.string.ptr_last_updated);
flipAnimation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
flipAnimation.setInterpolator(new LinearInterpolator());
flipAnimation.setDuration(ROTATE_ARROW_ANIMATION_DURATION);
flipAnimation.setFillAfter(true);
reverseFlipAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
reverseFlipAnimation.setInterpolator(new LinearInterpolator());
reverseFlipAnimation.setDuration(ROTATE_ARROW_ANIMATION_DURATION);
reverseFlipAnimation.setFillAfter(true);
addHeaderView(headerContainer);
setState(State.PULL_TO_REFRESH);
scrollbarEnabled = isVerticalScrollBarEnabled();
ViewTreeObserver vto = header.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new PTROnGlobalLayoutListener());
// super.setOnItemClickListener(new PTROnItemClickListener());
// super.setOnItemLongClickListener(new PTROnItemLongClickListener());
}
private void setHeaderPadding(int padding) {
headerPadding = padding;
MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) header.getLayoutParams();
mlp.setMargins(0, Math.round(padding), 0, 0);
header.setLayoutParams(mlp);
}
private boolean isPulling = false;
private boolean isPull(MotionEvent event) {
return isPulling;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// Log.i("Vingle", "interceptEvent x : " + event.getX() + ", y : " +
// event.getY());
// MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)
// header.getLayoutParams();
// Log.i("Vingle", "interceptEvent hx : " + mlp.topMargin);
// Log.i("Vingle", "isHeaderRefresing : " + isHeaderRefreshing);
if (isHeaderRefreshing && isHeaderShowing) {
}
if (lockScrollWhileRefreshing
&& (state == State.REFRESHING || getAnimation() != null
&& !getAnimation().hasEnded())) {
return true; // consume touch event here..
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (getFirstVisiblePosition() == 0)
previousY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
if (getFirstVisiblePosition() == 0 && event.getY() - previousY > 0) {
isPulling = true;
return true;
} else {
isPulling = false;
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
isPulling = false;
break;
}
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Log.i("Vingle", "Event x : " + event.getX() + ", y : " +
// event.getY());
// MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)
// header.getLayoutParams();
// Log.i("Vingle", "Event hx : " + mlp.topMargin);
// Log.i("Vingle", "isHeaderRefresing : " + isHeaderRefreshing);
if (isHeaderRefreshing && isHeaderShowing) {
}
if (lockScrollWhileRefreshing
&& (state == State.REFRESHING || getAnimation() != null
&& !getAnimation().hasEnded())) {
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if (isPull(event)
&& (state == State.RELEASE_TO_REFRESH || getFirstVisiblePosition() == 0)) {
switch (state) {
case RELEASE_TO_REFRESH:
setState(State.REFRESHING);
bounceBackHeader();
break;
case PULL_TO_REFRESH:
resetHeader();
break;
default:
break;
}
}
break;
case MotionEvent.ACTION_MOVE:
if (isPull(event)) {
float y = event.getY();
float diff = y - previousY;
if (diff > 0)
diff /= PULL_RESISTANCE;
previousY = y;
int newHeaderPadding = Math.max(Math.round(headerPadding + diff),
-header.getHeight());
if (newHeaderPadding != headerPadding && state != State.REFRESHING) {
setHeaderPadding(newHeaderPadding);
if (state == State.PULL_TO_REFRESH && headerPadding > 0) {
setState(State.RELEASE_TO_REFRESH);
image.clearAnimation();
image.startAnimation(flipAnimation);
} else if (state == State.RELEASE_TO_REFRESH && headerPadding < 0) {
setState(State.PULL_TO_REFRESH);
image.clearAnimation();
image.startAnimation(reverseFlipAnimation);
}
}
}
break;
}
return super.onTouchEvent(event);
}
private void bounceBackHeader() {
int yTranslate = state == State.REFRESHING ?
header.getHeight() - headerContainer.getHeight() :
-headerContainer.getHeight() - headerContainer.getTop();
bounceAnimation = new TranslateAnimation(
TranslateAnimation.ABSOLUTE, 0,
TranslateAnimation.ABSOLUTE, 0,
TranslateAnimation.ABSOLUTE, 0,
TranslateAnimation.ABSOLUTE, yTranslate);
bounceAnimation.setDuration(BOUNCE_ANIMATION_DURATION);
bounceAnimation.setFillEnabled(true);
bounceAnimation.setFillAfter(false);
bounceAnimation.setFillBefore(true);
// bounceAnimation.setInterpolator(new
// OvershootInterpolator(BOUNCE_OVERSHOOT_TENSION));
bounceAnimation.setAnimationListener(new HeaderAnimationListener(yTranslate));
startAnimation(bounceAnimation);
}
private void resetHeader() {
if (getFirstVisiblePosition() > 0) {
setHeaderPadding(-header.getHeight());
setState(State.PULL_TO_REFRESH);
return;
}
if (getAnimation() != null && !getAnimation().hasEnded()) {
bounceBackHeader = true;
} else {
bounceBackHeader();
}
}
private void setUiRefreshing() {
spinner.setVisibility(View.GONE);
image.clearAnimation();
image.setVisibility(View.INVISIBLE);
text.setText(refreshingText);
mLoadingThread = new LoadingThread(mLoadingHandler);
mLoadingThread.start();
isHeaderRefreshing = true;
// loadingText.setVisibility(View.VISIBLE);
}
private void setState(State state) {
this.state = state;
switch (state) {
case PULL_TO_REFRESH:
spinner.setVisibility(View.GONE);
image.setVisibility(View.VISIBLE);
text.setText(pullToRefreshText);
if (mLoadingThread != null) {
mLoadingThread.interrupt();
mLoadingThread = null;
}
isHeaderRefreshing = false;
// loadingText.setVisibility(View.GONE);
if (showLastUpdatedText && lastUpdated != -1) {
lastUpdatedTextView.setVisibility(View.VISIBLE);
lastUpdatedTextView.setText(String.format(lastUpdatedText,
lastUpdatedDateFormat.format(new Date(lastUpdated))));
}
break;
case RELEASE_TO_REFRESH:
spinner.setVisibility(View.GONE);
image.setVisibility(View.VISIBLE);
text.setText(releaseToRefreshText);
if (mLoadingThread != null) {
mLoadingThread.interrupt();
mLoadingThread = null;
}
// loadingText.setVisibility(View.GONE);
isHeaderRefreshing = false;
break;
case REFRESHING:
setUiRefreshing();
lastUpdated = System.currentTimeMillis();
if (onRefreshListener == null) {
setState(State.PULL_TO_REFRESH);
} else {
onRefreshListener.onRefresh();
}
break;
}
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
Log.i("Vingle", "hasResetHeader : " + hasResetHeader + ", t : " + t + ", oldt : " + oldt);
if (!hasResetHeader) {
if (measuredHeaderHeight > 0 && state != State.REFRESHING) {
setHeaderPadding(-measuredHeaderHeight);
}
hasResetHeader = true;
}
}
private class HeaderAnimationListener implements AnimationListener {
private int height, translation;
private State stateAtAnimationStart;
public HeaderAnimationListener(int translation) {
this.translation = translation;
}
@Override
public void onAnimationStart(Animation animation) {
stateAtAnimationStart = state;
android.view.ViewGroup.LayoutParams lp = getLayoutParams();
height = lp.height;
lp.height = getHeight() - translation;
setLayoutParams(lp);
if (scrollbarEnabled) {
setVerticalScrollBarEnabled(false);
}
}
@Override
public void onAnimationEnd(Animation animation) {
setHeaderPadding(stateAtAnimationStart == State.REFRESHING ? 0 : -measuredHeaderHeight
- headerContainer.getTop());
// setSelection(0);
android.view.ViewGroup.LayoutParams lp = getLayoutParams();
lp.height = height;
setLayoutParams(lp);
if (scrollbarEnabled) {
setVerticalScrollBarEnabled(true);
}
if (bounceBackHeader) {
bounceBackHeader = false;
postDelayed(new Runnable() {
@Override
public void run() {
resetHeader();
}
}, BOUNCE_ANIMATION_DELAY);
} else if (stateAtAnimationStart != State.REFRESHING) {
setState(State.PULL_TO_REFRESH);
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
}
private class PTROnGlobalLayoutListener implements OnGlobalLayoutListener {
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {
int initialHeaderHeight = header.getHeight();
if (initialHeaderHeight > 0) {
measuredHeaderHeight = initialHeaderHeight;
if (measuredHeaderHeight > 0 && state != State.REFRESHING) {
setHeaderPadding(-measuredHeaderHeight);
requestLayout();
}
}
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
// //////////////////////////////////////////////////////////////////
// Loading Thread & Handler */
// //////////////////////////////////////////////////////////////////
private class LoadingThread extends Thread {
Handler mHandler;
public LoadingThread(Handler mHandler) {
this.mHandler = mHandler;
}
@Override
public void run() {
try {
while (true) {
Message msg_loading_0 = mHandler.obtainMessage(LOADINGZERO);
msg_loading_0.obj = new WeakReference<TextView>(
text);
mHandler.sendMessage(msg_loading_0);
Thread.sleep(LOADINGBUFFER);
Message msg_loading_1 = mHandler.obtainMessage(LOADINGONE);
msg_loading_1.obj = new WeakReference<TextView>(
text);
mHandler.sendMessage(msg_loading_1);
Thread.sleep(LOADINGBUFFER);
Message msg_loading_2 = mHandler.obtainMessage(LOADINGTWO);
msg_loading_2.obj = new WeakReference<TextView>(
text);
mHandler.sendMessage(msg_loading_2);
Thread.sleep(LOADINGBUFFER);
Message msg_loading_3 = mHandler
.obtainMessage(LOADINGTHREE);
msg_loading_3.obj = new WeakReference<TextView>(
text);
mHandler.sendMessage(msg_loading_3);
Thread.sleep(LOADINGBUFFER);
}
} catch (InterruptedException e) {
// do nothing.
}
}
}
private static Handler mLoadingHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@SuppressWarnings("unchecked")
TextView tv = ((WeakReference<TextView>) msg.obj).get();
if (tv == null)
return;
switch (msg.what) {
case LOADINGZERO:
tv.setText("Loading");
break;
case LOADINGONE:
tv.setText("Loading.");
break;
case LOADINGTWO:
tv.setText("Loading..");
break;
case LOADINGTHREE:
tv.setText("Loading...");
break;
default:
break;
}
}
};
// private class PTROnItemClickListener implements OnItemClickListener {
//
// @Override
// public void onItemClick(AdapterView<?> adapterView, View view, int
// position, long id){
// hasResetHeader = false;
//
// if(onItemClickListener != null && state == State.PULL_TO_REFRESH){
// // Passing up onItemClick. Correct position with the number of header
// views
// onItemClickListener.onItemClick(adapterView, view, position -
// getHeaderViewsCount(), id);
// }
// }
// }
//
// private class PTROnItemLongClickListener implements
// OnItemLongClickListener{
//
// @Override
// public boolean onItemLongClick(AdapterView<?> adapterView, View view, int
// position, long id){
// hasResetHeader = false;
//
// if(onItemLongClickListener != null && state == State.PULL_TO_REFRESH){
// // Passing up onItemLongClick. Correct position with the number of header
// views
// return onItemLongClickListener.onItemLongClick(adapterView, view,
// position - getHeaderViewsCount(), id);
// }
//
// return false;
// }
// }
}