分享自: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,注釋你要看不懂,那我也很糾結該怎么辦。
該控件非常好用,推薦。