Created
October 13, 2014 04:51
-
-
Save cbedoy/1f504943e18c580c42b2 to your computer and use it in GitHub Desktop.
StrechableListView
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cbedoy.stretchablelistview; | |
/** | |
* Created by Carlos on 27/09/2014. | |
*/ | |
import android.content.Context; | |
import android.util.AttributeSet; | |
import android.util.Log; | |
import android.view.LayoutInflater; | |
import android.view.MotionEvent; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.view.animation.LinearInterpolator; | |
import android.view.animation.RotateAnimation; | |
import android.widget.AbsListView; | |
import android.widget.ImageView; | |
import android.widget.ListAdapter; | |
import android.widget.ListView; | |
import android.widget.ProgressBar; | |
import android.widget.RelativeLayout; | |
import android.widget.TextView; | |
import android.widget.AbsListView.OnScrollListener; | |
public class StretchableListView extends ListView implements OnScrollListener { | |
private static final int TAP_TO_REFRESH = 1; | |
private static final int PULL_TO_REFRESH = 2; | |
private static final int RELEASE_TO_REFRESH = 3; | |
private static final int REFRESHING = 4; | |
private static final String TAG = "PullToRefreshListView"; | |
private OnRefreshListener mOnRefreshListener; | |
/** | |
* Listener that will receive notifications every time the list scrolls. | |
*/ | |
private OnScrollListener mOnScrollListener; | |
private LayoutInflater mInflater; | |
private RelativeLayout mRefreshView; | |
private int mCurrentScrollState; | |
private int mRefreshState; | |
private RotateAnimation mFlipAnimation; | |
private RotateAnimation mReverseFlipAnimation; | |
private int mRefreshViewHeight; | |
private int mRefreshOriginalTopPadding; | |
private int mLastMotionY; | |
private boolean mBounceHack; | |
public StretchableListView(Context context) { | |
super(context); | |
init(context); | |
} | |
public StretchableListView(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
init(context); | |
} | |
public StretchableListView(Context context, AttributeSet attrs, int defStyle) { | |
super(context, attrs, defStyle); | |
init(context); | |
} | |
private void init(Context context) { | |
// Load all of the animations we need in code rather than through XML | |
mFlipAnimation = new RotateAnimation(0, -180, | |
RotateAnimation.RELATIVE_TO_SELF, 0.5f, | |
RotateAnimation.RELATIVE_TO_SELF, 0.5f); | |
mFlipAnimation.setInterpolator(new LinearInterpolator()); | |
mFlipAnimation.setDuration(250); | |
mFlipAnimation.setFillAfter(true); | |
mReverseFlipAnimation = new RotateAnimation(-180, 0, | |
RotateAnimation.RELATIVE_TO_SELF, 0.5f, | |
RotateAnimation.RELATIVE_TO_SELF, 0.5f); | |
mReverseFlipAnimation.setInterpolator(new LinearInterpolator()); | |
mReverseFlipAnimation.setDuration(250); | |
mReverseFlipAnimation.setFillAfter(true); | |
mInflater = (LayoutInflater) context.getSystemService( | |
Context.LAYOUT_INFLATER_SERVICE); | |
mRefreshView = (RelativeLayout) mInflater.inflate( | |
R.layout.easter_egg_view, this, false); | |
mRefreshView.setOnClickListener(new OnClickRefreshListener()); | |
mRefreshOriginalTopPadding = mRefreshView.getPaddingTop(); | |
mRefreshState = TAP_TO_REFRESH; | |
addHeaderView(mRefreshView); | |
super.setOnScrollListener(this); | |
measureView(mRefreshView); | |
mRefreshViewHeight = mRefreshView.getMeasuredHeight(); | |
} | |
@Override | |
protected void onAttachedToWindow() { | |
super.onAttachedToWindow(); | |
setSelection(1); | |
} | |
@Override | |
public void setAdapter(ListAdapter adapter) { | |
super.setAdapter(adapter); | |
setSelection(1); | |
} | |
/** | |
* Set the listener that will receive notifications every time the list | |
* scrolls. | |
* | |
* @param l The scroll listener. | |
*/ | |
@Override | |
public void setOnScrollListener(AbsListView.OnScrollListener l) { | |
mOnScrollListener = l; | |
} | |
/** | |
* Register a callback to be invoked when this list should be refreshed. | |
* | |
* @param onRefreshListener The callback to run. | |
*/ | |
public void setOnRefreshListener(OnRefreshListener onRefreshListener) { | |
mOnRefreshListener = onRefreshListener; | |
} | |
/** | |
* Set a text to represent when the list was last updated. | |
* @param lastUpdated Last updated at. | |
*/ | |
public void setLastUpdated(CharSequence lastUpdated) { | |
if (lastUpdated != null) { | |
} else { | |
} | |
} | |
@Override | |
public boolean onTouchEvent(MotionEvent event) { | |
final int y = (int) event.getY(); | |
mBounceHack = false; | |
switch (event.getAction()) { | |
case MotionEvent.ACTION_UP: | |
if (!isVerticalScrollBarEnabled()) { | |
setVerticalScrollBarEnabled(true); | |
} | |
if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) { | |
if ((mRefreshView.getBottom() >= mRefreshViewHeight | |
|| mRefreshView.getTop() >= 0) | |
&& mRefreshState == RELEASE_TO_REFRESH) { | |
// Initiate the refresh | |
mRefreshState = REFRESHING; | |
prepareForRefresh(); | |
onRefresh(); | |
} else if (mRefreshView.getBottom() < mRefreshViewHeight | |
|| mRefreshView.getTop() <= 0) { | |
// Abort refresh and scroll down below the refresh view | |
resetHeader(); | |
setSelection(1); | |
} | |
} | |
break; | |
case MotionEvent.ACTION_DOWN: | |
mLastMotionY = y; | |
break; | |
case MotionEvent.ACTION_MOVE: | |
applyHeaderPadding(event); | |
break; | |
} | |
return super.onTouchEvent(event); | |
} | |
private void applyHeaderPadding(MotionEvent ev) { | |
// getHistorySize has been available since API 1 | |
int pointerCount = ev.getHistorySize(); | |
for (int p = 0; p < pointerCount; p++) { | |
if (mRefreshState == RELEASE_TO_REFRESH) { | |
if (isVerticalFadingEdgeEnabled()) { | |
setVerticalScrollBarEnabled(false); | |
} | |
int historicalY = (int) ev.getHistoricalY(p); | |
// Calculate the padding to apply, we divide by 1.7 to | |
// simulate a more resistant effect during pull. | |
int topPadding = (int) (((historicalY - mLastMotionY) | |
- mRefreshViewHeight) / 1.7); | |
mRefreshView.setPadding( | |
mRefreshView.getPaddingLeft(), | |
topPadding, | |
mRefreshView.getPaddingRight(), | |
mRefreshView.getPaddingBottom()); | |
} | |
} | |
} | |
/** | |
* Sets the header padding back to original size. | |
*/ | |
private void resetHeaderPadding() { | |
mRefreshView.setPadding( | |
mRefreshView.getPaddingLeft(), | |
mRefreshOriginalTopPadding, | |
mRefreshView.getPaddingRight(), | |
mRefreshView.getPaddingBottom()); | |
} | |
/** | |
* Resets the header to the original state. | |
*/ | |
private void resetHeader() { | |
if (mRefreshState != TAP_TO_REFRESH) { | |
mRefreshState = TAP_TO_REFRESH; | |
resetHeaderPadding(); | |
} | |
} | |
private void measureView(View child) { | |
ViewGroup.LayoutParams p = child.getLayoutParams(); | |
if (p == null) { | |
p = new ViewGroup.LayoutParams( | |
ViewGroup.LayoutParams.FILL_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); | |
} | |
@Override | |
public void onScroll(AbsListView view, int firstVisibleItem, | |
int visibleItemCount, int totalItemCount) { | |
// When the refresh view is completely visible, change the text to say | |
// "Release to refresh..." and flip the arrow drawable. | |
if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL | |
&& mRefreshState != REFRESHING) { | |
if (firstVisibleItem == 0) { | |
if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20 | |
|| mRefreshView.getTop() >= 0) | |
&& mRefreshState != RELEASE_TO_REFRESH) { | |
mRefreshState = RELEASE_TO_REFRESH; | |
} else if (mRefreshView.getBottom() < mRefreshViewHeight + 20 | |
&& mRefreshState != PULL_TO_REFRESH) { | |
if (mRefreshState != TAP_TO_REFRESH) { | |
} | |
mRefreshState = PULL_TO_REFRESH; | |
} | |
} else { | |
resetHeader(); | |
} | |
} else if (mCurrentScrollState == SCROLL_STATE_FLING | |
&& firstVisibleItem == 0 | |
&& mRefreshState != REFRESHING) { | |
setSelection(1); | |
mBounceHack = true; | |
} else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) { | |
setSelection(1); | |
} | |
if (mOnScrollListener != null) { | |
mOnScrollListener.onScroll(view, firstVisibleItem, | |
visibleItemCount, totalItemCount); | |
} | |
} | |
@Override | |
public void onScrollStateChanged(AbsListView view, int scrollState) { | |
mCurrentScrollState = scrollState; | |
if (mCurrentScrollState == SCROLL_STATE_IDLE) { | |
mBounceHack = false; | |
} | |
if (mOnScrollListener != null) { | |
mOnScrollListener.onScrollStateChanged(view, scrollState); | |
} | |
} | |
public void prepareForRefresh() { | |
resetHeaderPadding(); | |
mRefreshState = REFRESHING; | |
} | |
public void onRefresh() { | |
Log.d(TAG, "onRefresh"); | |
if (mOnRefreshListener != null) { | |
mOnRefreshListener.onRefresh(); | |
} | |
} | |
/** | |
* Resets the list to a normal state after a refresh. | |
* @param lastUpdated Last updated at. | |
*/ | |
public void onRefreshComplete(CharSequence lastUpdated) { | |
setLastUpdated(lastUpdated); | |
onRefreshComplete(); | |
} | |
/** | |
* Resets the list to a normal state after a refresh. | |
*/ | |
public void onRefreshComplete() { | |
Log.d(TAG, "onRefreshComplete"); | |
resetHeader(); | |
// If refresh view is visible when loading completes, scroll down to | |
// the next item. | |
if (getFirstVisiblePosition() == 0) { | |
invalidateViews(); | |
setSelection(1); | |
} | |
} | |
/** | |
* Invoked when the refresh view is clicked on. This is mainly used when | |
* there's only a few items in the list and it's not possible to drag the | |
* list. | |
*/ | |
private class OnClickRefreshListener implements OnClickListener { | |
@Override | |
public void onClick(View v) { | |
if (mRefreshState != REFRESHING) { | |
prepareForRefresh(); | |
onRefresh(); | |
} | |
} | |
} | |
/** | |
* Interface definition for a callback to be invoked when list should be | |
* refreshed. | |
*/ | |
public interface OnRefreshListener { | |
/** | |
* Called when the list should be refreshed. | |
* <p> | |
* A call to {@link #onRefreshComplete()} is | |
* expected to indicate that the refresh has completed. | |
*/ | |
public void onRefresh(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment