編輯:關於Android編程
在上一篇博文的最後,我說要寫一個下拉刷新的ListView和RecyclerView,並且可以直接使用QQheader
本篇博文准備講如何實現下拉刷新和上拉加載,寫的比較爛,篇幅比較長。
先預覽下最終效果吧,不然我怕你們看不下去=。=
在上一篇博文中,我們通過移動header的位置來實現下拉刷新。
但是這種方式在ListView中並不是十分合適,所以我需要修改一下實現方式。
vcq9wLTKtc/Wz8LArcui0MKhozwvcD4NCjxoMSBpZD0="二加載更多的實現方式">二、加載更多的實現方式
加載更多一般情況下有三種方式:
1. 滑動到最底部的時候顯示footer,上拉加載更多
2. 滑動到底部的時候,自動加載更多
3. 快要到底部的時候(還沒到底部,比如bilibili客戶端),自動加載更多
而博主我是用第二種, 雖然三種都可以同時實現,但是博主表示懶癌發作了=。=
關於監聽滾動事件也有兩種方式:
一是監聽onScrollStateChange,二是監聽onScroll。
這兩者有什麼區別呢,為什麼我會選擇onScroll拋棄onScrollStateChange:
在一次滾動事件中,onScrollStateChange只會回調兩次或三次,分別是scroll、fling、idle。那麼,第三種方式就不合適了,你並不能知道你當前滑動到哪個位置了
在界面初始化時, onScroll會執行, 而ScrollState不會。
有人可能會問,這有什麼用,不會執行不是更好麼?
如果你分頁加載,首次加載的時候很奇葩的數據沒有滿屏怎麼辦,這時候是不是應該再加載一頁數據。而onScroll因為在初始化時會執行,所以不需要做任何事情就能自適應了。
下面代碼以ListView為例,而RecyclerView代碼稍微會多一些,等以後我完成整個框架的時候直接上傳到Github再說。
我們先繼承ListView,處理一下事件
因為打算將邏輯都交給header和footer處理,所以代碼出奇的少,干干淨淨。
也因為將邏輯交給header/footer處理,ListView並不知道什麼時候調用刷新方法。所以當header進入刷新狀態時,就要回調ListView的刷新方法。所以我們需要定義一個接口。
/**
* Created by AItsuki on 2016/6/22.
* 需要使用刷新header的控件必須實現此接口
*/
public interface Refreshing {
void onRefreshCallBack();
}
/**
* Created by AItsuki on 2016/6/24.
* 同Refreshing
*/
public interface Loading {
void onLoadMoreCallBack();
}
/**
* Created by AItsuki on 2016/6/21.
*/
public class AListView extends ListView implements AbsListView.OnScrollListener, Refreshing, Loading {
private int activePointerId;
private float lastY;
public AListView(Context context) {
super(context);
initView();
}
public AListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public AListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int actionMasked = ev.getActionMasked();
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
activePointerId = ev.getPointerId(0);
lastY = ev.getY(0);
// header和footer的按下處理在這裡!
break;
case MotionEvent.ACTION_MOVE:
float y = ev.getY(MotionEventCompat.findPointerIndex(ev, activePointerId));
float diffY = y - lastY;
lastY = y;
// --------------header----------------------
if (getFirstVisiblePosition() == 0 && diffY > 0) {
// header的onMove在這處理
} else if (diffY < 0 && refreshHeader.getVisibleHeight() > 0) {
// 往上滑動,header回去的過程中,listView也會跟著滾動,導致滑動致header消失後,其實header高度還沒有到0
// setSelection可以保證header一直處於頂部
setSelection(0);
// header的onMove在這處理
}
// --------------footer--------------------
int lastVisible = getLastVisiblePosition();
int lastPosition = getAdapter().getCount() - 1;
if (getFirstVisiblePosition() != 0 && lastVisible == lastPosition) {
// footer的onMove也在這裡,讓footer也能拉動,彈彈彈!
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
onSecondaryPointerDown(ev);
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// header和footer的手指松開處理在這裡!
break;
}
return super.onTouchEvent(ev);
}
private void onSecondaryPointerDown(MotionEvent ev) {
int pointerIndex = MotionEventCompat.getActionIndex(ev);
lastY = ev.getY(pointerIndex);
activePointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
}
private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = MotionEventCompat.getActionIndex(ev);
final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
if (pointerId == activePointerId) {
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
lastY = ev.getY(newPointerIndex);
activePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// footer的onScroll事件在此~
}
/**
* Refreshing接口的方法, 當header進入刷新狀態時會回調此方法
*/
@Override
public void onRefreshCallBack() {
}
/**
* Loading接口的方法,當footer進入加載狀態時會回調此方法
*/
@Override
public void onLoadMoreCallBack() {
}
}
header和footer需要處理的地方都已經注釋過了,接下來我們就需要在header和footer中處理事件了。
相對於上一篇博客來說,header多了一個Failure狀態,不然無論刷新成功還是失敗,header都顯示刷新成功不太好。
package com.aitsuki.library;
import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Scroller;
/**
* Created by AItsuki on 2016/6/21.
* RefreshHeader相當於一個容器,裝著我們自定義的Header
* child通過getContentView方法將header傳進來。
*/
public abstract class RefreshHeader extends LinearLayout {
private static final String TAG = "RefreshHeader";
private static final float DRAG_RATE = 0.5f;
// scroller duration
private static final int SCROLL_TO_TOP_DURATION = 800;
private static final int SCROLL_TO_REFRESH_DURATION = 250;
private static final long SHOW_COMPLETED_TIME = 500;
private static final long SHOW_FAILURE_TIME = 500;
private long showCompletedTime = SHOW_COMPLETED_TIME;
private long showFailureTime = SHOW_FAILURE_TIME;
private View contentView; // header
private int refreshHeight; // 刷新高度
private int maxDragDistance;
private boolean isTouch;
private State state = State.RESET;
private final AutoScroll autoScroll;
private Refreshing refreshing;
public RefreshHeader(Context context) {
super(context);
maxDragDistance = Utils.dip2px(context, 500); // 默認500dp
autoScroll = new AutoScroll();
// content由子類創建
contentView = getContentView();
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
addView(contentView, lp);
measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
refreshHeight = getMeasuredHeight();
Log.e(TAG, "refreshHeight = " + refreshHeight);
}
protected void bindRefreshing(Refreshing refreshing) {
this.refreshing = refreshing;
}
protected void setVisibleHeight(int height) {
height = Math.max(0, height);
LayoutParams lp = (LayoutParams) contentView.getLayoutParams();
lp.height = height;
contentView.setLayoutParams(lp);
}
/**
* 設置成功狀態時,header的停留時間
*/
public void setShowCompletedTime(long time) {
if (time >= 0) {
showCompletedTime = time;
}
}
/**
* 設置失敗狀態時,header的停留時間
*/
public void setShowFailureTime(long time) {
if (time >= 0) {
showFailureTime = time;
}
}
/**
* 設置最大下拉高度(默認300dp)
*
* @param distance 最大下拉高度, 單位px
*/
public void setMaxDragDistance(int distance) {
if (distance > 0) {
maxDragDistance = distance;
}
}
public int getVisibleHeight() {
return contentView.getLayoutParams().height;
}
public int getRefreshHeight() {
return refreshHeight;
}
public State getState() {
return state;
}
public boolean isTouch() {
return isTouch;
}
public void onPress() {
isTouch = true;
removeCallbacks(delayToScrollTopRunnable);
autoScroll.stop();
}
public void onMove(float offset) {
if (offset == 0) {
return;
}
float dragRate = DRAG_RATE;
if (offset > 0) {
offset = offset * dragRate + 0.5f;
if(getVisibleHeight() > refreshHeight) {
// 因為忽略了refreshHeight,所以這裡會超出最大高度,直接價格判斷省事。
if(getVisibleHeight() >= maxDragDistance) {
offset = 0;
} else {
float extra = getVisibleHeight() - refreshHeight;
float extraPercent = Math.min(1, extra / maxDragDistance);
dragRate = Utils.calcDragRate(extraPercent);
offset = offset * dragRate + 0.5f;
}
}
}
int target = (int) Math.max(0, getVisibleHeight() + offset);
// 1. 在RESET狀態時,第一次下拉出現header的時候,設置狀態變成PULL
if (state == State.RESET && getVisibleHeight() == 0 && target > 0) {
changeState(State.PULL);
}
// 2. 在PULL或者COMPLETE狀態時,header回到頂部的時候,狀態變回RESET
if (getVisibleHeight() > 0 && target <= 0) {
if (state == State.PULL || state == State.COMPLETE || state == State.FAILURE) {
changeState(State.RESET);
}
}
// 3. 如果是從底部回到頂部的過程(往上滾動),並且手指是松開狀態, 並且當前是PULL狀態,狀態變成LOADING
if (state == State.PULL && !isTouch && getVisibleHeight() > refreshHeight && target <= refreshHeight) {
// 這時候我們需要強制停止autoScroll
autoScroll.stop();
changeState(State.REFRESHING);
target = refreshHeight;
}
setVisibleHeight(target);
Log.e("123123", "onMove: height = "+ getVisibleHeight());
onPositionChange();
}
/**
* 松開手指,滾動到刷新高度或者滾動回頂部
*/
public void onRelease() {
isTouch = false;
if (state == State.REFRESHING) {
if (getVisibleHeight() > refreshHeight) {
autoScroll.scrollTo(refreshHeight, SCROLL_TO_REFRESH_DURATION);
}
} else {
autoScroll.scrollTo(0, SCROLL_TO_TOP_DURATION);
}
}
public void setRefreshComplete(boolean success) {
if (success) {
changeState(State.COMPLETE);
} else {
changeState(State.FAILURE);
}
if (getVisibleHeight() == 0) {
changeState(State.RESET);
return;
}
if (!isTouch && getVisibleHeight() > 0) {
if (success) {
postDelayed(delayToScrollTopRunnable, showCompletedTime);
} else {
postDelayed(delayToScrollTopRunnable, showFailureTime);
}
}
}
private void changeState(State state) {
Log.e(TAG, "changeState: " + state.name());
this.state = state;
switch (state) {
case RESET:
onReset();
break;
case PULL:
onPullStart();
break;
case REFRESHING:
onRefreshing();
if (refreshing != null) {
refreshing.onRefreshCallBack();
}
break;
case COMPLETE:
onComplete();
break;
case FAILURE:
onFailure();
break;
}
}
/**
* header的內容
*
* @return view
*/
public abstract View getContentView();
/**
* header回到頂部了
*/
public abstract void onReset();
/**
* header被下拉了
*/
public abstract void onPullStart();
/**
* header高度變化的回調
*/
public abstract void onPositionChange();
/**
* header正在刷新
*/
public abstract void onRefreshing();
/**
* header刷新成功
*/
public abstract void onComplete();
/**
* header刷新失敗
*/
public abstract void onFailure();
/**
* 自動刷新
*/
public void autoRefresh() {
if (state != State.RESET) {
return;
}
// post可以保證View已經測量完畢
post(new Runnable() {
@Override
public void run() {
setVisibleHeight(refreshHeight);
changeState(State.REFRESHING);
}
});
}
private class AutoScroll implements Runnable {
private Scroller scroller;
private int lastY;
public AutoScroll() {
scroller = new Scroller(getContext());
}
@Override
public void run() {
boolean finished = !scroller.computeScrollOffset() || scroller.isFinished();
if (!finished) {
int currY = scroller.getCurrY();
int offset = currY - lastY;
lastY = currY;
onMove(offset);
post(this);
} else {
stop();
}
}
public void scrollTo(int to, int duration) {
int from = getVisibleHeight();
int distance = to - from;
stop();
if (distance == 0) {
return;
}
scroller.startScroll(0, 0, 0, distance, duration);
post(this);
}
private void stop() {
removeCallbacks(this);
if (!scroller.isFinished()) {
scroller.forceFinished(true);
}
lastY = 0;
}
}
public enum State {
RESET, PULL, REFRESHING, FAILURE, COMPLETE
}
// 刷新成功,顯示500ms成功狀態再滾動回頂部
private Runnable delayToScrollTopRunnable = new Runnable() {
@Override
public void run() {
autoScroll.scrollTo(0, SCROLL_TO_TOP_DURATION);
}
};
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (autoScroll != null) {
autoScroll.stop();
}
if (delayToScrollTopRunnable != null) {
removeCallbacks(delayToScrollTopRunnable);
delayToScrollTopRunnable = null;
}
}
}
具體的邏輯不多解釋,因為上一篇博客中已經將事件處理的邏輯說的很清楚了,其實是懶_(:з」∠)_
footer的狀態有四個RESET, LOADING, FAILURE, NO_MORE
reset:因為加載數據後,footer已經看不見了,所以沒必要success狀態,直接回到reset就行了
failure: 刷新失敗,顯示failure狀態,用戶點擊footer就可以重新加載。
no_more:沒有更多數據的時候進入這個狀態,footer高度設置為0,當下拉刷新或者手動設置後會重新顯示。
在這裡特別說明一點,footer的contentView不能設置點擊事件,事件應該在ListView中有onItemClick處理。
因為如果footer設置了點擊事件,那麼事件處理會出現問題。因為多點觸控的原因,是通過getY() getX()獲取坐標的,當手指在footer中滑動時,獲取到的坐標位置是相對於footer的。
package com.aitsuki.library; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.Scroller; /** * Created by AItsuki on 2016/6/23. * 邏輯: * 拉到最底部的時候顯示footer -- 從reset進入loading狀態 * 加載成功後,隱藏footer * 加載失敗,點擊footer可以重新加載 * <p/> * 1. loading失敗 -- 到failure狀態 * 2. loading成功 -- 到Reset狀態,隱藏footer */ public abstract class LoadMoreFooter extends LinearLayout { private static final float DRAG_RATE = 0.5f; private final AutoScroll autoScroll; private final int maxDragDistance; private View contentView; // footer private State state = State.RESET; private final int footerHeight; private Loading loading; private boolean canLoadMore = true; public LoadMoreFooter(Context context) { super(context); maxDragDistance = Utils.dip2px(context, 500); // 默認500dp autoScroll = new AutoScroll(); contentView = getContentView(); LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); addView(contentView, lp); measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); footerHeight = getMeasuredHeight(); } protected void bindLoading(Loading loading) { this.loading = loading; } protected void setVisibleHeight(int height) { LayoutParams lp = (LayoutParams) contentView.getLayoutParams(); lp.height = height; contentView.setLayoutParams(lp); } public int getVisibleHeight() { return contentView.getLayoutParams().height; } protected void setLoadMoreComplete(boolean successful) { if (successful) { changeState(State.RESET); } else { changeState(State.FAILURE); } } protected void onScroll(int lastVisiblePosition, int count) { // 當沒有數據的時候,不顯示footer if(count <= 0) { contentView.setVisibility(GONE); } else if(count > 0 && contentView.getVisibility() == GONE) { contentView.setVisibility(VISIBLE); } if (count > 0 && lastVisiblePosition >= count && state == State.RESET && canLoadMore) { changeState(LoadMoreFooter.State.LOADING); } } protected void onPress() { autoScroll.stop(); } protected void onRelease() { int height = canLoadMore ? footerHeight : 0; if(getVisibleHeight() > height) { autoScroll.scrollTo(height, 250); } } public State getState() { return state; } protected void changeState(State state) { this.state = state; switch (state) { case RESET: onReset(); break; case LOADING: onLoading(); if (loading != null) { loading.onLoadMoreCallBack(); } break; case FAILURE: onFailure(); break; case NO_MORE: onNoMore(); break; } } protected void onMove(float offset) { if (offset == 0) { return; } float dragRate = DRAG_RATE; if (offset < 0) { offset = offset * dragRate - 0.5f; if(getVisibleHeight() > footerHeight) { // 因為忽略了refreshHeight,所以這裡會超出最大高度,直接價格判斷省事。 if(getVisibleHeight() >= maxDragDistance) { offset = 0; } else { float extra = getVisibleHeight() - footerHeight; float extraPercent = Math.min(1, extra / maxDragDistance); dragRate = Utils.calcDragRate(extraPercent); offset = offset * dragRate - 0.5f; } } } // 往上滑動的時候才增加footer的高度,所以offset為負數。 int height = canLoadMore ? footerHeight : 0; int target = (int) Math.max(height, getVisibleHeight() - offset); setVisibleHeight(target); } protected void setCanLoadMore(boolean canLoadMore) { // 設置不能加載更多 if(!canLoadMore && state != State.NO_MORE) { changeState(State.NO_MORE); setVisibleHeight(0); this.canLoadMore = false; } else if(canLoadMore && state == State.NO_MORE) { changeState(State.RESET); setVisibleHeight(footerHeight); this.canLoadMore = true; } } private class AutoScroll implements Runnable { private Scroller scroller; private int lastY; public AutoScroll() { scroller = new Scroller(getContext()); } @Override public void run() { boolean finished = !scroller.computeScrollOffset() || scroller.isFinished(); if (!finished) { int currY = scroller.getCurrY(); int offset = currY - lastY; lastY = currY; onMove(offset); post(this); } else { stop(); } } public void scrollTo(int to, int duration) { int from = getVisibleHeight(); int distance = to - from; stop(); if (distance == 0) { return; } scroller.startScroll(0, 0, 0, -distance, duration); post(this); } private void stop() { removeCallbacks(this); if (!scroller.isFinished()) { scroller.forceFinished(true); } lastY = 0; } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (autoScroll != null) { autoScroll.stop(); } } protected void onItemClick() { if(state == State.FAILURE) { changeState(State.LOADING); } } public enum State { RESET, LOADING, FAILURE, NO_MORE } protected abstract View getContentView(); protected abstract void onReset(); protected abstract void onNoMore(); protected abstract void onLoading(); protected abstract void onFailure(); }
package com.aitsuki.library; import android.content.Context; import android.support.v4.view.MotionEventCompat; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewParent; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.ListView; /** * Created by AItsuki on 2016/6/21. * 下拉刷新:支持多點觸控,支持自定義header,完美體驗! * 加載更多:在這裡有兩種方式 * 1. 監聽scrollState,在idle狀態下並且到達listView底部,顯示加載更多 * 2. 監聽scrollState,滾動到某個位置(比如倒數第3個item時就開始loadmore) */ public class AListView extends ListView implements AbsListView.OnScrollListener, AdapterView.OnItemClickListener,Refreshing, Loading { private int activePointerId; private float lastY; private OnRefreshListener onRefreshListener; private OnLoadMoreListener onLoadMoreListener; private RefreshHeader refreshHeader; private LoadMoreFooter loadMoreFooter; private OnScrollListener scrollListener; // user's scroll listener private OnItemClickListener onItemClickListener; // user's itemClick listener private boolean waitingLoadingCompletedToRefresh; // 等待加載更多成功後去調用刷新 private boolean waitingRefreshCompletedToLoadMore; // 等待刷新成功後去調用加載更多 public enum CallBackMode { NORMAL, ENQUEUE } private CallBackMode callBackMode = CallBackMode.ENQUEUE; public AListView(Context context) { super(context); initView(); } public AListView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public AListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } public void setCanLoadMore(boolean canLoadMore) { loadMoreFooter.setCanLoadMore(canLoadMore); } private void initView() { super.setOnScrollListener(this); super.setOnItemClickListener(this); setOverScrollMode(OVER_SCROLL_NEVER); QQHeader QQHeader = new QQHeader(getContext()); setRefreshHeader(QQHeader); QQFooter footer = new QQFooter(getContext()); setLoadMoreFooter(footer); } /** * 設置刷新頭部 * @param refreshHeader header */ public void setRefreshHeader(RefreshHeader refreshHeader) { if (refreshHeader != null && this.refreshHeader != refreshHeader) { removeHeaderView(this.refreshHeader); LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); refreshHeader.setLayoutParams(lp); refreshHeader.bindRefreshing(this); addHeaderView(refreshHeader, null, false); this.refreshHeader = refreshHeader; } } /** * 設置加載更多的footer * @param footer footer */ public void setLoadMoreFooter(LoadMoreFooter footer) { if (footer != null && loadMoreFooter != footer) { removeFooterView(footer); LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); footer.setLayoutParams(lp); footer.bindLoading(this); addFooterView(footer); this.loadMoreFooter = footer; } } /** * 刷新偵聽 */ public void setOnRefreshListener(OnRefreshListener onRefreshListener) { this.onRefreshListener = onRefreshListener; } /** * 加載更多偵聽 */ public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) { this.onLoadMoreListener = onLoadMoreListener; } /** * 當數據加載完畢後,需要調用此方法讓header回到原位 * @param success 刷新成功,刷新失敗(header的狀態不一致:complete或者failure) */ public void refreshComplete(boolean success) { refreshHeader.setRefreshComplete(success); // 刷新成功後,將failure狀態重置 if (success && loadMoreFooter.getState() == LoadMoreFooter.State.FAILURE) { loadMoreFooter.changeState(LoadMoreFooter.State.RESET); } // 刷新成功後,如果需要加載更多,那麼回調onLoadMore if (waitingRefreshCompletedToLoadMore) { onLoadMoreCallBack(); waitingRefreshCompletedToLoadMore = false; } } /** * 當數據加載完畢後,需要調用此方法重置footer的狀態 * @param success 刷新成功:隱藏了。 刷新失敗,會顯示失敗狀態(點擊重試) */ public void loadMoreComplete(boolean success) { loadMoreFooter.setLoadMoreComplete(success); Log.e("123123", "loadMoreComplete"); // 加載成功後,如果需要刷新,那麼回調onRefresh if (waitingLoadingCompletedToRefresh) { onRefreshCallBack(); waitingLoadingCompletedToRefresh = false; } } /** * 自動刷新 */ public void autoRefresh() { refreshHeader.autoRefresh(); } /** * 當刷新和加載更多同時進行時,可能會導致分頁加載時的頁面錯誤。<br/> * 可以通過此方法設置處理方式:<br/> * NORMAL: 不做處理,但是開發者必須自己處理。<br/> * ENQUEUE: 隊列模式,刷新和加載更多同時進行時,只會回調其中一個,成功後再回調另一個(默認ENQUEUE) */ public void setCallBackMode(CallBackMode callBackMode) { this.callBackMode = callBackMode; } @Override public boolean onTouchEvent(MotionEvent ev) { int actionMasked = ev.getActionMasked(); switch (actionMasked) { case MotionEvent.ACTION_DOWN: activePointerId = ev.getPointerId(0); lastY = ev.getY(0); refreshHeader.onPress(); loadMoreFooter.onPress(); break; case MotionEvent.ACTION_MOVE: float y = ev.getY(MotionEventCompat.findPointerIndex(ev, activePointerId)); float diffY = y - lastY; lastY = y; // --------------header---------------------- if (getFirstVisiblePosition() == 0 && diffY > 0) { refreshHeader.onMove(diffY); } else if (diffY < 0 && refreshHeader.getVisibleHeight() > 0) { // 往上滑動,header回去的過程中,listView也會跟著滾動,導致滑動致header消失後,其實header高度還沒有到0 // setSelection可以保證header一直處於頂部 refreshHeader.onMove(diffY); setSelection(0); } // --------------footer-------------------- int lastVisible = getLastVisiblePosition(); int lastPosition = getAdapter().getCount() - 1; if (getFirstVisiblePosition() != 0 && lastVisible == lastPosition) { loadMoreFooter.onMove(diffY); } break; case MotionEvent.ACTION_POINTER_DOWN: onSecondaryPointerDown(ev); break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: refreshHeader.onRelease(); loadMoreFooter.onRelease(); break; } return super.onTouchEvent(ev); } private void onSecondaryPointerDown(MotionEvent ev) { int pointerIndex = MotionEventCompat.getActionIndex(ev); lastY = ev.getY(pointerIndex); activePointerId = MotionEventCompat.getPointerId(ev, pointerIndex); } private void onSecondaryPointerUp(MotionEvent ev) { final int pointerIndex = MotionEventCompat.getActionIndex(ev); final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); if (pointerId == activePointerId) { final int newPointerIndex = pointerIndex == 0 ? 1 : 0; lastY = ev.getY(newPointerIndex); activePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); } } /** * Refreshing接口的方法, 當header進入刷新狀態時會回調此方法 */ @Override public void onRefreshCallBack() { if (onRefreshListener != null) { if (loadMoreFooter.getState() == LoadMoreFooter.State.LOADING && callBackMode == CallBackMode.ENQUEUE) { // 等待加載更多完畢後回調onRefresh方法 waitingLoadingCompletedToRefresh = true; } else { onRefreshListener.onRefresh(); } } } /** * Loading接口的方法,當footer進入加載狀態時會回調此方法 */ @Override public void onLoadMoreCallBack() { if (onLoadMoreListener != null) { if (refreshHeader.getState() == RefreshHeader.State.REFRESHING && callBackMode == CallBackMode.ENQUEUE) { // 等待刷新完畢後回調onLoadMore方法 waitingRefreshCompletedToLoadMore = true; } else { onLoadMoreListener.onLoadMore(); } } } @Override public void setOnScrollListener(OnScrollListener l) { this.scrollListener = l; } /** * onScrollStateChanged和onScroll的區別: * <p/> * ListView滾動時,會一直執行onScroll。 而scrollState只會執行三次 * <p/> * 在初始化時, onScroll會執行, 而ScrollState不會 * <p/> * 在頂部或者底部滑動時,onScroll不會執行, 而ScrollState執行 */ @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollListener != null) { scrollListener.onScrollStateChanged(view, scrollState); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (scrollListener != null) { scrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } int lastVisiblePosition = getLastVisiblePosition(); int count = totalItemCount - getHeaderViewsCount() - getFooterViewsCount(); if (loadMoreFooter != null) { loadMoreFooter.onScroll(lastVisiblePosition, count); } } @Override public void setOnItemClickListener(OnItemClickListener listener) { onItemClickListener = listener; } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { /* * 在加載更多成功的那一刻點擊item,可能會報空指針 * 因為此時loadMoreFooter這個item已經從listView中暫時移除 * 所以在這裡做了parent判斷 */ int footerPosition = -1; ViewParent viewParent = loadMoreFooter.getParent(); if(viewParent != null) { footerPosition = getPositionForView(loadMoreFooter); if(position == footerPosition) { loadMoreFooter.onItemClick(); } } if(onItemClickListener != null && position != footerPosition) { onItemClickListener.onItemClick(parent, view, position, id); } } }
https://github.com/AItsuki/AListView
在本篇博客中,我們要實現在Android中“新建文件”和“讀取文件”: 目標界面: 在輸入文件名稱之後,輸入文件內容,點擊保存,可以保存成為一個文檔 He
360推出了旗下新品手機360 Q5和360 Q5plus,主打安全手機。那麼下面小編來針對Q5和Q5plus的參數對比,給大家一個購機參考。36
Service有什麼作用?許多人不明白service是用來干嘛的,其實Service作為Android四大組件之一,可以理解為一個運行在後台的Activity,它適用於處
+ code); if (code == 200) { InputStream is = conn.getInputStream();