再也不用担心下拉刷新,上拉加载啦!-自定义ListView对上拉刷新,上拉加载的详解
2015-10-12 21:20
411 查看
前言:
看过许多下拉刷新的例子,好多大牛们的代码写的很完美,让人羡慕嫉妒恨~~~,可是,对于下拉刷新时的手势操作却没有给出详细的解释,当一堆堆逻辑代码出来的时候,对于我们这些菜鸟来说,理解起来真是让人脑子都大了。为了解放大脑(懒得自己进行全面分析),一步一步详解下拉操作,妈妈再也不用担心ListView下拉刷新是什么鬼啦!~~先上效果图:~~
上拉加载数据:下拉刷新:下拉距离短不刷新数据下拉刷新数据:
思路详解:
自定义的带有下拉刷新和上拉加载的ListView开始时,跟系统的ListView一样。不过多了个header和footer只不过这两个布局以不同的方式隐藏起来了而已。(header是在手机屏幕外的上面,footer是直接隐藏起来了。因为下拉和上拉加载不同,下拉加载有手势判断,要出现动画效果,根据下拉的各种手势,来设置具体的操作。如果直接跟footer一样首先默认header的View.setVisibility(GONE),当有下拉手势时再设置View.setVisibility(View.VISIBLE)就会没有良好的动画效果。)如下图所示。
PS:破电脑只有自带的Window画图工具。凑合看吧
先将代码拆分~ 上拉加载跟下拉刷新分开来讲,后面会有完整的代码。~~~
上拉加载数据:
由于这个比下拉刷新简单。先理解这个。上拉加载适用于需要加载的数据量很大时,如果一下子加载完。会使ListView出现卡顿。这时候,如果利用上拉加载。先加载一部分数据。当上拉时,再加载其他的一部分数据。这样就会有很好的用户体验。
footer布局文件
<?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true" android:orientation="vertical"> <LinearLayout android:id="@+id/ll_footer" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <ProgressBar android:id="@+id/footer_pb" android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:attr/progressBarStyleSmall" android:layout_gravity="center" /> <TextView android:id="@+id/footer_tv" android:text="footer正在加载。。" android:textSize="16sp" android:gravity="center" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout> </LinearLayout>
接下来看上拉加载逻辑:上拉加载我们利用的是AbsListView.OnScrollListener这个接口。它有两个方法需要重写:
1.publicvoidonScroll(AbsListViewview,intfirstVisibleItem,int
visibleItemCount,inttotalItemCount){};
2.publicvoidonScrollStateChanged(AbsListViewview,intscrollState){};
借鉴别的地方的对这两个方法的详细解释。他讲解的很详细啦:
newOnScrollListener(){ booleanisLastRow=false; @Override publicvoidonScroll(AbsListViewview,intfirstVisibleItem,intvisibleItemCount,inttotalItemCount){ //滚动时一直回调,直到停止滚动时才停止回调。单击时回调一次。 //firstVisibleItem:当前能看见的第一个列表项ID(从0开始) //visibleItemCount:当前能看见的列表项个数(小半个也算) //totalItemCount:列表项共数 //判断是否滚到最后一行 if(firstVisibleItem+visibleItemCount==totalItemCount&&totalItemCount>0){ isLastRow=true; } } @Override publicvoidonScrollStateChanged(AbsListViewview,intscrollState){ //正在滚动时回调,回调2-3次,手指没抛则回调2次。scrollState=2的这次不回调 //回调顺序如下 //第1次:scrollState=SCROLL_STATE_TOUCH_SCROLL(1)正在滚动 //第2次:scrollState=SCROLL_STATE_FLING(2)手指做了抛的动作(手指离开屏幕前,用力滑了一下) //第3次:scrollState=SCROLL_STATE_IDLE(0)停止滚动 //当屏幕停止滚动时为0;当屏幕滚动且用户使用的触碰或手指还在屏幕上时为1; //由于用户的操作,屏幕产生惯性滑动时为2 //当滚到最后一行且停止滚动时,执行加载 if(isLastRow&&scrollState==AbsListView.OnScrollListener.SCROLL_STATE_IDLE){ //加载元素 ...... isLastRow=false; } } }
好了,了解了OnScrollListener的这两个方法具体是干什么的,接下来看我们怎么实现~~~:
上拉加载的关键就在这个接口中实现:
首先是LoadListView中的定义的变量:
privateintlastVisibleItem;//最后一个可见项 privateinttotalItems;//总的item privateViewfooter;//底部View+头部View; privatebooleanisLoading=false;//是否正在加载 privateILoadListeneriListener;//自定义的一个加载接口。暴露给MainActivity让它实现具体加载操作。可以根据需求不同而改写。
加载布局文件以及设置监听:
privatevoidinitViews(Contextcontext){ //获得footer+header布局文件 LayoutInflaterinflater=LayoutInflater.from(context); footer=inflater.inflate(R.layout.footer,null); footer.findViewById(R.id.ll_footer).setVisibility(GONE);//初始化时设置footer不可见 this.addFooterView(footer); this.setOnScrollListener(this);//设置滚动监听 }
重写OnScrollListener的这两个方法:
@Override publicvoidonScrollStateChanged(AbsListViewview,intscrollState){ if(lastVisibleItem==totalItems&&scrollState==SCROLL_STATE_IDLE){ //如果不是在加载 if(!isLoading){ footer.findViewById(R.id.ll_footer).setVisibility(View.VISIBLE); iListener.onLoad(); isLoading=true; } } } @Override publicvoidonScroll(AbsListViewview,intfirstVisibleItem,intvisibleItemCount,inttotalItemCount){ this.lastVisibleItem=firstVisibleItem+visibleItemCount; this.totalItems=totalItemCount; }
这样就差不多了。接下来再完成接口的设置,具体操作再MainActivity中实现。
/** *加载更多数据的回调接口 */ publicinterfaceILoadListener{ publicvoidonLoad(); } //上拉加载完毕 publicvoidloadCompleted(){ isLoading=false; footer.findViewById(R.id.ll_footer).setVisibility(GONE); } publicvoidsetInterface(ILoadListeneriListener){ this.iListener=iListener; }
上拉加载就完成了80%了,具体操作时,在MainActivity中:
privateLoadListViewmListView; privateList<String>datas; privateArrayAdapter<String>arrayAdapter; @Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setupViews(); initDatas(); } privatevoidinitDatas(){ for(inti=0;i<16;i++){ datas.add("ListView的数据"+i+""); } } privatevoidinitNewDatas(){ for(inti=0;i<3;i++){ datas.add("footer加载出的数据"+i+""); } } privatevoidsetupViews(){ mListView=(LoadListView)findViewById(R.id.lv_main); //上拉加载接口 mListView.setInterface(this); datas=newArrayList<String>(); arrayAdapter=newArrayAdapter<String>(MainActivity.this,android.R.layout.simple_expandable_list_item_1,datas); mListView.setAdapter(arrayAdapter); }
实现LoadList暴露的接口中的onLoad()方法:
//实现onLoad()方法。 @Override publicvoidonLoad(){ //添加延时效果模拟数据加载 Handlerhandler=newHandler(); handler.postDelayed(newRunnable(){ @Override publicvoidrun(){ initNewDatas();//得到新数据 arrayAdapter.notifyDataSetChanged();//刷新ListView; mListView.loadCompleted(); } },2000); }OK~上拉加载数据就大功告成了~~
下拉刷新:
首先是header布局文件:
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dip"
android:paddingBottom="10dip"
>
<LinearLayout
android:id="@+id/ll_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉可以刷新"/>
<TextView
android:id="@+id/tv_lastupdate_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<ImageView
android:id="@+id/arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/ll_header"
android:layout_marginRight="20dip"
android:src="@drawable/pull_down_refresh_arrow"
/>
<ProgressBar
android:id="@+id/header_pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleSmall"
android:layout_toLeftOf="@id/ll_header"
android:layout_marginRight="20dip"
android:visibility="gone"/>
</RelativeLayout>
</LinearLayout>
LoadList中自定义的一些变量:
privatebooleanisRemark=false;//判断是否在当前页的最顶端并下滑
privateintstartY;//Y坐标记录手指开始按下的坐标
privateRLoadListenerrLoadListener;//自定义的一个加载接口。暴露给MainActivity让它实现具体加载操作。可以根据需求不同而改写。
privateintscrollState;//当前滚动的状态
privateintheaderHeight;//顶部布局文件的高度
finalintNONE=0;//正常状态
finalintPULL=1;//下拉
finalintRELESE=2;//释放
finalintREFLASHING=3;//刷新
privateintstate=0;//判断当前状态,默认为正常状态
privateintfirstVisibleItem;//第一个可见项
privateViewheader;//头部View;
添加头部提示到ListView中:
LayoutInflaterinflater=LayoutInflater.from(context);
header=inflater.inflate(R.layout.header,null);
//测量header的宽和高
measureView(header);
//记录下header的高
headerHeight=header.getMeasuredHeight();
topPadding(-headerHeight);
this.addHeaderView(header);
this.setOnScrollListener(this);//设置滚动监听
这里添加头布局的时候需要计算header到底要移出屏幕多少的距离(移出的距离即为header的高),并且要告知父布局:
/**
*通知父布局,占用的宽和高
*@paramview
*/
privatevoidmeasureView(Viewview){
//得到view的布局宽高
ViewGroup.LayoutParamsvlp=view.getLayoutParams();
//如果没有就new一个
if(vlp==null){
vlp=newViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
}
//ViewGroup.getChildMeasureSpec(intspec,intpadding,intchildDimension)1.父view的详细尺寸2.view当前尺寸的下的边距3.child在当前尺寸下的宽(高)
intwidth=ViewGroup.getChildMeasureSpec(0,0,vlp.width);
intheight;
inttempHeight=vlp.height;
if(tempHeight>0){
height=MeasureSpec.makeMeasureSpec(tempHeight,MeasureSpec.EXACTLY);
}else{
height=MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
}
view.measure(width,height);
}
/**
*设置header布局的上边距
*@paramtopPadding
*/
privatevoidtopPadding(inttopPadding){
header.setPadding(header.getPaddingLeft(),topPadding,header.getPaddingRight(),header.getPaddingBottom());
//重绘
header.invalidate();
}
在OnScrollListener的两个方法中记录一些状态量便于后面对OnTouch事件的操作:
@Override
publicvoidonScrollStateChanged(AbsListViewview,intscrollState){
this.scrollState=scrollState;
}
@Override
publicvoidonScroll(AbsListViewview,intfirstVisibleItem,intvisibleItemCount,inttotalItemCount){
this.firstVisibleItem=firstVisibleItem;
}
利用OnTouchEvent对下拉手势操作进行监听:
先来说明下拉刷新会出现的情况:1.下拉距离过短,不进行数据刷新。2.下拉到一定距离刷新数据。
当进行下拉刷新的时候,手势有3种状态:1.刚按下的时候;2.手指下滑移动的时候;3.手指抬起释放的时候。这3种手指状态又对应了header不同的View状态,然后根据view的状态,再来改变header的显示。
就是靠下面这三个方法来实现下拉的核心操作分析在代码后面:
@Override
publicbooleanonTouchEvent(MotionEventev){
switch(ev.getAction()){
caseMotionEvent.ACTION_DOWN://如果按下
if(firstVisibleItem==0){
isRemark=true;
startY=(int)ev.getY();
}
break;
caseMotionEvent.ACTION_MOVE:
onMove(ev);
break;
caseMotionEvent.ACTION_UP:
if(state==RELESE){
state=REFLASHING;
//加载最新数据
reflashViewByState();
rLoadListener.onRefresh();
}elseif(state==PULL){
state=NONE;
isRemark=false;
reflashViewByState();
}
break;
}
returnsuper.onTouchEvent(ev);
}
/**
*判断移动的过程
*
*@paramev
*/
privatevoidonMove(MotionEventev){
if(!isRemark){
return;
}
inttempY=(int)ev.getY();
//记录滑动距离
intspace=tempY-startY;
//比较滑动距离和header的高
inttopPadding=space-headerHeight;
switch(state){
caseNONE:
if(space>0){
state=PULL;
reflashViewByState();
}
break;
casePULL:
//重绘header
topPadding(topPadding);
if(space>headerHeight+30&&scrollState==SCROLL_STATE_TOUCH_SCROLL){
state=RELESE;
reflashViewByState();
}
break;
caseRELESE:
topPadding(topPadding);
if(space<headerHeight+30){
state=PULL;
reflashViewByState();
}elseif(space<=0){//如果space<0说明向上滑。所以firstVisibleItem就不是0了所以将isRemark设置为false;即listView现在不是最顶端的位置
state=NONE;
isRemark=false;
reflashViewByState();
}
break;
}
}
/**
*根据当前状态,改变界面显示
*/
privatevoidreflashViewByState(){
TextViewtip=(TextView)header.findViewById(R.id.tip);
ImageViewarrow=(ImageView)header.findViewById(R.id.arrow);
ProgressBarheader_pb=(ProgressBar)header.findViewById(R.id.header_pb);
RotateAnimationanimation=newRotateAnimation(0,180,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
animation.setDuration(500);
animation.setFillAfter(true);
RotateAnimationanimation2=newRotateAnimation(180,0,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
animation2.setDuration(500);
animation2.setFillAfter(true);
/**
*四种状态动画的改变
*/
switch(state){
caseNONE:
arrow.clearAnimation();
topPadding(-headerHeight);
break;
casePULL:
arrow.setVisibility(View.VISIBLE);
header_pb.setVisibility(View.GONE);
tip.setText("下拉可以刷新!!");
arrow.clearAnimation();
arrow.setAnimation(animation2);
break;
caseRELESE:
arrow.setVisibility(View.VISIBLE);
header_pb.setVisibility(View.GONE);
tip.setText("松开可以刷新!!");
arrow.clearAnimation();
arrow.setAnimation(animation);
break;
caseREFLASHING:
topPadding(50);
arrow.setVisibility(View.GONE);
header_pb.setVisibility(View.VISIBLE);
tip.setText("正在刷新...");
arrow.clearAnimation();
break;
}
}
手指操作的图示:
由于图太大,放到里面看不清。。。需要高清无码大图的点击这里:
这样,下拉刷新的核心代码已经完成了,接下来就是设置接口,和刷新完所做的动作的方法:
下拉刷新接口,暴露给MainActivity来具体实现到底刷新出什么数据:
/**
*下拉刷新接口
*/
publicinterfaceRLoadListener{
publicvoidonRefresh();
}
publicvoidsetReflashInterface(RLoadListenerrLoadListener){
this.rLoadListener=rLoadListener;
}
刷新完要做的工作:
/**
*获取完整数据
*
*/
publicvoidreflashComplete(){
state=NONE;
isRemark=false;
reflashViewByState();
TextViewlasetupdate_time=(TextView)header.findViewById(R.id.tv_lastupdate_time);
SimpleDateFormatsimpleDateFormat=newSimpleDateFormat("yyyy年MM月dd日hh:mm:ss");
Datedate=newDate(System.currentTimeMillis());
Stringtime=simpleDateFormat.format(date);
lasetupdate_time.setText(time);
}
duang~duang~duang~接下来就看怎么用这些东西在MainActivity中。
贴上完整的代码~~。
1.布局文件:
footer.xml
<?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true" android:orientation="vertical"> <LinearLayout android:id="@+id/ll_footer" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <ProgressBar android:id="@+id/footer_pb" android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:attr/progressBarStyleSmall" android:layout_gravity="center" /> <TextView android:id="@+id/footer_tv" android:text="footer正在加载。。" android:textSize="16sp" android:gravity="center" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout> </LinearLayout>
header.xml:
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dip"
android:paddingBottom="10dip"
>
<LinearLayout
android:id="@+id/ll_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉可以刷新"/>
<TextView
android:id="@+id/tv_lastupdate_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<ImageView
android:id="@+id/arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/ll_header"
android:layout_marginRight="20dip"
android:src="@drawable/pull_down_refresh_arrow"
/>
<ProgressBar
android:id="@+id/header_pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleSmall"
android:layout_toLeftOf="@id/ll_header"
android:layout_marginRight="20dip"
android:visibility="gone"/>
</RelativeLayout>
</LinearLayout>
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dip"
android:paddingBottom="10dip"
>
<LinearLayout
android:id="@+id/ll_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉可以刷新"/>
<TextView
android:id="@+id/tv_lastupdate_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<ImageView
android:id="@+id/arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/ll_header"
android:layout_marginRight="20dip"
android:src="@drawable/pull_down_refresh_arrow"
/>
<ProgressBar
android:id="@+id/header_pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleSmall"
android:layout_toLeftOf="@id/ll_header"
android:layout_marginRight="20dip"
android:visibility="gone"/>
</RelativeLayout>
</LinearLayout>
activity_main.xml
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"
android:layout_height="match_parent"android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"tools:context=".MainActivity">
<com.example.administrator.listviewtest.LoadListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"></com.example.administrator.listviewtest.LoadListView>
</RelativeLayout>
LoadListView.java
packagecom.example.administrator.listviewtest;
importandroid.content.Context;
importandroid.util.AttributeSet;
importandroid.view.LayoutInflater;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.view.animation.RotateAnimation;
importandroid.widget.AbsListView;
importandroid.widget.ImageView;
importandroid.widget.ListView;
importandroid.widget.ProgressBar;
importandroid.widget.TextView;
importjava.text.SimpleDateFormat;
importjava.util.Date;
/**
*CreatedbyAdministratoron2015-10-12.
*/
publicclassLoadListViewextendsListViewimplementsAbsListView.OnScrollListener{
privateintlastVisibleItem;//最后一个可见项
privateinttotalItems;//总的item
privateViewfooter,header;//底部View+头部View;
privatebooleanisLoading=false;//是否正在加载
privateILoadListeneriListener;//自定义的一个加载接口。暴露给MainActivity让它实现具体加载操作。可以根据需求不同而改写。
privatebooleanisRemark=false;//判断是否在当前页的最顶端并下滑
privateintstartY;//Y坐标记录手指开始按下的坐标
privateRLoadListenerrLoadListener;//自定义的一个加载接口。暴露给MainActivity让它实现具体加载操作。可以根据需求不同而改写。
privateintscrollState;//当前滚动的状态
privateintheaderHeight;//顶部布局文件的高度
finalintNONE=0;//正常状态
finalintPULL=1;//下拉
finalintRELESE=2;//释放
finalintREFLASHING=3;//刷新
privateintstate=0;//判断当前状态,默认为正常状态
privateintfirstVisibleItem;//第一个可见项
publicLoadListView(Contextcontext){
this(context,null);
}
publicLoadListView(Contextcontext,AttributeSetattrs){
this(context,attrs,android.R.attr.listViewStyle);
}
publicLoadListView(Contextcontext,AttributeSetattrs,intdefStyleAttr){
super(context,attrs,defStyleAttr);
initViews(context);
}
/**
*添加底部+头部提示到ListVIew
*@paramcontext
*/
privatevoidinitViews(Contextcontext){
//获得footer+header布局文件
LayoutInflaterinflater=LayoutInflater.from(context);
footer=inflater.inflate(R.layout.footer,null);
footer.findViewById(R.id.ll_footer).setVisibility(GONE);//初始化时设置footer不可见
header=inflater.inflate(R.layout.header,null);
//测量header的宽和高
measureView(header);
//记录下header的高
headerHeight=header.getMeasuredHeight();
topPadding(-headerHeight);
this.addHeaderView(header);
this.addFooterView(footer);
this.setOnScrollListener(this);//设置滚动监听
}
/**
*通知父布局,占用的宽和高
*@paramview
*/
privatevoidmeasureView(Viewview){
//得到view的布局宽高
ViewGroup.LayoutParamsvlp=view.getLayoutParams();
//如果没有就new一个
if(vlp==null){
vlp=newViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
}
//ViewGroup.getChildMeasureSpec(intspec,intpadding,intchildDimension)1.父view的详细尺寸2.view当前尺寸的下的边距3.child在当前尺寸下的宽(高)
intwidth=ViewGroup.getChildMeasureSpec(0,0,vlp.width);
intheight;
inttempHeight=vlp.height;
if(tempHeight>0){
height=MeasureSpec.makeMeasureSpec(tempHeight,MeasureSpec.EXACTLY);
}else{
height=MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
}
view.measure(width,height);
}
/**
*设置header布局的上边距
*@paramtopPadding
*/
privatevoidtopPadding(inttopPadding){
header.setPadding(header.getPaddingLeft(),topPadding,header.getPaddingRight(),header.getPaddingBottom());
//重绘
header.invalidate();
}
@Override
publicvoidonScrollStateChanged(AbsListViewview,intscrollState){
this.scrollState=scrollState;
if(lastVisibleItem==totalItems&&scrollState==SCROLL_STATE_IDLE){
//如果不是在加载
if(!isLoading){
footer.findViewById(R.id.ll_footer).setVisibility(View.VISIBLE);
iListener.onLoad();
isLoading=true;
}
}
}
@Override
publicvoidonScroll(AbsListViewview,intfirstVisibleItem,intvisibleItemCount,inttotalItemCount){
this.lastVisibleItem=firstVisibleItem+visibleItemCount;
this.totalItems=totalItemCount;
this.firstVisibleItem=firstVisibleItem;
}
/** *加载更多数据的回调接口 */ publicinterfaceILoadListener{ publicvoidonLoad(); } //上拉加载完毕 publicvoidloadCompleted(){ isLoading=false; footer.findViewById(R.id.ll_footer).setVisibility(GONE); } publicvoidsetInterface(ILoadListeneriListener){ this.iListener=iListener; }
/**
*下拉刷新接口
*/
publicinterfaceRLoadListener{
publicvoidonRefresh();
}
publicvoidsetReflashInterface(RLoadListenerrLoadListener){
this.rLoadListener=rLoadListener;
}
@Override
publicbooleanonTouchEvent(MotionEventev){
switch(ev.getAction()){
caseMotionEvent.ACTION_DOWN://如果按下
if(firstVisibleItem==0){
isRemark=true;
startY=(int)ev.getY();
}
break;
caseMotionEvent.ACTION_MOVE:
onMove(ev);
break;
caseMotionEvent.ACTION_UP:
if(state==RELESE){
state=REFLASHING;
//加载最新数据
reflashViewByState();
rLoadListener.onRefresh();
}elseif(state==PULL){
state=NONE;
isRemark=false;
reflashViewByState();
}
break;
}
returnsuper.onTouchEvent(ev);
}
/**
*判断移动的过程
*
*@paramev
*/
privatevoidonMove(MotionEventev){
if(!isRemark){
return;
}
inttempY=(int)ev.getY();
//记录滑动距离
intspace=tempY-startY;
//比较滑动距离和header的高
inttopPadding=space-headerHeight;
switch(state){
caseNONE:
if(space>0){
state=PULL;
reflashViewByState();
}
break;
casePULL:
//重绘header
topPadding(topPadding);
if(space>headerHeight+30&&scrollState==SCROLL_STATE_TOUCH_SCROLL){
state=RELESE;
reflashViewByState();
}
break;
caseRELESE:
topPadding(topPadding);
if(space<headerHeight+30){
state=PULL;
reflashViewByState();
}elseif(space<=0){//如果space<0说明向上滑。所以firstVisibleItem就不是0了所以将isRemark设置为false;即listView现在不是最顶端的位置
state=NONE;
isRemark=false;
reflashViewByState();
}
break;
}
}
/**
*根据当前状态,改变界面显示
*/
privatevoidreflashViewByState(){
TextViewtip=(TextView)header.findViewById(R.id.tip);
ImageViewarrow=(ImageView)header.findViewById(R.id.arrow);
ProgressBarheader_pb=(ProgressBar)header.findViewById(R.id.header_pb);
//设置的下拉箭头的动画效果
RotateAnimationanimation=newRotateAnimation(0,180,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
animation.setDuration(500);
animation.setFillAfter(true);
//设置的下拉箭头的动画效果
RotateAnimationanimation2=newRotateAnimation(180,0,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
animation2.setDuration(500);
animation2.setFillAfter(true);
/**
*四种状态动画的改变
*/
switch(state){
caseNONE:
arrow.clearAnimation();
topPadding(-headerHeight);
break;
casePULL:
arrow.setVisibility(View.VISIBLE);
header_pb.setVisibility(View.GONE);
tip.setText("下拉可以刷新!!");
arrow.clearAnimation();
arrow.setAnimation(animation2);
break;
caseRELESE:
arrow.setVisibility(View.VISIBLE);
header_pb.setVisibility(View.GONE);
tip.setText("松开可以刷新!!");
arrow.clearAnimation();
arrow.setAnimation(animation);
break;
caseREFLASHING:
topPadding(50);
arrow.setVisibility(View.GONE);
header_pb.setVisibility(View.VISIBLE);
tip.setText("正在刷新...");
arrow.clearAnimation();
break;
}
}
/**
*获取完整数据
*
*/
publicvoidreflashComplete(){
state=NONE;
isRemark=false;
reflashViewByState();
//设置刷新完成的时间
TextViewlasetupdate_time=(TextView)header.findViewById(R.id.tv_lastupdate_time);
SimpleDateFormatsimpleDateFormat=newSimpleDateFormat("yyyy年MM月dd日hh:mm:ss");
Datedate=newDate(System.currentTimeMillis());
Stringtime=simpleDateFormat.format(date);
lasetupdate_time.setText(time);
}
}
MainActivity.java
packagecom.example.administrator.listviewtest;
importandroid.os.Handler;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.widget.ArrayAdapter;
importjava.util.ArrayList;
importjava.util.List;
importjava.util.logging.LogRecord;
publicclassMainActivityextendsAppCompatActivityimplementsLoadListView.ILoadListener,LoadListView.RLoadListener{
privateLoadListViewmListView;
privateList<String>datas;
privateArrayAdapter<String>arrayAdapter;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupViews();
initDatas();
}
privatevoidinitDatas(){
for(inti=0;i<16;i++){
datas.add("ListView的数据"+i+"");
}
}
privatevoidinitNewDatas(){
for(inti=0;i<3;i++){
datas.add("footer加载出的数据"+i+"");
}
}
privatevoidinitREflashDatas(){
datas.add(0,"下拉刷新加载的数据");
}
privatevoidsetupViews(){
mListView=(LoadListView)findViewById(R.id.lv_main);
//上拉加载接口
mListView.setInterface(this);
mListView.setReflashInterface(this);
datas=newArrayList<String>();
arrayAdapter=newArrayAdapter<String>(MainActivity.this,android.R.layout.simple_expandable_list_item_1,datas);
mListView.setAdapter(arrayAdapter);
}
//实现onLoad()方法。
@Override
publicvoidonLoad(){
//添加延时效果模拟数据加载
Handlerhandler=newHandler();
handler.postDelayed(newRunnable(){
@Override
publicvoidrun(){
initNewDatas();//得到新数据
arrayAdapter.notifyDataSetChanged();//刷新ListView;
mListView.loadCompleted();
}
},2000);
}
//实现的刷新方法
@Override
publicvoidonRefresh(){
Handlerhandler=newHandler();
handler.postDelayed(newRunnable(){
@Override
publicvoidrun(){
initREflashDatas();//得到新数据
arrayAdapter.notifyDataSetChanged();//刷新ListView;
mListView.reflashComplete();
}
},2000);
}
}
大功告成~~。
附上源码记得给好评~:
Ps:Google其实早已经更新了sdk新增加的一个widget,SwipeRefreshLayout字面意思就是下拉刷新的布局,继承自ViewGroup,可以实现下拉刷新的操作~~
想要了解的同学可以点击这里:
相关文章推荐
- NYOJ 20 吝啬的国度 (DFS & vector)
- 第一次文档编写被驳回的总结
- ffmpeg 水印问题
- 【ShancoLove】带你看数据结构——第五课:堆栈
- WebView的用法
- testD
- erlang实现ssh
- Xcode 操作数据库
- 插入排序——希尔排序
- leetcode Unique Binary Search Trees
- DNS、活动目录、域
- [Android]实现ListView的删除功能
- Struts2 重定向
- android 代码绘制转盘抽奖的实现
- iOS GCD
- LevelDB 的缺憾
- 消息队列
- excel中 lookup的使用
- 【Python之旅】第六篇(六):Python多进程使用
- 熟悉DEBUG调试环境试验