Files
EhViewer/src/com/hippo/ehviewer/widget/PullListView.java
T
2014-05-01 20:05:09 +08:00

593 lines
21 KiB
Java

package com.hippo.ehviewer.widget;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.util.Ui;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.view.animation.Transformation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
public class PullListView extends LinearLayout implements AbsListView.OnScrollListener{
@SuppressWarnings("unused")
private static final String TAG = "PullListView";
private final static int HEADER_PULL_TO_REFRESH = 0;
private final static int HEADER_RELEASE_TO_REFRESH = 1;
private final static int HEADER_REFRESHING = 2;
private final static int HEADER_DONE = 3;
private final static int HEADER_CANCEL = 4;
private final static int FOOTER_REFRESHING = 0;
private final static int FOOTER_SUCCESS = 1;
private final static int FOOTER_FAIL = 2;
// arrow animation
private RotateAnimation mAnimation;
private RotateAnimation mReverseAnimation;
// Header animation
private Animation mRefreshAnimation;
private Animation mOverAnimation;
private ListView mListView;
private LinearLayout mHeader;
private LinearLayout mFooter;
private TextView mTipTextView;
private ImageView mArrowImageView;
private ProgressBar mProgressBar;
private TextView mFooterTipTextView;
private ProgressBar mFooterProgressBar;
// Make sure only one pull event
private boolean isHeaderRecored = false;
private boolean isFooterRecored = false;
// Header String to show
private String mHeaderPullStr;
private String mHeaderReleaseStr;
private String mHeaderRefreshStr;
private String mHeaderDoneStr;
private String mHeaderCancelStr;
// Footer String to show
private String mFooterRefreshStr;
private String mFooterSuccessStr;
private String mFooterFailStr;
// Header
private int headerHeight;
private int headerOriginalLeftPadding;
private int headerOriginalTopPadding;
private int headerOriginalRightPadding;
private int headerOriginalBottomPadding;
private int releaseTopPadding;
private int doneTopPadding;
private int pullThreshold;
private int pullOffestMax;
// Footer
private boolean isActionWhenShow = true;
private int startY;
private int headerState = HEADER_DONE;
private boolean isBack;
private int footerState = FOOTER_SUCCESS;
private OnHeaderRefreshListener headerRefreshListener;
private OnFooterRefreshListener footerRefreshListener;
// To Check scroll to top
// Store y of last point
private int lastY;
private boolean mHeaderEnabled = true;
@SuppressWarnings("unused")
private boolean mFooterEnabled = true;
public interface OnHeaderRefreshListener {
public void onHeaderRefresh();
public void onHeaderRefresh(Object obj);
}
public interface OnFooterRefreshListener {
public void onFooterRefresh();
}
public PullListView(Context context) {
super(context);
init(context);
}
public PullListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public PullListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
// Set arrow animation
mAnimation = new RotateAnimation(0, -180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mAnimation.setInterpolator(new LinearInterpolator());
mAnimation.setDuration(100);
mAnimation.setFillAfter(true);
mReverseAnimation = new RotateAnimation(-180, 0,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mReverseAnimation.setInterpolator(new LinearInterpolator());
mReverseAnimation.setDuration(100);
mReverseAnimation.setFillAfter(true);
// Set header animation
mRefreshAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mHeader.setPadding(headerOriginalLeftPadding,
(int)((1 - interpolatedTime) * releaseTopPadding + (interpolatedTime * headerOriginalTopPadding)),
headerOriginalRightPadding, headerOriginalBottomPadding);
mHeader.invalidate();
}
};
mRefreshAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
setEnabled(false);
}
@Override
public void onAnimationEnd(Animation animation) {
setEnabled(true);
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
mRefreshAnimation.setDuration(100);
mOverAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mHeader.setPadding(headerOriginalLeftPadding,
(int)((1 - interpolatedTime) * doneTopPadding - (interpolatedTime * headerHeight)),
headerOriginalRightPadding, headerOriginalBottomPadding);
mHeader.invalidate();
}
};
mOverAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
setEnabled(false);
}
@Override
public void onAnimationEnd(Animation animation) {
setEnabled(true);
if (headerState == HEADER_CANCEL) {
headerState = HEADER_DONE;
isHeaderRecored = false;
isBack = false;
}
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
mOverAnimation.setDuration(200);
View view = LayoutInflater.from(context).inflate(R.layout.pull_list_view, this);
mFooter = (LinearLayout)LayoutInflater.from(context).inflate(R.layout.pull_list_view_footer, null);
mFooter.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
footerRefresh();
}
});
mListView = (ListView)view.findViewById(R.id.list_view);
mListView.setOnScrollListener(this);
mListView.addFooterView(mFooter);
mFooterTipTextView = (TextView)mFooter.findViewById(R.id.footer_tip_text);
mFooterProgressBar = (ProgressBar)mFooter.findViewById(R.id.footer_progressBar);
mHeader = (LinearLayout)view.findViewById(R.id.header);
mArrowImageView = (ImageView) mHeader.findViewById(R.id.header_arrow_image);
mArrowImageView.setMinimumWidth(50);
mArrowImageView.setMinimumHeight(50);
mProgressBar = (ProgressBar)mHeader.findViewById(R.id.header_progressBar);
mTipTextView = (TextView)mHeader.findViewById(R.id.header_tip_text);
headerOriginalLeftPadding = mHeader.getPaddingLeft();
headerOriginalTopPadding = mHeader.getPaddingTop();
headerOriginalRightPadding = mHeader.getPaddingRight();
headerOriginalBottomPadding = mHeader.getPaddingBottom();
Ui.measureView(mHeader);
headerHeight = mHeader.getMeasuredHeight();
pullThreshold = headerHeight;
pullOffestMax = (int)(headerHeight * 2.3f);
mHeader.setPadding(headerOriginalLeftPadding, -headerHeight, headerOriginalRightPadding, headerOriginalBottomPadding);
mHeader.invalidate();
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (firstVisibleItem + visibleItemCount == totalItemCount
&& isActionWhenShow && totalItemCount > 1)
footerRefresh();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (!mHeaderEnabled || isHeaderRecored || isFooterRecored)
return false;
final int action = event.getAction() & MotionEvent.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
lastY = (int)event.getY();
return false;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
return false;
case MotionEvent.ACTION_MOVE:
int y = (int)event.getY();
int offsetY = y - lastY;
lastY = y;
if (offsetY > 0 && isListAtTop())
return true;
else
return false;
default:
return super.onInterceptTouchEvent(event);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getAction() & MotionEvent.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
if (mHeaderEnabled && !isHeaderRecored && !isFooterRecored) {
isHeaderRecored = true;
startY = (int) event.getY();
}
break;
case MotionEvent.ACTION_CANCEL:
headerState = HEADER_DONE;
isHeaderRecored = false;
isBack = false;
changeHeaderViewByState();
break;
case MotionEvent.ACTION_UP:
if (headerState == HEADER_PULL_TO_REFRESH) {
headerState = HEADER_CANCEL;
changeHeaderViewByState();
}
else if (headerState == HEADER_RELEASE_TO_REFRESH) {
headerState = HEADER_REFRESHING;
changeHeaderViewByState();
onHeaderRefresh();
} else if (headerState == HEADER_DONE) {
isHeaderRecored = false;
isBack = false;
changeHeaderViewByState();
}
break;
case MotionEvent.ACTION_MOVE:
int tempY = (int)event.getY();
if (mHeaderEnabled && !isHeaderRecored && !isFooterRecored) {
isHeaderRecored = true;
startY = tempY;
}
if (headerState != HEADER_REFRESHING && isHeaderRecored) {
int offsetY = getShowOffset(tempY - startY, pullOffestMax);
if (headerState == HEADER_RELEASE_TO_REFRESH) {
// 往上推,推到屏幕足够掩盖head的程度,但还没有全部掩盖
if (offsetY < pullThreshold && offsetY > 0) {
headerState = HEADER_PULL_TO_REFRESH;
changeHeaderViewByState();
}
// 一下子推到顶
else if (offsetY <= 0) {
headerState = HEADER_DONE;
startY = tempY;
}
} else if (headerState == HEADER_PULL_TO_REFRESH) {
// 下拉到可以进入RELEASE_TO_REFRESH的状态
if (offsetY >= pullThreshold) {
headerState = HEADER_RELEASE_TO_REFRESH;
isBack = true;
changeHeaderViewByState();
}
// 上推到顶了
else if (offsetY <= 0) {
headerState = HEADER_DONE;
startY = tempY;
}
} else if (headerState == HEADER_DONE) {
if (offsetY > 0) {
headerState = HEADER_PULL_TO_REFRESH;
changeHeaderViewByState();
} else {
startY = tempY;
}
}
// 更新headView的size
if (headerState == HEADER_PULL_TO_REFRESH) {
int topPadding = (int)(offsetY - headerHeight);
mHeader.setPadding(headerOriginalLeftPadding, topPadding, headerOriginalRightPadding, headerOriginalBottomPadding);
mHeader.invalidate();
}
// 更新headView的paddingTop
if (headerState == HEADER_RELEASE_TO_REFRESH) {
int topPadding = (int)(offsetY - headerHeight);
mHeader.setPadding(headerOriginalLeftPadding, topPadding, headerOriginalRightPadding, headerOriginalBottomPadding);
mHeader.invalidate();
}
}
break;
}
return true;
}
private int getShowOffset(int eventOffset, int target) {
if (eventOffset <= 0) // avoid divide zero
return 0;
else
return (target * eventOffset) / (eventOffset + target);
}
// Refresh header UI
private void changeHeaderViewByState() {
switch (headerState) {
case HEADER_RELEASE_TO_REFRESH:
mArrowImageView.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.GONE);
mTipTextView.setVisibility(View.VISIBLE);
mArrowImageView.clearAnimation();
mArrowImageView.startAnimation(mAnimation);
mTipTextView.setText(mHeaderReleaseStr);
break;
case HEADER_PULL_TO_REFRESH:
mProgressBar.setVisibility(View.GONE);
mTipTextView.setVisibility(View.VISIBLE);
mArrowImageView.clearAnimation();
mArrowImageView.setVisibility(View.VISIBLE);
if (isBack) {
isBack = false;
mArrowImageView.clearAnimation();
mArrowImageView.startAnimation(mReverseAnimation);
}
mTipTextView.setText(mHeaderPullStr);
break;
case HEADER_REFRESHING:
releaseTopPadding = mHeader.getPaddingTop();
mHeader.startAnimation(mRefreshAnimation);
mProgressBar.setVisibility(View.VISIBLE);
mArrowImageView.clearAnimation();
mArrowImageView.setVisibility(View.GONE);
mTipTextView.setText(mHeaderRefreshStr);
break;
case HEADER_DONE:
case HEADER_CANCEL:
doneTopPadding = mHeader.getPaddingTop();
mHeader.startAnimation(mOverAnimation);
mProgressBar.setVisibility(View.GONE);
mArrowImageView.setVisibility(View.GONE);
mArrowImageView.clearAnimation();
// 此处更换图标
mArrowImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow);
if (headerState == HEADER_DONE)
mTipTextView.setText(mHeaderDoneStr);
else
mTipTextView.setText(mHeaderCancelStr);
break;
}
}
// Refresh footer UI
private void changeFooterViewByState() {
switch (footerState) {
case FOOTER_REFRESHING:
mFooterTipTextView.setText(mFooterRefreshStr);
mFooterProgressBar.setVisibility(View.VISIBLE);
break;
case FOOTER_SUCCESS:
mFooterTipTextView.setText(mFooterSuccessStr);
mFooterProgressBar.setVisibility(View.GONE);
break;
case FOOTER_FAIL:
mFooterTipTextView.setText(mFooterFailStr);
mFooterProgressBar.setVisibility(View.GONE);
break;
}
}
public ListView getListView() {
return mListView;
}
// Header
public void setHeaderEnabled(boolean enabled) {
mHeaderEnabled = enabled;
}
public void setHeaderString(String pullStr, String releaseStr, String refreshStr, String doneStr, String cancelStr) {
mHeaderPullStr = pullStr;
mHeaderReleaseStr = releaseStr;
mHeaderRefreshStr = refreshStr;
mHeaderDoneStr = doneStr;
mHeaderCancelStr = cancelStr;
switch (headerState) {
case HEADER_PULL_TO_REFRESH:
mTipTextView.setText(mHeaderPullStr);
break;
case HEADER_RELEASE_TO_REFRESH:
mTipTextView.setText(mHeaderReleaseStr);
break;
case HEADER_REFRESHING:
mTipTextView.setText(mHeaderRefreshStr);
break;
case HEADER_DONE:
mTipTextView.setText(mHeaderDoneStr);
break;
case HEADER_CANCEL:
mTipTextView.setText(mHeaderCancelStr);
break;
}
}
public boolean clickHeaderRefresh(Object obj) {
if (!mHeaderEnabled || isHeaderRecored || isFooterRecored)
return false;
isHeaderRecored = true;
headerState = HEADER_REFRESHING;
changeHeaderViewByState();
onHeaderRefresh(obj);
return true;
}
public void setOnRefreshListener(OnHeaderRefreshListener refreshListener) {
this.headerRefreshListener = refreshListener;
}
private void onHeaderRefresh() {
if (headerRefreshListener != null) {
headerRefreshListener.onHeaderRefresh();
}
}
private void onHeaderRefresh(Object obj) {
if (headerRefreshListener != null) {
headerRefreshListener.onHeaderRefresh(obj);
}
}
public boolean isHeaderRefreshing() {
return headerState == HEADER_REFRESHING;
}
public void onHeaderRefreshComplete() {
headerState = HEADER_DONE;
isHeaderRecored = false;
isBack = false;
changeHeaderViewByState();
}
// Footer
public void setFooterEnabled(boolean enabled) {
// TODO Remove footer if false
mFooterEnabled = enabled;
}
public void setFooterString(String refreshStr, String successStr, String failStr) {
mFooterRefreshStr = refreshStr;
mFooterSuccessStr = successStr;
mFooterFailStr = failStr;
switch (footerState) {
case FOOTER_REFRESHING:
mFooterTipTextView.setText(mFooterRefreshStr);
break;
case FOOTER_SUCCESS:
mFooterTipTextView.setText(mFooterSuccessStr);
break;
case FOOTER_FAIL:
mFooterTipTextView.setText(mFooterFailStr);
break;
}
}
public void footerRefresh() {
if (!mHeaderEnabled || isHeaderRecored || isFooterRecored)
return;
isFooterRecored = true;
footerState = FOOTER_REFRESHING;
changeFooterViewByState();
if (footerRefreshListener != null) {
footerRefreshListener.onFooterRefresh();
}
}
public void setOnFooterRefreshListener(OnFooterRefreshListener footerRefreshListener) {
this.footerRefreshListener = footerRefreshListener;
}
public void onFooterRefreshComplete(boolean isSuccess) {
if (isSuccess) {
footerState = FOOTER_SUCCESS;
isActionWhenShow = true;
}
else {
footerState = FOOTER_FAIL;
isActionWhenShow = false;
}
isFooterRecored = false;
changeFooterViewByState();
}
public boolean isFooterRefreshing() {
return footerState == FOOTER_REFRESHING;
}
public void onFooterRefreshComplete(boolean isSuccess, boolean actionWhenShow) {
if (isSuccess)
footerState = FOOTER_SUCCESS;
else
footerState = FOOTER_FAIL;
isActionWhenShow = actionWhenShow;
isFooterRecored = false;
changeFooterViewByState();
}
public void setActionWhenShow(boolean actionWhenShow) {
isActionWhenShow = actionWhenShow;
}
private boolean isListAtTop() {
if(mListView.getChildCount() == 0)
return true;
return mListView.getFirstVisiblePosition() == 0
&& mListView.getChildAt(0).getTop() == 0;
}
// Get header width and height
}