編輯:關於Android編程
該篇為ListView下拉刷新和上拉加載實現的各種方法大合集。可能在具體的細節邏輯上處理不太到位,但基本上完成邏輯的實現。細節方面,個人可以根據自己的需求進行完善。
該博客將以四種思路來完成下拉刷新和上拉加載
自定義View實現上拉加載和下拉刷新 使用PullToRefresh 實現上拉加載和下拉刷新 使用Ultra-Pull-To-Refresh實現上拉加載和下拉刷新 使用SwipeToRefreshLayout實現上拉加載和下拉刷新如果你對ListView
不夠熟悉,歡迎看我之前的博客 ListView使用總結
該方法,我們通過完全自定義的方式實現,不添加任何依賴庫和jar包。純原生。
首先看一下我們實現的效果
實現的關鍵點:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCs6qPGNvZGU+TGlzdFZpZXc8L2NvZGU+zO280823sry+1rrNtdeyvL7WoaMgzai5/bjEseTNt7K8vta1xDxjb2RlPnBhZGRpbmdUb3A8L2NvZGU+1rWjrMC0v9jWxr/YvP61xM/Uyr66zdL+stgguPm+3c7Sw8e7rLavtcTXtMyso6y2r8ys0N64xM23sr+yvL7Wus2117K/sry+1qGjDQo8cD6/tNK7z8K0+sLro7o8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;">
public class CustomRefreshListView extends ListView implements OnScrollListener{
/**
* 頭布局
*/
private View headerView;
/**
* 頭部布局的高度
*/
private int headerViewHeight;
/**
* 頭部旋轉的圖片
*/
private ImageView iv_arrow;
/**
* 頭部下拉刷新時狀態的描述
*/
private TextView tv_state;
/**
* 下拉刷新時間的顯示控件
*/
private TextView tv_time;
/**
* 底部布局
*/
private View footerView;
/**
* 底部旋轉progressbar
*/
private ProgressBar pb_rotate;
/**
* 底部布局的高度
*/
private int footerViewHeight;
/**
* 按下時的Y坐標
*/
private int downY;
private final int PULL_REFRESH = 0;//下拉刷新的狀態
private final int RELEASE_REFRESH = 1;//松開刷新的狀態
private final int REFRESHING = 2;//正在刷新的狀態
/**
* 當前下拉刷新處於的狀態
*/
private int currentState = PULL_REFRESH;
/**
* 頭部布局在下拉刷新改變時,圖標的動畫
*/
private RotateAnimation upAnimation,downAnimation;
/**
* 當前是否在加載數據
*/
private boolean isLoadingMore = false;
public CustomRefreshListView(Context context) {
this(context,null);
}
public CustomRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init(){
//設置滑動監聽
setOnScrollListener(this);
//初始化頭布局
initHeaderView();
//初始化頭布局中圖標的旋轉動畫
initRotateAnimation();
//初始化為尾布局
initFooterView();
}
/**
* 初始化headerView
*/
private void initHeaderView() {
headerView = View.inflate(getContext(), R.layout.head_custom_listview, null);
iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);
pb_rotate = (ProgressBar) headerView.findViewById(R.id.pb_rotate);
tv_state = (TextView) headerView.findViewById(R.id.tv_state);
tv_time = (TextView) headerView.findViewById(R.id.tv_time);
//測量headView的高度
headerView.measure(0, 0);
//獲取高度,並保存
headerViewHeight = headerView.getMeasuredHeight();
//設置paddingTop = -headerViewHeight;這樣,該控件被隱藏
headerView.setPadding(0, -headerViewHeight, 0, 0);
//添加頭布局
addHeaderView(headerView);
}
/**
* 初始化旋轉動畫
*/
private void initRotateAnimation() {
upAnimation = new RotateAnimation(0, -180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
upAnimation.setDuration(300);
upAnimation.setFillAfter(true);
downAnimation = new RotateAnimation(-180, -360,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
downAnimation.setDuration(300);
downAnimation.setFillAfter(true);
}
//初始化底布局,與頭布局同理
private void initFooterView() {
footerView = View.inflate(getContext(), R.layout.foot_custom_listview, null);
footerView.measure(0, 0);
footerViewHeight = footerView.getMeasuredHeight();
footerView.setPadding(0, -footerViewHeight, 0, 0);
addFooterView(footerView);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//獲取按下時y坐標
downY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if(currentState==REFRESHING){
//如果當前處在滑動狀態,則不做處理
break;
}
//手指滑動偏移量
int deltaY = (int) (ev.getY() - downY);
//獲取新的padding值
int paddingTop = -headerViewHeight + deltaY;
if(paddingTop>-headerViewHeight && getFirstVisiblePosition()==0){
//向下滑,且處於頂部,設置padding值,該方法實現了頂布局慢慢滑動顯現
headerView.setPadding(0, paddingTop, 0, 0);
if(paddingTop>=0 && currentState==PULL_REFRESH){
//從下拉刷新進入松開刷新狀態
currentState = RELEASE_REFRESH;
//刷新頭布局
refreshHeaderView();
}else if (paddingTop<0 && currentState==RELEASE_REFRESH) {
//進入下拉刷新狀態
currentState = PULL_REFRESH;
refreshHeaderView();
}
return true;//攔截TouchMove,不讓listview處理該次move事件,會造成listview無法滑動
}
break;
case MotionEvent.ACTION_UP:
if(currentState==PULL_REFRESH){
//仍處於下拉刷新狀態,未滑動一定距離,不加載數據,隱藏headView
headerView.setPadding(0, -headerViewHeight, 0, 0);
}else if (currentState==RELEASE_REFRESH) {
//滑倒一定距離,顯示無padding值得headcView
headerView.setPadding(0, 0, 0, 0);
//設置狀態為刷新
currentState = REFRESHING;
//刷新頭部布局
refreshHeaderView();
if(listener!=null){
//接口回調加載數據
listener.onPullRefresh();
}
}
break;
}
return super.onTouchEvent(ev);
}
/**
* 根據currentState來更新headerView
*/
private void refreshHeaderView(){
switch (currentState) {
case PULL_REFRESH:
tv_state.setText("下拉刷新");
iv_arrow.startAnimation(downAnimation);
break;
case RELEASE_REFRESH:
tv_state.setText("松開刷新");
iv_arrow.startAnimation(upAnimation);
break;
case REFRESHING:
iv_arrow.clearAnimation();//因為向上的旋轉動畫有可能沒有執行完
iv_arrow.setVisibility(View.INVISIBLE);
pb_rotate.setVisibility(View.VISIBLE);
tv_state.setText("正在刷新...");
break;
}
}
/**
* 完成刷新操作,重置狀態,在你獲取完數據並更新完adater之後,去在UI線程中調用該方法
*/
public void completeRefresh(){
if(isLoadingMore){
//重置footerView狀態
footerView.setPadding(0, -footerViewHeight, 0, 0);
isLoadingMore = false;
}else {
//重置headerView狀態
headerView.setPadding(0, -headerViewHeight, 0, 0);
currentState = PULL_REFRESH;
pb_rotate.setVisibility(View.INVISIBLE);
iv_arrow.setVisibility(View.VISIBLE);
tv_state.setText("下拉刷新");
tv_time.setText("最後刷新:"+getCurrentTime());
}
}
/**
* 獲取當前系統時間,並格式化
* @return
*/
private String getCurrentTime(){
SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
return format.format(new Date());
}
private OnRefreshListener listener;
public void setOnRefreshListener(OnRefreshListener listener){
this.listener = listener;
}
public interface OnRefreshListener{
void onPullRefresh();
void onLoadingMore();
}
/**
* SCROLL_STATE_IDLE:閒置狀態,就是手指松開
* SCROLL_STATE_TOUCH_SCROLL:手指觸摸滑動,就是按著來滑動
* SCROLL_STATE_FLING:快速滑動後松開
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if(scrollState==OnScrollListener.SCROLL_STATE_IDLE
&& getLastVisiblePosition()==(getCount()-1) &&!isLoadingMore){
isLoadingMore = true;
footerView.setPadding(0, 0, 0, 0);//顯示出footerView
setSelection(getCount());//讓listview最後一條顯示出來,在頁面完全顯示出底布局
if(listener!=null){
listener.onLoadingMore();
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
}
代碼的注釋寫的比較詳細,在這裡只說明主要的邏輯。
下拉刷新是通過設置setOnTouchListener()
方法,監聽觸摸事件,通過手指滑動的不同處理實現相應邏輯。
實現比較復雜,分為了三個情況,初始狀態(顯示下拉刷新),釋放刷新狀態,刷新狀態。
其中下拉刷新狀態和釋放狀態的改變,是由於手指滑動的不同距離,是在MotionEvent.ACTION_MOVE
中進行判斷,該判斷不處理任何數據邏輯,只是根據手指滑動的偏移量該表UI的顯示。
刷新狀態的判斷是在MotionEvent.ACTION_UP
手指抬起時判斷的。這很好理解,因為最終下拉刷新是否加載數據的確定,是由我們手指離開屏幕時與初始值的偏移量確定的。如果我們的偏移量小於了頭布局的高度,代表不刷新,繼續隱藏頭布局。如果偏移量大於了頭布局的高度,代表刷新,修改UI,同時通過接口回調,讓其持有者進行加載數據。
上拉加載和下拉刷新不同,他的視線較為簡單,我們通過ListView
的滾動監聽進行處理相應邏輯。即setOnScrollListener(this);
。
該方法需要實現兩個回調方法
public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount)
:滾動監聽的調用。 public void onScrollStateChanged(AbsListView view, int scrollState)
:滑動狀態改變的回調。其中scrollState
為回調的狀態,可能值為我們在onScrollStateChanged
中進行判斷,主要判斷一下條件;
如果符合條件,則開始加載數據,通過接口回調。
該類庫已經很久沒有更新過了,該類庫因為作者的不更新,導致局限性非常大。在這裡十分的不推薦,但還是說一下簡單的用法
Android-PullToRefresh">下載地址
添加控件
因為其繼承ListView
,所以其加載數據用法和ListView
一樣
private void initPTR() {
// TODO 初始化下拉刷新控件的相關設置
//1. 設置刷新模式
ptrListView.setMode(Mode.BOTH);
//2. 設置下拉或上拉事件監聽
ptrListView.setOnRefreshListener(new OnRefreshListener2() {
@Override
public void onPullDownToRefresh(PullToRefreshBase refreshView) {
// TODO 下拉刷新數據
refreshData(false);
}
@Override
public void onPullUpToRefresh(PullToRefreshBase refreshView) {
// TODO 上拉加載下頁數據
loadData(true);
}
});
//3. 定制下拉或上拉布局中相應的提示信息與加載圖標
//獲取下拉布局的代理對象
ILoadingLayout proxyPullDown =
ptrListView.getLoadingLayoutProxy(true,false);
proxyPullDown.setPullLabel("下拉刷新");
proxyPullDown.setReleaseLabel("放開以刷新");
proxyPullDown.setRefreshingLabel("正在玩命地加載....");
proxyPullDown.setLoadingDrawable(getResources().getDrawable(R.drawable.ic_launcher));
ILoadingLayout proxyPullUp =
ptrListView.getLoadingLayoutProxy(false,true);
proxyPullUp.setPullLabel("上拉加載更多");
proxyPullUp.setReleaseLabel("放開以加載");
proxyPullUp.setRefreshingLabel("正在玩命地加載....");
proxyPullUp.setLoadingDrawable(getResources().getDrawable(android.R.drawable.ic_menu_camera));
}
在其中分為了三部:
ptrListView.setMode(Mode.BOTH);
:Mode.BOTH
表示上拉加載和下拉刷新都有 setOnRefreshListener()
:注意其傳入參數為OnRefreshListener2
,如果傳入OnRefreshListener
,表示只有刷新 getLoadingLayoutProxy()
方法,傳入參數的不同分別獲得上拉刷新和下拉加載的布局代理對象 完成數據的加載,結束下拉刷新或上拉加載,調用ptrListView.onRefreshComplete();
這個刷新沒有例子,上面還是我以前總結的筆記。該三方庫已經有3年沒有更新了。真的不推薦使用啊。
Ultra-Pull-To-Refresh是一個功能非常強大的類庫,通過他,我們可以實現非常豐富的下拉刷新視圖,並且他支持幾乎所有的控件的下拉刷新(不僅僅是ListView
),但該視圖不支持上拉加載,作者可能在考慮此庫設計時的想法與Google
官方的SwipeRefreshLayout
的理念符合。即刷新可能是許多控件都需要,而上拉加載只有列表視圖需要。
那麼我們就開始嘗試使用他吧。
build.gradle
中,加上如下代碼
allprojects {
repositories {
jcenter()
mavenCentral();
maven {
url 'https://oss.sonatype.org/content/repositories/snapshots'
}
}
}
添加依賴包
compile 'in.srain.cube:ultra-ptr:1.0.11'
Clean一下工程即可
因為作者提供的只有AndroidStudio
版本,所以,我把它代碼導出到了一個Eclipse
工程中,直接添加依賴即可。
Ultra-Pull-To-Refresh Eclipse 版本下載地址
添加xml文件:activity_listview_ultra_refresh
<framelayout android:background="#f00" android:id="@+id/ultra_refresh_frame" android:layout_height="match_parent" android:layout_width="match_parent" android:paddingtop="100dp">
</framelayout>
添加了一個PtrClassicFrameLayout
包含了一個FrameLayout
.
看一下java代碼
public class UltraRefreshActivity extends Activity {
private PtrClassicFrameLayout ptrFrame;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_listview_ultra_refresh);
ptrFrame = ((PtrClassicFrameLayout) findViewById(R.id.ultra_ptr_frame));
ptrFrame.setLastUpdateTimeRelateObject(this);
//下拉刷新的阻力,下拉時,下拉距離和顯示頭部的距離比例,值越大,則越不容易滑動
ptrFrame.setRatioOfHeaderHeightToRefresh(1.2f);
ptrFrame.setDurationToClose(200);//返回到刷新的位置(暫未找到)
ptrFrame.setDurationToCloseHeader(1000);//關閉頭部的時間 // default is false
ptrFrame.setPullToRefresh(false);//當下拉到一定距離時,自動刷新(true),顯示釋放以刷新(false)
ptrFrame.setKeepHeaderWhenRefresh(true);//見名只意
ptrFrame.setPtrHandler(new PtrHandler() {
@Override
public boolean checkCanDoRefresh(PtrFrameLayout frame,
View content, View header) {
return PtrDefaultHandler.checkContentCanBePulledDown(frame,
content, header);
}
@Override
public void onRefreshBegin(PtrFrameLayout frame) {
//數據刷新的回調
ptrFrame.postDelayed(new Runnable() {
@Override
public void run() {
ptrFrame.refreshComplete();
}
}, 1500);
}
});
}
}
看一下效果
通過實現過程,我們可以發現,其下拉刷新和其包含的控件沒有之間聯系:也就是說我們可以在PtrClassicFrameLayout
包含任何子控件。
他可以設置的一些常量,代碼注釋中都有體現。而他有兩個關鍵性的方法:
ptrFrame.setPtrHandler(new PtrHandler())
:數據刷新的接口回調。包含兩個回調方法checkCanDoRefresh
:是否能夠刷新。 onRefreshBegin
:開始刷新的回調。 ptrFrame.addPtrUIHandler(new PtrUIHandler())
:UI更新接口的回調。其中PtrUIHandler
為一個接口,其定義如下
public interface PtrUIHandler {
/**
* When the content view has reached top and refresh has been completed, view will be reset.
*
* @param frame
*/
public void onUIReset(PtrFrameLayout frame);
/**
* prepare for loading
*
* @param frame
*/
public void onUIRefreshPrepare(PtrFrameLayout frame);
/**
* perform refreshing UI
*/
public void onUIRefreshBegin(PtrFrameLayout frame);
/**
* perform UI after refresh
*/
public void onUIRefreshComplete(PtrFrameLayout frame);
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator);
}
含義如下:
onUIRefreshPrepare
:開始下拉之前的接口回調。 onUIRefreshBegin
:開始刷新的接口回調。 onUIRefreshComplete
:刷新完成的接口回調。 onUIReset
:刷新完成之後,UI消失之後的接口回調。 onUIPositionChange
:下拉滑動的接口回調,多次調用。isUnderTouch
:手指是否觸摸 status
:狀態值 ptrIndicator
:滑動偏移量等值的封裝對象。
那麼對於onUIPositionChange
中,都是返回了那些值呢,我寫了一個自定義的HeadView,並設置高位100,同時打印相應的Log。在這只貼onUIPositionChange
中的Log代碼。
@Override
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {
int headerHeight = ptrIndicator.getHeaderHeight();//頭部的高度
int lastPosY = ptrIndicator.getLastPosY();//上一次滑動的Y偏移值
int offsetToRefresh = ptrIndicator.getOffsetToRefresh();//刷新需要滑動的偏移量
float offsetY = ptrIndicator.getOffsetY();//當前與上一次滑動處理的偏移值
int currentPosY = ptrIndicator.getCurrentPosY();//當前系統偏移值
Log.i("info","isUnderTouch"+isUnderTouch+"headHeight: "+headerHeight+" lastPosY "+lastPosY+" offsetToRefresh "+offsetToRefresh+" offsetY "+offsetY+" currentPosY "+currentPosY);
}
看一下打印信息
onUIRefreshPrepare
isUnderTouchtrueheadHeight: 100 lastPosY 0 offsetToRefresh 120 offsetY 23.025852 currentPosY 23
............................................................
isUnderTouchtrueheadHeight: 100 lastPosY 299 offsetToRefresh 120 offsetY 2.1575928 currentPosY 301
onUIRefreshBegin
isUnderTouchfalseheadHeight: 100 lastPosY 301 offsetToRefresh 120 offsetY 2.1575928 currentPosY 278
............................................................
isUnderTouchfalseheadHeight: 100 lastPosY 101 offsetToRefresh 120 offsetY 2.1575928 currentPosY 100
onUIRefreshComplete
isUnderTouchfalseheadHeight: 100 lastPosY 100 offsetToRefresh 120 offsetY 2.1575928 currentPosY 96
............................................................
isUnderTouchtrueheadHeight: 100 lastPosY 239 offsetToRefresh 120 offsetY 1.324391 currentPosY 240
onUIRefreshComplete
isUnderTouchfalseheadHeight: 100 lastPosY 240 offsetToRefresh 120 offsetY 1.324391 currentPosY 223
............................................................
isUnderTouchfalseheadHeight: 100 lastPosY 2 offsetToRefresh 120 offsetY 1.324391 currentPosY 1
onUIReset
isUnderTouchfalseheadHeight: 100 lastPosY 1 offsetToRefresh 120 offsetY 1.324391 currentPosY 0
關於每個字段的含義,代碼注釋已經說的很清楚了,不在多述。
看一下我們將要實現的效果:
自定義頭部的代碼實現
public class CustomUltraRefreshHeader extends RelativeLayout implements PtrUIHandler {
CircleView mCircleView;
TextView mDescText;
private ObjectAnimator anim;
public CustomUltraRefreshHeader(Context context) {
this(context,null);
}
public CustomUltraRefreshHeader(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomUltraRefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(widthMeasureSpec,100);
}
/**
* 初始化布局
*/
private void initView() {
int circlewidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, getResources().getDisplayMetrics());
mCircleView = new CircleView(getContext());
LinearLayout.LayoutParams circleParams = new LinearLayout.LayoutParams(circlewidth,circlewidth);
mCircleView.setLayoutParams(circleParams);
mDescText = new TextView(getContext());
LinearLayout.LayoutParams descParams = new LinearLayout.LayoutParams(circlewidth*3, ViewGroup.LayoutParams.WRAP_CONTENT);
descParams.gravity = Gravity.CENTER;
descParams.setMargins(circlewidth/2,0,0,0);
mDescText.setLayoutParams(descParams);
mDescText.setTextSize(12);
mDescText.setTextColor(Color.GRAY);
mDescText.setText("下拉刷新");
//添加線性的父布局
LinearLayout ll = new LinearLayout(getContext());
RelativeLayout.LayoutParams llParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
llParams.addRule(CENTER_IN_PARENT);
ll.setLayoutParams(llParams);
ll.setPadding(10,10,10,10);
ll.addView(mCircleView);
ll.addView(mDescText);
addView(ll);
}
@Override
public void onUIReset(PtrFrameLayout frame) {
//重置時,將動畫置為初始狀態
mCircleView.setRotation(0f);
Log.i("info","onUIReset");
}
@Override
public void onUIRefreshPrepare(PtrFrameLayout frame) {
mDescText.setText("下拉加載數據");
Log.i("info","onUIRefreshPrepare");
}
@Override
public void onUIRefreshBegin(PtrFrameLayout frame) {
//開始刷新,啟動動畫
anim = ObjectAnimator.ofFloat(mCircleView, "rotation", mCircleView.getRotation(), mCircleView.getRotation()+360f)
.setDuration(500);
anim.setRepeatCount(ValueAnimator.INFINITE);
anim.setRepeatMode(ValueAnimator.RESTART);
anim.start();
mDescText.setText("正在加載數據");
Log.i("info","onUIRefreshBegin");
}
@Override
public void onUIRefreshComplete(PtrFrameLayout frame) {
anim.cancel();
mDescText.setText("加載完成");
Log.i("info","onUIRefreshComplete");
}
@Override
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {
int headerHeight = ptrIndicator.getHeaderHeight();//頭部的高度
int lastPosY = ptrIndicator.getLastPosY();//上一次滑動的Y偏移值
int offsetToRefresh = ptrIndicator.getOffsetToRefresh();//舒心需要滑動的偏移量
float offsetY = ptrIndicator.getOffsetY();//當前與上一次滑動處理的偏移值
int currentPosY = ptrIndicator.getCurrentPosY();//當前系統偏移值
if (isUnderTouch&&status== PtrFrameLayout.PTR_STATUS_PREPARE) {
mCircleView.setRotation(currentPosY);
if(currentPosY= offsetToRefresh){
//表示不刷新了
mDescText.setText("下拉加載數據");
}else if(currentPosY>offsetToRefresh&&lastPosY<=offsetToRefresh){
mDescText.setText("松開加載更多");
}
}
/* if (currentPos < mOffsetToRefresh && lastPos >= mOffsetToRefresh) {
if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {*//*
Log.i("info","isUnderTouch"+isUnderTouch+"headHeight: "+headerHeight+" lastPosY "+lastPosY+" offsetToRefresh "+offsetToRefresh+" offsetY "+offsetY+" currentPosY "+currentPosY);*/
}
}
在這裡我們實現了PtrUIHandler
接口,用於下拉刷新時的接口會調,有兩點說明:
使用的CircleView
為自定義控件,就是圖中的轉盤,在我以前的博客中有相應的實現。RecyclerView 下拉刷新和上拉加載
在動態改變現實視圖時,需要注意的便是onUIPositionChange
,我們只有在手指觸摸且status
為PtrFrameLayout.PTR_STATUS_PREPARE
才修改我們的屬性,其他時候不能修改。因為該方法會一直回調。
封裝自定義的UltraRefreshListView
繼承ListView
,並實現了PtrHandler
接口,用以數據更新回調。
public class UltraRefreshListView extends ListView implements PtrHandler,AbsListView.OnScrollListener {
private UltraRefreshListener mUltraRefreshListener;
/**
* 根布局
*/
private View footView;
/**
* 是否正在加載數據
*/
private boolean isLoadData = false;
/**
* 是否是下拉刷新,主要在處理結果時使用
*/
private boolean isRefresh = false;
public UltraRefreshListView(Context context) {
this(context,null);
}
public UltraRefreshListView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public UltraRefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//初始化布局,及添加一個跟布局
initView();
}
private void initView() {
footView = LayoutInflater.from(getContext()).inflate(R.layout.foot_ultra_refresh_listview, null);
setOnScrollListener(this);
}
@Override
public void onRefreshBegin(PtrFrameLayout frame) {
isLoadData =true;
isRefresh =true;
//下拉刷新的回調
if(mUltraRefreshListener!=null){
mUltraRefreshListener.onRefresh();
}
}
@Override
public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
// PtrHandler 的接口回調,是否能夠加載數據的判斷
return !isLoadData&&checkContentCanBePulledDown(frame, content, header);
}
// 從PtrHandler的默認實現類 PtrDefaultHandler中找到的,用以判斷是否可以下拉刷新
public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) {
return !canChildScrollUp(content);
}
// 從PtrHandler的默認實現類 PtrDefaultHandler中找到的,用以判斷是否可以下拉刷新
public static boolean canChildScrollUp(View view) {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (view instanceof AbsListView) {
final AbsListView absListView = (AbsListView) view;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return view.getScrollY() > 0;
}
} else {
return view.canScrollVertically(-1);
}
}
/**
* 設置ListView的監聽回調
*/
public void setUltraRefreshListener(UltraRefreshListener mUltraRefreshListener) {
this.mUltraRefreshListener = mUltraRefreshListener;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
/*Log.i("info","isLoadData:"+isLoadData+" totalItemCount "+totalItemCount+" firstVisibleItem "+
firstVisibleItem+" visibleItemCount "+ visibleItemCount);
*/
//加載更多的判斷
if(totalItemCount>1&&!isLoadData&&totalItemCount==firstVisibleItem+visibleItemCount){
isRefresh =false;
isLoadData = true;
addFooterView(footView);
mUltraRefreshListener.addMore();
}
}
//刷新完成的後調用此方法還原布局
public void refreshComplete(){
isLoadData = false;
if(isRefresh){
//獲取其父控件,刷新
ViewParent parent = getParent();
if(parent instanceof PtrClassicFrameLayout){
((PtrClassicFrameLayout) parent).refreshComplete();
}
}else{
removeFooterView(footView);
}
}
}
其中有以下幾個關鍵點需要注意:
isRefresh
:標清當前是正在下拉刷新還是在正在加載更多,這樣就不用其調用者在使用refreshComplete()
時,傳入相應參數。 checkCanDoRefresh()
方法中,在判斷是否可以刷新時,加入了!isLoadData
,該目的是,當正在加載更多時,不允許下拉刷新。 checkContentCanBePulledDown()
和canChildScrollUp()
方法的實現,是從其源碼PtrDefaultHandler
中找到的,作者已經給出了比較靠譜的實現方式。
該方法,將下拉刷新和上拉加載整合到了一起,定義一個共同的接口以便進行相應操作的回調
/**
* 數據接口的回調
* Created by Alex_MaHao on 2016/5/18.
*/
public interface UltraRefreshListener {
//下拉刷新
void onRefresh();
//上拉加載
void addMore();
}
最後我們看一下如何使用
/**
* Created by Alex_MaHao on 2016/5/18.
*/
public class UltraRefreshListActivity extends AppCompatActivity implements UltraRefreshListener {
private PtrClassicFrameLayout mPtrFrame;
private List datas = new ArrayList<>();
private SimpleBaseAdapter mAdapter;
private UltraRefreshListView mLv;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_listview_ultra);
//查找控件
mPtrFrame = ((PtrClassicFrameLayout) findViewById(R.id.ultra_ptr));
mLv = ((UltraRefreshListView) findViewById(R.id.ultra_lv));
//創建我們的自定義頭部視圖
CustomUltraRefreshHeader header = new CustomUltraRefreshHeader(this);
//設置頭部視圖
mPtrFrame.setHeaderView(header);
//設置視圖修改的回調,因為我們的CustomUltraRefreshHeader實現了PtrUIHandler
mPtrFrame.addPtrUIHandler(header);
//設置數據刷新的會回調,因為UltraRefreshListView實現了PtrHandler
mPtrFrame.setPtrHandler(mLv);
mAdapter = new SimpleBaseAdapter(datas);
mLv.setAdapter(mAdapter);
//設置數據刷新回調接口
mLv.setUltraRefreshListener(this);
}
@Override
public void onRefresh() {
mPtrFrame.postDelayed(new Runnable() {
@Override
public void run() {
datas.clear();
for(int i = 0;i<20;i++){
datas.add("添加了數據~~"+i);
}
//刷新完成
mLv.refreshComplete();
mAdapter.notifyDataSetChanged();
}
},1000);
}
@Override
public void addMore() {
mPtrFrame.postDelayed(new Runnable() {
@Override
public void run() {
int count = mAdapter.getCount();
for(int i = count; i< count +10; i++){
datas.add("添加了數據~~"+i);
}
mAdapter.notifyDataSetChanged();
//刷新完成
mLv.refreshComplete();
}
},1000);
}
}
OK,注釋你要看不懂,那我也很糾結該怎麼辦。
該控件非常好用,推薦。
雖然之前的博客中說的是RecyclerView
的使用,但其使用方法完全相同,這裡不再重復了。
該項目源碼已經共享到我的github,在模塊
systemwidgetdemo
中。
在給大家講解了paint的幾個方法之後,我覺得有必要插一篇有關Canvas畫布的知識,在開始paint之前,我們講解了canvas繪圖的幾篇文章和cavas的save()
前言最近這幾個月都是在准備找工作和找工作中,付出了很多,總算是有點收獲,所以都沒有怎麼整理筆記。到了最近才有空把自己的筆記整理一下發上來,分享一下我的學習經驗。推送由於最
概述 本篇博客是對developer.android.com/上的Training課程的簡單翻譯,若是覺得翻譯出來的理解有困難,請點擊下方鏈接查看原文! 關
前言:項目中除了登陸,支付等接口采用rsa非對稱加密,之外的采用aes對稱加密,今天我們來認識一下aes加密。 其他幾種加密方式: •An