編輯:關於Android編程
下拉刷新和上拉加載時一個很常用的功能,剛好今天學了,好好的總結一把!
由於兩個功能是寫在一個類裡面的,這裡就不需要再創建一個類了。
第一步:寫一個尾部,添加到listview中,先將其隱藏 第二步:設置監聽滾動監聽事件,判斷是否滑動到底部 第三部:加載數據代碼裡面思路也很清楚,而且注釋很多,相信很容易看明白。
自定義控件類:
public class PullToRefresh extends ListView {
private int downY;
private View header;
private int headerHeight;
private ImageView ivArrow;
private TextView tvTitle;
private TextView tvTime;
private RotateAnimation downAnin;
private RotateAnimation upAnin;
private boolean isLoading = false;
private OnPullToRefreshListener onPullToRefreshListener;
private static final int PULL_TO_REFRESH = 1; // 下拉刷新狀態
private static final int RELEASE_TO_REFRESH = 2; // 釋放刷新狀態
private static final int REFRESHING = 3; // 正在刷新狀態
public PullToRefresh(Context context, AttributeSet attrs) {
super(context, attrs);
addHeader();
initComponent();
addFooter();
// 設置滑動監聽事件
setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
int count = view.getAdapter().getCount(); // 獲取條目數
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_FLING:
break;
case OnScrollListener.SCROLL_STATE_IDLE:
// 在閒置狀態時判斷是否到達了底部
if (getLastVisiblePosition() == count - 1 && !isLoading) {
footer.setPadding(0, 0, 0, 0);
setSelection(count - 1);
isLoading = true;
if (onPullToRefreshListener != null) {
onPullToRefreshListener.loadMore();
}
}
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
});
}
/**
* 為控件添加一個尾部
* */
private void addFooter() {
footer = View.inflate(getContext(), R.layout.footer, null);
footer.measure(0, 0); // 手動測量尾部的高度,否則在這裡無法獲取到尾部的高度
footerrHeight = header.getMeasuredHeight();
footer.setPadding(0, -footerrHeight, 0, 0); // 通過設置尾部的paddingTop來將尾部先隱藏
addFooterView(footer);
}
/**
* 設置接口的引用
* */
public void setOnPullToRefreshListener(
OnPullToRefreshListener onPullToRefreshListener) {
this.onPullToRefreshListener = onPullToRefreshListener;
}
/**
* 初始化組件找到它們
* */
private void initComponent() {
ivArrow = (ImageView) findViewById(R.id.iv_arrow);
ivLeft = (ImageView) findViewById(R.id.iv_left);
tvTitle = (TextView) findViewById(R.id.tv_title);
tvTime = (TextView) findViewById(R.id.tv_time);
upAnin = new RotateAnimation(0, 180, RotateAnimation.RELATIVE_TO_SELF,
0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f);
upAnin.setDuration(200);
upAnin.setFillAfter(true);// 動畫完成時停留在那裡
downAnin = new RotateAnimation(180, 0,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
downAnin.setDuration(200);
downAnin.setFillAfter(true);// 動畫完成時停留在那裡
loadingAnim = new RotateAnimation(0, 360,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
loadingAnim.setRepeatCount(RotateAnimation.INFINITE);
loadingAnim.setRepeatMode(RotateAnimation.RESTART);
loadingAnim.setInterpolator(new LinearInterpolator());
loadingAnim.setDuration(1000);
}
/**
* 第一步:為listview添加一個頭部
* */
private void addHeader() {
header = View.inflate(getContext(), R.layout.header, null);
header.measure(0, 0); // 手動測量頭部的高度,否則在這裡無法獲取到頭部的高度
headerHeight = header.getMeasuredHeight();
header.setPadding(0, -headerHeight, 0, 0); // 通過設置頭部的paddingTop來將頭部先隱藏
addHeaderView(header);
}
// 第二步:監聽觸屏事件,判斷是否滑動到頂部並進行相應的操作
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
// 記錄下當前滑動的垂直坐標
int moveY = (int) ev.getY();
int dy = moveY - downY; // 垂直位移
downY = moveY;
int paddingTop = header.getPaddingTop();
// 不是正在刷新狀態、到達頭部並且頭部在顯示的時候
if (state != REFRESHING // 不是正在刷新狀態
&& getFirstVisiblePosition() == 0 // 到達頭部
&& (dy > 0 // 下滑
|| paddingTop > -headerHeight)) { // 上滑,頭部在顯示
header.setPadding(0, paddingTop + dy, 0, 0); // 設置頭部顯示狀態
/**
* 第三步:通過下拉的高度來判斷進行何種刷新動作 1.頭部沒有完全露出來,進行下拉刷新 2.頭部完全露出來,則進行釋放刷新
* */
if (paddingTop >= 0) {
// 進入釋放刷新狀態
setState(RELEASE_TO_REFRESH);
} else {
// 進入下拉刷新狀態
setState(PULL_TO_REFRESH);
}
return true;
}
break;
case MotionEvent.ACTION_UP:
/**
* 第四步:當松開手之後,判斷需要進行哪種刷新 這裡我們在resetHeader()方法裡面進行判斷
*
* */
resetHeader();
break;
default:
break;
}
return super.onTouchEvent(ev);
}
/**
* 設置刷新狀態
* */
private int state = PULL_TO_REFRESH; // 記錄當前狀態
private ImageView ivLeft;
private RotateAnimation loadingAnim;
private View footer;
private int footerrHeight;
private void setState(int state) {
if (this.state != state) {
if (state == PULL_TO_REFRESH) {
tvTitle.setText("下拉刷新");
ivArrow.startAnimation(downAnin);
} else if (state == RELEASE_TO_REFRESH) {
tvTitle.setText("釋放刷新");
ivArrow.startAnimation(upAnin);
} else if (state == REFRESHING) {
tvTitle.setText("正在刷新");
tvTime.setText("最後刷新時間:"
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
ivArrow.clearAnimation();// 清除箭頭動畫
ivArrow.setVisibility(View.GONE);// 將箭頭隱藏
ivLeft.setVisibility(View.VISIBLE); // 將正在刷新圖標顯示出來
ivLeft.startAnimation(loadingAnim);// 開始刷新動畫
}
this.state = state;
}
}
/**
* 下拉刷新操作完成
* */
public void refreshFinish() {
ivLeft.clearAnimation();
ivLeft.setVisibility(View.GONE);
ivArrow.setVisibility(View.VISIBLE);
setState(PULL_TO_REFRESH);
header.setPadding(0, -headerHeight, 0, 0);// 重新隱藏頭
}
/**
* 上拉加載操作完成
* */
public void loadMoreFinish() {
isLoading = false;
footer.setPadding(0, -footerrHeight, 0, 0);
}
/**
* 重新設置頭部
*
* */
private void resetHeader() {
if (state == PULL_TO_REFRESH) {
header.setPadding(0, -headerHeight, 0, 0); // 重新將頭部隱藏
} else if (state == RELEASE_TO_REFRESH) {
header.setPadding(0, 0, 0, 0); // 剛好將頭部完全顯示出來
setState(REFRESHING); // 設置為正在刷新狀態
// 開始加載數據
if (onPullToRefreshListener != null) {
onPullToRefreshListener.refresh();
}
}
}
public interface OnPullToRefreshListener {
public void refresh(); // 刷新
public void loadMore(); // 加載更多
}
}
總結一下寫的過程中碰到的坑:
由於我們添加頭部和尾部的操作是在構造方法裡面進行的,系統還沒來得及為我們測量控件的高寬,因此我們在獲取控件高寬的時候必須自己調用measure()方法來測量,否則獲取到的控件高寬始終為0; 在監聽觸屏事件中,我們做了一個操作:downY = moveY;這樣我們所獲取到的垂直位移才是實時移動的位移。 在if (state != REFRESHING && getFirstVisiblePosition() == 0 && (dy > 0 || paddingTop > -headerHeight))這語句中,paddingTop > -headerHeight這句是用來判斷向上滑動並且頭部處於顯示狀態。如果沒有這個判斷,我們高頻率的小幅度滑動屏幕時,會發現頭部上面的空白區域會不斷的增大。 在setState(int state)方法處,我們需要設置一個變量來記錄當前控件所處的狀態,並通過這個變量狀態來決定是否執行方法裡面的代碼,否則你會發現,當你下拉控件的時候,那個指示圖標會不停的上下轉動,那是它在不斷的反復執行動畫的效果。 加載數據屬於耗時操作,必須在子線程中進行,否則會出現“卡卡”的現象,反正我由於忘記了這個東東找了好長時間。 在刷新完成之後,即在refreshFinish()方法裡面,我們需要進行動畫清除,不然再次下拉的時候會出現圖標“殘影”,還要將控件狀態還原到下拉刷新狀態setState(PULL_TO_REFRESH),不然保證你拉不了第二次,就這麼給力。 最後一點,為了降低代碼之間的耦合性,用到了接口回調,那麼,我們在需要加載數據的時候(控件類代碼中)必須先進行非空判斷,不然空指針異常等著你。
header.xml
footer.xml
在其他項目裡面測試的代碼:
布局文件activity_main.xml
MainActivity
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final PullToRefresh ptr = (PullToRefresh) findViewById(R.id.ptrlist);
final List data = getData();
final ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, android.R.id.text1,data);
ptr.setAdapter(adapter);
/**
* 刷新加載數據,接口回調,大大降低了代碼間的耦合性
* */
ptr.setOnPullToRefreshListener(new OnPullToRefreshListener() {
@Override
public void refresh() {
//加載數據屬於耗時操作,需要開啟子線程
new Thread(new Runnable() {
public void run() {
SystemClock.sleep(1000); //模擬數據
String newData = "下拉刷新更多的數據";
data.add(0,newData);
//更新UI的動作只能在主線程中完成
runOnUiThread(new Runnable() {
public void run() {
adapter.notifyDataSetChanged();
ptr.refreshFinish();
}
});
}
}).start();
}
@Override
public void loadMore() {
// TODO Auto-generated method stub
new Thread(new Runnable() {
public void run() {
SystemClock.sleep(1000);
String newData = "上拉加載更多的數據";
data.add(newData);
//更新UI的動作只能在主線程中完成
runOnUiThread(new Runnable() {
public void run() {
adapter.notifyDataSetChanged();
ptr.loadMoreFinish();
}
});
}
}).start();
}
});
}
/**
* 初始化數據
* */
private List getData() {
List list = new ArrayList();
for (int i = 0; i < 15; i++) {
list.add("原始數據"+i);
}
return list;
}
}
前言 Android中支持許多資源,包括圖片(Bitmap),對應於bitmap的文件夾是drawable,除了drawable,還有drawable-ld
本文實例講述了Android編程使用android-support-design實現MD風格對話框功能。分享給大家供大家參考,具體如下:首先上效果圖: 測試設備
我們在開發安卓App時難免要與服務器打交道,尤其是對於用戶賬號信息的注冊與登錄更是每個Android開發人員必須掌握的技能,本文將對客戶端的注冊/登錄功能的實現進行分析,
看了這篇文章Android Studio如何查看資源或者函數在哪些類中被引用,知道了快捷鍵失效的原因,其中有一個原因就是快捷鍵沖突,那如何查看快捷鍵哪些項沖突了呢? A