一、通过上拉加载下拉刷新方式实现
public class RefreshListView extends ListView implements AbsListView.OnScrollListener {
private final String TAG ="RefreshListView";
private View mRefreshHeaderView;
private int MAX_HEIGHT = 0;
private int mPaddingTop;
private AbsListView.OnScrollListener mOnScrollListener;
private float mLastTouchY;
private boolean isScrollAtTop;
private volatile boolean isRefreshing;
private View mRefreshFooterView;
private int mPaddingBottom;
private boolean isScrollAtBottom;
public RefreshListView(Context context) {
this(context,null);
}
public RefreshListView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHeaderView();
setOnScrollListener(this);
}
private void initHeaderView() {
mRefreshHeaderView = createFooterOrHeaderView("下拉刷新");
mRefreshFooterView = createFooterOrHeaderView("加载更多");
addHeaderView(mRefreshHeaderView);
addFooterView(mRefreshFooterView);
MAX_HEIGHT = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,150,getContext().getResources().getDisplayMetrics());
final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); //宽度未指定,当然也可以设置为E(-1,EXACTLY)
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(MAX_HEIGHT, MeasureSpec.AT_MOST); //高度最大值限制为150dip
mRefreshHeaderView.measure(widthMeasureSpec,heightMeasureSpec);
mRefreshFooterView.measure(widthMeasureSpec,heightMeasureSpec);
final int headerHeight = mRefreshHeaderView.getMeasuredHeight();
mPaddingTop = - headerHeight;
mRefreshHeaderView.setPadding(0,mPaddingTop,0,0);
final int footerHeight = mRefreshHeaderView.getMeasuredHeight();
mPaddingBottom = - footerHeight;
mRefreshFooterView.setPadding(0,0,0,mPaddingBottom);
}
private LinearLayout createFooterOrHeaderView(String msg){
final Context context = getContext();
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
final int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP ,10,metrics );
final int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP ,40, metrics);
LinearLayout headerPanel = new LinearLayout(context);
headerPanel.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT));
headerPanel.setOrientation(LinearLayout.HORIZONTAL);
headerPanel.setBackgroundColor(0x33ff0000);
headerPanel.setGravity(Gravity.CENTER);
headerPanel.setAccessibilityDelegate(new AccessibilityDelegate());
ProgressBar progressBar = new ProgressBar(context);
final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(size, size);
params.topMargin = padding;
params.bottomMargin = padding;
progressBar.setLayoutParams(params);
progressBar.setIndeterminate(true);
progressBar.setFocusable(true);
TextView tv = new TextView(context);
tv.setTextSize(12);
tv.setText(msg);
tv.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
tv.setId(android.R.id.text1);
headerPanel.addView(progressBar);
headerPanel.addView(tv);
return headerPanel;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if( !isScrollAtTop &&!isScrollAtBottom || isRefreshing ) {
mLastTouchY = ev.getY();
return super.onTouchEvent(ev);
}
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
mLastTouchY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
startHandlePadding(ev);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
endHandlePadding(ev);
break;
}
return super.onTouchEvent(ev);
}
private boolean endHandlePadding(MotionEvent ev) {
if(isScrollAtTop) {
float deltaY = (ev.getY() - mLastTouchY)/2;
if (deltaY < 0) return false;
if (deltaY < (MAX_HEIGHT *3) / 5) {
mRefreshHeaderView.setPadding(0, mPaddingTop, 0, 0);
} else {
isRefreshing = true;
mRefreshHeaderView.setPadding(0, mPaddingTop + MAX_HEIGHT, 0, 0);
mRefreshHeaderView.postDelayed(finishTask, 1500);
}
}else if(isScrollAtBottom){
float deltaY = (-ev.getY() + mLastTouchY)/2;
if (deltaY < 0) return false;
if (deltaY < (MAX_HEIGHT*3) / 5) {
mRefreshFooterView.setPadding(0, 0, 0, mPaddingBottom);
} else {
isRefreshing = true;
mRefreshFooterView.setPadding(0, 0, 0, MAX_HEIGHT + mPaddingBottom);
mRefreshFooterView.postDelayed(finishTask, 1500);
}
}
return true;
}
private boolean startHandlePadding(MotionEvent ev) {
if(isScrollAtTop) {
float deltaY = (ev.getY() - mLastTouchY)/2;// 这里 乘以 0.5作为弹性系数
if (deltaY < 0) return false;
deltaY = Math.min(deltaY, MAX_HEIGHT);
Log.d(TAG, " deltaY=" + deltaY);
mRefreshHeaderView.setPadding(0, (int) deltaY + mPaddingTop, 0, 0);
return true;
}else if(isScrollAtBottom){
float deltaY = (-ev.getY() + mLastTouchY)/2 ; // 这里 乘以 0.5作为弹性系数
if (deltaY < 0) return false;
deltaY = Math.min(deltaY, MAX_HEIGHT);
Log.d(TAG, " deltaY=" + deltaY);
mRefreshFooterView.setPadding(0, 0, 0, (int) deltaY + mPaddingBottom);
return true;
}
return false;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if(mOnScrollListener!=null){
mOnScrollListener.onScrollStateChanged(view,scrollState);
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if(mOnScrollListener!=null){
mOnScrollListener.onScroll(view,firstVisibleItem,visibleItemCount,totalItemCount);
}
isScrollAtTop = (firstVisibleItem==0);
isScrollAtBottom = (totalItemCount==(firstVisibleItem+visibleItemCount));
onDatasetChanged();
}
private final Runnable finishTask = new Runnable() {
@Override
public void run() {
isRefreshing = false;
mRefreshHeaderView.setPadding(0, mPaddingTop, 0, 0);
mRefreshFooterView.setPadding(0, 0, 0, mPaddingBottom);
}
};
@Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
this.onDatasetChanged();
}
private void onDatasetChanged() {
final int pos = this.getFirstVisiblePosition() + this.getHeaderViewsCount();
final ListAdapter adapter = this.getAdapter();
if(adapter==null) return;
if(pos>adapter.getCount()) return;
final String item = (String) adapter.getItem(pos);
if(TextUtils.isEmpty(item)|| item.trim().length()>1) return;
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
onDatasetChanged();
}
}
二、通过重写overScrollBy实现
/**
* Created by Noah on 2016/1/16.
*/
public class BounceListView extends ListView {
private static final float MAX_Y_OVERSCROLL_DISTANCE = 200;
private float mMaxYOverscrollDistance;
public BounceListView(Context context) {
this(context, null);
}
public BounceListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BounceListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initBounceListView();
}
private void initBounceListView(){
final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
final float density = metrics.density;
mMaxYOverscrollDistance = (int) (density * MAX_Y_OVERSCROLL_DISTANCE);
}
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, (int)mMaxYOverscrollDistance, isTouchEvent);
}
/**
* 设置本App所有的ListView弹性粒度
* @param ctx
* @param size
* @return
*/
public boolean configGlobalMaxOverScrollDistance(Context ctx,int size)
{
try {
final DisplayMetrics metrics = ctx.getResources().getDisplayMetrics();
final float density = metrics.density;
int value = (int) (density * size);
mMaxYOverscrollDistance = value;
ViewConfiguration config = ViewConfiguration.get(ctx);
Field mOverscrollDistance = ViewConfiguration.class.getDeclaredField("mOverscrollDistance");
if(!mOverscrollDistance.isAccessible() || !Modifier.isPublic(mOverscrollDistance.getModifiers()))
{
mOverscrollDistance.setAccessible(true);
}
mOverscrollDistance.setInt(config,value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
}