分享自:http://blog.csdn.net/lisdye2/article/details/51449716
Ultra-Pull-To-Refresh是一个功能非常强大的类库,通过他,我们可以实现非常丰富的下拉刷新视图,并且他支持几乎所有的控件的下拉刷新(不仅仅是ListView
),但该视图不支持上拉加载,作者可能在考虑此库设计时的想法与Google
官方的SwipeRefreshLayout
的理念符合。即刷新可能是许多控件都需要,而上拉加载只有列表视图需要。
那么我们就开始尝试使用他吧。
Android Studio 导入Ultra
- 在工程的
build.gradle
中,加上如下代码 -
1 allprojects { 2 repositories { 3 jcenter() 4 mavenCentral(); 5 maven { 6 url 'https://oss.sonatype.org/content/repositories/snapshots' 7 } 8 } 9 }
- 添加依赖包
1 compile 'in.srain.cube:ultra-ptr:1.0.11'
- Clean一下工程即可
Eclipse 导入 Ultra
因为作者提供的只有AndroidStudio
版本,所以,我把它代码导出到了一个Eclipse
工程中,直接添加依赖即可。
Ultra-Pull-To-Refresh Eclipse 版本下载地址
简单使用
添加xml文件:activity_listview_ultra_refresh
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:orientation="vertical" android:layout_width="match_parent" 3 android:layout_height="match_parent"> 4 5 <in.srain.cube.views.ptr.PtrClassicFrameLayout 6 android:id="@+id/ultra_ptr_frame" 7 xmlns:cube_ptr="http://schemas.android.com/apk/res-auto" 8 android:layout_width="match_parent" 9 android:layout_height="match_parent" 10 > 11 12 <FrameLayout 13 android:id="@+id/ultra_refresh_frame" 14 android:layout_width="match_parent" 15 android:layout_height="match_parent" 16 android:background="#f00" 17 android:paddingTop="100dp"> 18 </FrameLayout> 19 </in.srain.cube.views.ptr.PtrClassicFrameLayout> 20 21 22 </LinearLayout>
添加了一个PtrClassicFrameLayout
包含了一个FrameLayout
.
看一下Java代码
1 public class UltraRefreshActivity extends Activity { 2 3 4 private PtrClassicFrameLayout ptrFrame; 5 6 @Override 7 protected void onCreate(Bundle savedInstanceState) { 8 super.onCreate(savedInstanceState); 9 10 setContentView(R.layout.activity_listview_ultra_refresh); 11 12 ptrFrame = ((PtrClassicFrameLayout) findViewById(R.id.ultra_ptr_frame)); 13 14 ptrFrame.setLastUpdateTimeRelateObject(this); 15 16 //下拉刷新的阻力,下拉时,下拉距离和显示头部的距离比例,值越大,则越不容易滑动 17 ptrFrame.setRatioOfHeaderHeightToRefresh(1.2f); 18 19 ptrFrame.setDurationToClose(200);//返回到刷新的位置(暂未找到) 20 21 ptrFrame.setDurationToCloseHeader(1000);//关闭头部的时间 // default is false 22 23 ptrFrame.setPullToRefresh(false);//当下拉到一定距离时,自动刷新(true),显示释放以刷新(false) 24 25 ptrFrame.setKeepHeaderWhenRefresh(true);//见名只意 26 27 ptrFrame.setPtrHandler(new PtrHandler() { 28 @Override 29 public boolean checkCanDoRefresh(PtrFrameLayout frame, 30 View content, View header) { 31 return PtrDefaultHandler.checkContentCanBePulledDown(frame, 32 content, header); 33 } 34 35 @Override 36 public void onRefreshBegin(PtrFrameLayout frame) { 37 38 //数据刷新的回调 39 40 ptrFrame.postDelayed(new Runnable() { 41 @Override 42 public void run() { 43 ptrFrame.refreshComplete(); 44 } 45 }, 1500); 46 } 47 }); 48 } 49 }
看一下效果
通过实现过程,我们可以发现,其下拉刷新和其包含的控件没有之间联系:也就是说我们可以在PtrClassicFrameLayout
包含任何子控件。
他可以设置的一些常量,代码注释中都有体现。而他有两个关键性的方法:
ptrFrame.setPtrHandler(new PtrHandler())
:数据刷新的接口回调。包含两个回调方法
- checkCanDoRefresh:是否能够刷新。
- onRefreshBegin:开始刷新的回调。
- ptrFrame.addPtrUIHandler(new PtrUIHandler()):UI更新接口的回调。其中PtrUIHandler为一个接口,其定义如下
-
1 public interface PtrUIHandler { 2 3 /** 4 * When the content view has reached top and refresh has been completed, view will be reset. 5 * 6 * @param frame 7 */ 8 public void onUIReset(PtrFrameLayout frame); 9 10 /** 11 * prepare for loading 12 * 13 * @param frame 14 */ 15 public void onUIRefreshPrepare(PtrFrameLayout frame); 16 17 /** 18 * perform refreshing UI 19 */ 20 public void onUIRefreshBegin(PtrFrameLayout frame); 21 22 /** 23 * perform UI after refresh 24 */ 25 public void onUIRefreshComplete(PtrFrameLayout frame); 26 27 public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator); 28 }
含义如下:
onUIRefreshPrepare
:开始下拉之前的接口回调。onUIRefreshBegin
:开始刷新的接口回调。onUIRefreshComplete
:刷新完成的接口回调。onUIReset
:刷新完成之后,UI消失之后的接口回调。onUIPositionChange
:下拉滑动的接口回调,多次调用。
isUnderTouch
:手指是否触摸status
:状态值ptrIndicator
:滑动偏移量等值的封装
那么对于onUIPositionChange
中,都是返回了那些值呢,我写了一个自定义的HeadView,并设置高位100,同时打印相应的Log。在这只贴onUIPositionChange
中的Log代码。
1 @Override 2 public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) { 3 int headerHeight = ptrIndicator.getHeaderHeight();//头部的高度 4 int lastPosY = ptrIndicator.getLastPosY();//上一次滑动的Y偏移值 5 int offsetToRefresh = ptrIndicator.getOffsetToRefresh();//刷新需要滑动的偏移量 6 float offsetY = ptrIndicator.getOffsetY();//当前与上一次滑动处理的偏移值 7 int currentPosY = ptrIndicator.getCurrentPosY();//当前系统偏移值 8 9 10 Log.i("info","isUnderTouch"+isUnderTouch+"headHeight: "+headerHeight+" lastPosY "+lastPosY+" offsetToRefresh "+offsetToRefresh+" offsetY "+offsetY+" currentPosY "+currentPosY); 11 }
看一下打印信息
1 onUIRefreshPrepare 2 isUnderTouchtrueheadHeight: 100 lastPosY 0 offsetToRefresh 120 offsetY 23.025852 currentPosY 23 3 ............................................................ 4 isUnderTouchtrueheadHeight: 100 lastPosY 299 offsetToRefresh 120 offsetY 2.1575928 currentPosY 301 5 onUIRefreshBegin 6 isUnderTouchfalseheadHeight: 100 lastPosY 301 offsetToRefresh 120 offsetY 2.1575928 currentPosY 278 7 ............................................................ 8 isUnderTouchfalseheadHeight: 100 lastPosY 101 offsetToRefresh 120 offsetY 2.1575928 currentPosY 100 9 onUIRefreshComplete 10 isUnderTouchfalseheadHeight: 100 lastPosY 100 offsetToRefresh 120 offsetY 2.1575928 currentPosY 96 11 ............................................................ 12 isUnderTouchtrueheadHeight: 100 lastPosY 239 offsetToRefresh 120 offsetY 1.324391 currentPosY 240 13 onUIRefreshComplete 14 isUnderTouchfalseheadHeight: 100 lastPosY 240 offsetToRefresh 120 offsetY 1.324391 currentPosY 223 15 ............................................................ 16 isUnderTouchfalseheadHeight: 100 lastPosY 2 offsetToRefresh 120 offsetY 1.324391 currentPosY 1 17 onUIReset 18 isUnderTouchfalseheadHeight: 100 lastPosY 1 offsetToRefresh 120 offsetY 1.324391 currentPosY 0
Ultra自定义刷新
看一下我们将要实现的效果:
自定义头部的代码实现
1 public class CustomUltraRefreshHeader extends RelativeLayout implements PtrUIHandler { 2 3 CircleView mCircleView; 4 5 TextView mDescText; 6 7 private ObjectAnimator anim; 8 9 public CustomUltraRefreshHeader(Context context) { 10 this(context,null); 11 } 12 13 public CustomUltraRefreshHeader(Context context, AttributeSet attrs) { 14 this(context, attrs,0); 15 } 16 17 public CustomUltraRefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) { 18 super(context, attrs, defStyleAttr); 19 20 initView(); 21 } 22 23 @Override 24 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 25 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 26 setMeasuredDimension(widthMeasureSpec,100); 27 } 28 29 /** 30 * 初始化布局 31 */ 32 private void initView() { 33 34 int circlewidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, getResources().getDisplayMetrics()); 35 36 mCircleView = new CircleView(getContext()); 37 38 LinearLayout.LayoutParams circleParams = new LinearLayout.LayoutParams(circlewidth,circlewidth); 39 40 mCircleView.setLayoutParams(circleParams); 41 42 mDescText = new TextView(getContext()); 43 44 LinearLayout.LayoutParams descParams = new LinearLayout.LayoutParams(circlewidth*3, ViewGroup.LayoutParams.WRAP_CONTENT); 45 46 descParams.gravity = Gravity.CENTER; 47 descParams.setMargins(circlewidth/2,0,0,0); 48 mDescText.setLayoutParams(descParams); 49 mDescText.setTextSize(12); 50 mDescText.setTextColor(Color.GRAY); 51 mDescText.setText("下拉刷新"); 52 53 //添加线性的父布局 54 LinearLayout ll = new LinearLayout(getContext()); 55 RelativeLayout.LayoutParams llParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 56 llParams.addRule(CENTER_IN_PARENT); 57 ll.setLayoutParams(llParams); 58 ll.setPadding(10,10,10,10); 59 60 ll.addView(mCircleView); 61 ll.addView(mDescText); 62 63 addView(ll); 64 } 65 66 @Override 67 public void onUIReset(PtrFrameLayout frame) { 68 //重置时,将动画置为初始状态 69 mCircleView.setRotation(0f); 70 Log.i("info","onUIReset"); 71 } 72 73 @Override 74 public void onUIRefreshPrepare(PtrFrameLayout frame) { 75 mDescText.setText("下拉加载数据"); 76 Log.i("info","onUIRefreshPrepare"); 77 } 78 79 @Override 80 public void onUIRefreshBegin(PtrFrameLayout frame) { 81 82 //开始刷新,启动动画 83 anim = ObjectAnimator.ofFloat(mCircleView, "rotation", mCircleView.getRotation(), mCircleView.getRotation()+360f) 84 .setDuration(500); 85 anim.setRepeatCount(ValueAnimator.INFINITE); 86 anim.setRepeatMode(ValueAnimator.RESTART); 87 anim.start(); 88 89 mDescText.setText("正在加载数据"); 90 Log.i("info","onUIRefreshBegin"); 91 } 92 93 @Override 94 public void onUIRefreshComplete(PtrFrameLayout frame) { 95 anim.cancel(); 96 mDescText.setText("加载完成"); 97 Log.i("info","onUIRefreshComplete"); 98 } 99 100 @Override 101 public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) { 102 int headerHeight = ptrIndicator.getHeaderHeight();//头部的高度 103 int lastPosY = ptrIndicator.getLastPosY();//上一次滑动的Y偏移值 104 int offsetToRefresh = ptrIndicator.getOffsetToRefresh();//舒心需要滑动的偏移量 105 float offsetY = ptrIndicator.getOffsetY();//当前与上一次滑动处理的偏移值 106 int currentPosY = ptrIndicator.getCurrentPosY();//当前系统偏移值 107 108 if (isUnderTouch&&status== PtrFrameLayout.PTR_STATUS_PREPARE) { 109 110 mCircleView.setRotation(currentPosY); 111 if(currentPosY<offsetToRefresh&&lastPosY >= offsetToRefresh){ 112 //表示不刷新了 113 mDescText.setText("下拉加载数据"); 114 }else if(currentPosY>offsetToRefresh&&lastPosY<=offsetToRefresh){ 115 mDescText.setText("松开加载更多"); 116 } 117 } 118 119 /* if (currentPos < mOffsetToRefresh && lastPos >= mOffsetToRefresh) { 120 if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {*//* 121 Log.i("info","isUnderTouch"+isUnderTouch+"headHeight: "+headerHeight+" lastPosY "+lastPosY+" offsetToRefresh "+offsetToRefresh+" offsetY "+offsetY+" currentPosY "+currentPosY);*/ 122 } 123 }
在这里我们实现了PtrUIHandler
接口,用于下拉刷新时的接口会调,有两点说明:
-
使用的
CircleView
为自定义控件,就是图中的转盘,在我以前的博客中有相应的实现。RecyclerView 下拉刷新和上拉加载 -
在动态改变现实视图时,需要注意的便是
onUIPositionChange
,我们只有在手指触摸且status
为PtrFrameLayout.PTR_STATUS_PREPARE
才修改我们的属性,其他时候不能修改。因为该方法会一直回调。
封装自定义的UltraRefreshListView
继承ListView
,并实现了PtrHandler
接口,用以数据更新回调。
1 public class UltraRefreshListView extends ListView implements PtrHandler,AbsListView.OnScrollListener { 2 3 private UltraRefreshListener mUltraRefreshListener; 4 5 /** 6 * 根布局 7 */ 8 private View footView; 9 10 11 /** 12 * 是否正在加载数据 13 */ 14 private boolean isLoadData = false; 15 16 /** 17 * 是否是下拉刷新,主要在处理结果时使用 18 */ 19 private boolean isRefresh = false; 20 21 public UltraRefreshListView(Context context) { 22 this(context,null); 23 } 24 25 public UltraRefreshListView(Context context, AttributeSet attrs) { 26 this(context, attrs,0); 27 } 28 29 public UltraRefreshListView(Context context, AttributeSet attrs, int defStyleAttr) { 30 super(context, attrs, defStyleAttr); 31 //初始化布局,及添加一个跟布局 32 initView(); 33 } 34 35 private void initView() { 36 footView = LayoutInflater.from(getContext()).inflate(R.layout.foot_ultra_refresh_listview, null); 37 38 setOnScrollListener(this); 39 } 40 41 42 43 @Override 44 public void onRefreshBegin(PtrFrameLayout frame) { 45 46 isLoadData =true; 47 isRefresh =true; 48 //下拉刷新的回调 49 if(mUltraRefreshListener!=null){ 50 51 mUltraRefreshListener.onRefresh(); 52 } 53 } 54 55 56 @Override 57 public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) { 58 // PtrHandler 的接口回调,是否能够加载数据的判断 59 return !isLoadData&&checkContentCanBePulledDown(frame, content, header); 60 } 61 62 // 从PtrHandler的默认实现类 PtrDefaultHandler中找到的,用以判断是否可以下拉刷新 63 public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) { 64 return !canChildScrollUp(content); 65 66 } 67 68 // 从PtrHandler的默认实现类 PtrDefaultHandler中找到的,用以判断是否可以下拉刷新 69 public static boolean canChildScrollUp(View view) { 70 if (android.os.Build.VERSION.SDK_INT < 14) { 71 if (view instanceof AbsListView) { 72 final AbsListView absListView = (AbsListView) view; 73 return absListView.getChildCount() > 0 74 && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 75 .getTop() < absListView.getPaddingTop()); 76 } else { 77 return view.getScrollY() > 0; 78 } 79 } else { 80 return view.canScrollVertically(-1); 81 } 82 } 83 84 85 /** 86 * 设置ListView的监听回调 87 */ 88 public void setUltraRefreshListener(UltraRefreshListener mUltraRefreshListener) { 89 this.mUltraRefreshListener = mUltraRefreshListener; 90 } 91 92 @Override 93 public void onScrollStateChanged(AbsListView view, int scrollState) { 94 95 } 96 97 @Override 98 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 99 /*Log.i("info","isLoadData:"+isLoadData+" totalItemCount "+totalItemCount+" firstVisibleItem "+ 100 firstVisibleItem+" visibleItemCount "+ visibleItemCount); 101 */ 102 //加载更多的判断 103 if(totalItemCount>1&&!isLoadData&&totalItemCount==firstVisibleItem+visibleItemCount){ 104 isRefresh =false; 105 isLoadData = true; 106 addFooterView(footView); 107 mUltraRefreshListener.addMore(); 108 } 109 } 110 111 112 113 //刷新完成的后调用此方法还原布局 114 public void refreshComplete(){ 115 isLoadData = false; 116 if(isRefresh){ 117 //获取其父控件,刷新 118 ViewParent parent = getParent(); 119 if(parent instanceof PtrClassicFrameLayout){ 120 ((PtrClassicFrameLayout) parent).refreshComplete(); 121 } 122 }else{ 123 removeFooterView(footView); 124 } 125 } 126 }
其中有以下几个关键点需要注意:
isRefresh
:标清当前是正在下拉刷新还是在正在加载更多,这样就不用其调用者在使用refreshComplete()
时,传入相应参数。checkCanDoRefresh()
方法中,在判断是否可以刷新时,加入了!isLoadData
,该目的是,当正在加载更多时,不允许下拉刷新。checkContentCanBePulledDown()
和canChildScrollUp()
方法的实现,是从其源码PtrDefaultHandler
中找到的,作者已经给出了比较靠谱的实现方式。
该方法,将下拉刷新和上拉加载整合到了一起,定义一个共同的接口以便进行相应操作的回调
1 /** 2 * 数据接口的回调 3 * Created by Alex_MaHao on 2016/5/18. 4 */ 5 public interface UltraRefreshListener { 6 7 //下拉刷新 8 void onRefresh(); 9 10 //上拉加载 11 void addMore(); 12 }
最后我们看一下如何使用
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:orientation="vertical" android:layout_width="match_parent" 3 android:layout_height="match_parent"> 4 5 <in.srain.cube.views.ptr.PtrClassicFrameLayout 6 android:id="@+id/ultra_ptr" 7 android:layout_width="match_parent" 8 android:layout_height="match_parent"> 9 10 <com.mahao.alex.systemwidgetdemo.listView.ultra_refresh.UltraRefreshListView 11 android:id="@+id/ultra_lv" 12 android:layout_width="match_parent" 13 android:layout_height="match_parent"/> 14 15 </in.srain.cube.views.ptr.PtrClassicFrameLayout> 16 17 </LinearLayout>
1 /** 2 * Created by Alex_MaHao on 2016/5/18. 3 */ 4 public class UltraRefreshListActivity extends AppCompatActivity implements UltraRefreshListener { 5 6 private PtrClassicFrameLayout mPtrFrame; 7 8 private List<String> datas = new ArrayList<>(); 9 10 private SimpleBaseAdapter mAdapter; 11 12 private UltraRefreshListView mLv; 13 14 @Override 15 protected void onCreate(@Nullable Bundle savedInstanceState) { 16 super.onCreate(savedInstanceState); 17 18 setContentView(R.layout.activity_listview_ultra); 19 20 //查找控件 21 mPtrFrame = ((PtrClassicFrameLayout) findViewById(R.id.ultra_ptr)); 22 23 mLv = ((UltraRefreshListView) findViewById(R.id.ultra_lv)); 24 25 //创建我们的自定义头部视图 26 CustomUltraRefreshHeader header = new CustomUltraRefreshHeader(this); 27 28 //设置头部视图 29 mPtrFrame.setHeaderView(header); 30 31 //设置视图修改的回调,因为我们的CustomUltraRefreshHeader实现了PtrUIHandler 32 mPtrFrame.addPtrUIHandler(header); 33 34 //设置数据刷新的会回调,因为UltraRefreshListView实现了PtrHandler 35 mPtrFrame.setPtrHandler(mLv); 36 37 mAdapter = new SimpleBaseAdapter(datas); 38 39 mLv.setAdapter(mAdapter); 40 41 //设置数据刷新回调接口 42 mLv.setUltraRefreshListener(this); 43 } 44 45 @Override 46 public void onRefresh() { 47 mPtrFrame.postDelayed(new Runnable() { 48 @Override 49 public void run() { 50 datas.clear(); 51 for(int i = 0;i<20;i++){ 52 datas.add("添加了数据~~"+i); 53 } 54 //刷新完成 55 mLv.refreshComplete(); 56 mAdapter.notifyDataSetChanged(); 57 } 58 },1000); 59 60 } 61 62 @Override 63 public void addMore() { 64 mPtrFrame.postDelayed(new Runnable() { 65 @Override 66 public void run() { 67 68 int count = mAdapter.getCount(); 69 for(int i = count; i< count +10; i++){ 70 datas.add("添加了数据~~"+i); 71 } 72 mAdapter.notifyDataSetChanged(); 73 //刷新完成 74 mLv.refreshComplete(); 75 } 76 },1000); 77 78 } 79 }
OK,注释你要看不懂,那我也很纠结该怎么办。
该控件非常好用,推荐。