一、通过上拉加载下拉刷新方式实现
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; }}