关于这方面的文章百度下有很多,我就只写写我自己实现过程。
我觉得学习一门语言不是做了几个项目就可以认为自己会了,这只是暂时的,若没有笔记,时间长了,你是怎么解决某些问题,估计连你自己都忘了,又得费时费力的重新研究~ 这就是我为什么写这篇文章的原因。
现在公司做Android是用AndroidStudio来做的,AndroidStudio开发是需要搞gradle 的,有点麻烦,我就直接上Eclipse上做些测试例子~
进入正文:
下拉的刷新的实现主要是基于ListView这个控件扩展来的。ListView的数据需要由“适配器”来提供,这个跟之前的web开发有些差异。以前做集合控件开发时,将一些有规律的数据直接与控件绑定就可以显示了。但Android却不行,为什么呢?因为ListView是个多功能控件,它能将不同数据表现实现都在一个集合中展示。那不同的形式要在一个容器中展示就涉及到“适配”的问题。
(什么的适配?我的理解就是匹配,插口转换之类的)
原来ListView要呈现不同子视图,所以就需要一个适配器来做“兼容”~
下拉ListView时它上面会有“刷新”等图标及文字,这是怎么做到的? ListView类中有个addHeaderView(View v) 方法,我们将 【放好“刷新”等图标及文字】的一个子视图,通过这个方法将这个子视图放到ListView中,就可以从“逻辑上”了解了,上面那块“刷新”的东东是怎么来的。
新增的Head视图(Layout) |
原ListView |
有了这个组合,剩下的就是滑动相关了~
由于我们是要实现“下拉刷新”效果的,且这个效果是基于ListView控件的,所以我们需要自定义一个ListView控件,我们叫它mListView吧~
在继续下面文章之前,我提一个代码使用技巧,高手略过~
Begin---------------------------------------------------------------------------------------------------------------------------
如图:
我们把相关事件定义在辅类时,实现却在主类中来实现,怎么来实现呢?
在辅类中定义一个接口如IInterface1
辅类部分代码Code-begin -------------------------------------
IInterface1 iobj;
public void setIInterface1(IInterface1 l)
{
iobj=l;
}
然后,在相应事件中直接使用iobj.doSomeThing() 把位置占住~
辅类部分代码Code-end -------------------------------------
主类Activity implements IInterface1 (主类Activity实现 辅类中定义的接口及方法)
public override doSomeThing(){......}
KEY:主类中调用辅类的 setIInterface1 方法把自己放进去 如:setIInterface1(this);
小节下,辅类创建一个接口定义些抽象方法,在相关事件把位置占好,并提供外部set接口实例的方法,主类实现接口把自己做为接口的实现者传入辅类中,成功替换之前的占位抽象方法~
End---------------------------------------------------------------------------------------------------------------------------
以上逻辑将用于下拉触点时,调用相关刷新数据(加载数据)
自定义的mListView 需要 实现(implements)OnScrollListener 接口,
申明一个Scroller 实例,如下:
mScroller=new Scroller(context,new DecelerateInterpolator());
重写 onTouchEvent 方法
根据不同事件保存与调用滑动方法
mScroller.startScroll(0, height, 0, finalHeight-height,SCROLL_DURATION);
然后就是调用方法 invalidate() 重绘界面
仅仅调用Scroller的startScroll方法是不能实现滚动的~ 还有个关键方法 computeScroll ,你可能需要重写这个方法
@Override public void computeScroll() { if(mScroller.computeScrollOffset()) { mHeaderView.setVisiableHeight(mScroller.getCurrY()); postInvalidate(); invokeOnScrolling(); } super.computeScroll(); }
每次滑动时都会调用这个computerScroll()方法,
mScroller.computeScrollOffset() ///// 是否有偏移
postInvalidate() ///// 刷新界面与 Invalidate 有点细微差别~
以上就是我的小节~
直接贴代码吧~ 如下:
package com.example.androidtest1.widget; import android.content.Context; import android.text.format.Time; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.animation.DecelerateInterpolator; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.Scroller; import com.example.androidtest1.R; public class XListView extends ListView implements OnScrollListener { private float mLastY = -1; // save event y private Scroller mScroller; private OnScrollListener mScrollListener; ///// 滚动侦听 private IXListViewListener mListViewListener; ///// 触发刷新接口 private Context m_context; private XListViewHeader mHeaderView; private RelativeLayout mHeaderViewContent; private int mHeaderViewHeight; private boolean mEnablePullRefresh=true; //// 是否允许下拉刷新 private boolean mPullRefreshing=false; //// 下拉刷新中? ////// list 中的数量 private int mTotalItemCount; private int mScrollBack; private final static int SCROLLBACK_HEADER=0; private final static int SCROLLBACK_FOOTER=1; private final static int SCROLL_DURATION=400; private final static int PULL_LOAD_MORE_DELTA=50; private final static float OFFSET_RADIO=1.8f; public XListView(Context context) { super(context); initWidthContext(context); } public XListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initWidthContext(context); } public XListView(Context context, AttributeSet attrs) { super(context, attrs); initWidthContext(context); } private void initWidthContext(Context context) { mScroller=new Scroller(context,new DecelerateInterpolator()); super.setOnScrollListener(this); m_context=context; mHeaderView=new XListViewHeader(context); mHeaderViewContent=(RelativeLayout) mHeaderView.findViewById(R.id.xlistview_header_content); addHeaderView(mHeaderView); mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener(){ @Override public void onGlobalLayout() { mHeaderViewHeight=mHeaderViewContent.getHeight(); getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } /** * 准备刷新中~ */ public void pullRefreshing(){ if(!mEnablePullRefresh){ return; } mHeaderView.setVisiableHeight(mHeaderViewHeight); mPullRefreshing=true; mHeaderView.setState(XListViewHeader.STATE_READY); } /* * 设置能否下拉刷新 */ public void setPullRefreshEnable(boolean enable){ mEnablePullRefresh=enable; if(!mEnablePullRefresh){ mHeaderViewContent.setVisibility(View.INVISIBLE); }else{ mHeaderViewContent.setVisibility(View.VISIBLE); } } /** * 停止刷新 */ public void stopRefresh(){ Time time=new Time(); time.setToNow(); mHeaderView.setRefreshTime(time.format("%Y-%m-%d %T")); if(mPullRefreshing==true){ mPullRefreshing=false; resetHeaderHeight(); } } /** * 未处于刷新状态,更新箭头 * @param delta */ private void updateHeaderHeight(float delta) { mHeaderView.setVisiableHeight((int)delta+mHeaderView.getVisiableHeight()); if(mEnablePullRefresh && !mPullRefreshing){ if(mHeaderView.getVisiableHeight()>mHeaderViewHeight){ mHeaderView.setState(XListViewHeader.STATE_READY); }else{ mHeaderView.setState(XListViewHeader.STATE_NORMAL); } } setSelection(0); } /** * 重置自定义头部高度 */ private void resetHeaderHeight() { int height=mHeaderView.getVisiableHeight(); if(height==0) return; /* * 下拉刷新的高度不够~ * */ if(mPullRefreshing && height<=mHeaderViewHeight) { mListViewListener.reShowMoreView(); } int finalHeight=0; if(mPullRefreshing && height>mHeaderViewHeight){ finalHeight=mHeaderViewHeight; } mScrollBack=SCROLLBACK_HEADER; ////// 设置滚动 mScroller.startScroll(0, height, 0, finalHeight-height,SCROLL_DURATION); ////// 触发computeScroll invalidate(); } private void invokeOnScrolling(){ if(mScrollListener instanceof OnXScrollListener){ OnXScrollListener l=(OnXScrollListener)mScrollListener; l.onXScrolling(this); } } @Override public boolean onTouchEvent(MotionEvent ev) { if(mLastY==-1) mLastY=ev.getRawY(); switch(ev.getAction()){ case MotionEvent.ACTION_DOWN: mLastY=ev.getRawY(); break; case MotionEvent.ACTION_MOVE: final float deltaY=ev.getRawY()-mLastY; mLastY=ev.getRawY(); if(getFirstVisiblePosition()==0 && (mHeaderView.getVisiableHeight()>0 || deltaY>0)) { updateHeaderHeight(deltaY/OFFSET_RADIO); invokeOnScrolling(); } break; default: mLastY=-1; if(getFirstVisiblePosition()==0){ if(mHeaderView.getVisiableHeight()>0) mPullRefreshing=true; if(mEnablePullRefresh && mHeaderView.getVisiableHeight()>mHeaderViewHeight){ mHeaderView.setState(XListViewHeader.STATE_REFRESHING); if(mListViewListener!=null){ mListViewListener.onRefresh(); } } resetHeaderHeight(); } break; } return super.onTouchEvent(ev); } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mTotalItemCount=totalItemCount; if(mScrollListener!=null) { mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if(mScrollListener!=null) { mScrollListener.onScrollStateChanged(view, scrollState); } } @Override public void computeScroll() { if(mScroller.computeScrollOffset()) { mHeaderView.setVisiableHeight(mScroller.getCurrY()); postInvalidate(); invokeOnScrolling(); } super.computeScroll(); } @Override public void setOnScrollListener(OnScrollListener l) { mScrollListener = l; } /** * * @param l */ public void setXListViewListener(IXListViewListener l){ mListViewListener=l; } public interface OnXScrollListener extends OnScrollListener{ public void onXScrolling(View view); } public interface IXListViewListener{ public void onRefresh(); public void onLoadMore(); public void hiddenShowMoreView(); public void reShowMoreView(); } }