Android自定義ListView實(shí)現(xiàn)下拉刷新
首先呈上效果圖
當(dāng)今APP,哪個(gè)沒有點(diǎn)滑動(dòng)刷新功能,簡(jiǎn)直就太落伍了。正因?yàn)樾枨蠖?,因此自然而然開源的也就多。但是若想引用開源庫,則很麻煩,比如PullToRefreshView這個(gè)庫,如果把開源代碼都移植到項(xiàng)目中,這是件很繁瑣的事,如果用依賴功能的話,對(duì)于強(qiáng)迫癥的我,又很不爽。現(xiàn)在也有各種自定義ListView實(shí)現(xiàn)PullToRefreshListView的控件,無非就是在header加入一個(gè)控件,通過setPadding的方式來改變顯示效果。效果已經(jīng)太out了,如意中發(fā)現(xiàn)google自帶的swiperefreshlayout實(shí)現(xiàn)的效果挺不錯(cuò),但是我發(fā)現(xiàn)這個(gè)控件在部分手機(jī)上的效果不一樣,估計(jì)和v7包相關(guān)。因此就有了這篇文章自定義這個(gè)喜歡的效果。
首先大概描述一下實(shí)現(xiàn)原理:
1、重寫ListView的onTouchEvent,在方法中根據(jù)手指滑動(dòng)的距離與臨界值判斷,決定當(dāng)前的狀態(tài),分為四個(gè)狀態(tài):RELEASE_TO_REFRESH、PULL_TO_REFRESH、REFRESHING、DONE四個(gè)狀態(tài),分別代表釋放刷新、拉動(dòng)刷新、正在刷新、默認(rèn)狀態(tài)。
2、重寫ListView的onDraw方法,根據(jù)不同的狀態(tài)值,顯示不同的圖形表示。
3、根據(jù)滑動(dòng)距離不同,顯示不同的透明度、圓弧角度值、整體圖形的坐標(biāo)等等。
4、圖形的變化分為兩種:1、手動(dòng)觸發(fā),滑動(dòng)一點(diǎn)距離就更新一點(diǎn)坐標(biāo)。比如PULL_TO_REFRESH狀態(tài),適合在onTouchEvent中的ACTION_MOVE中觸發(fā)。2、動(dòng)畫自動(dòng)觸發(fā),比如REFRESHING狀態(tài)和DONE狀態(tài),適合在onTouchEvent中的ACTION_UP方法中觸發(fā),手指一松開就自動(dòng)觸發(fā)動(dòng)畫效果。
5、必須在設(shè)置了刷新監(jiān)聽器才可以滑動(dòng),否則就是一個(gè)普通的LIstView。
代碼很簡(jiǎn)單,只有兩個(gè)文件,并且有很詳細(xì)的注釋:
PullToRefreshListView類:
package cc.wxf.view.pull; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.AbsListView; import android.widget.ListView; /** * Created by ccwxf on 2016/3/30. */ public class PullToRefreshListView extends ListView implements AbsListView.OnScrollListener { public final static int RELEASE_TO_REFRESH = 0; public final static int PULL_TO_REFRESH = 1; public final static int REFRESHING = 2; public final static int DONE = 3; // 達(dá)到刷新條件的滑動(dòng)距離 public final static int TOUCH_SLOP = 160; // 判斷是否記錄了最開始按下時(shí)的Y坐標(biāo) private boolean isRecored; // 記錄最開始按下時(shí)的Y坐標(biāo) private int startY; // ListView第一個(gè)Item private int firstItemIndex; // 當(dāng)前狀態(tài) private int state; // 是否可刷新,只有設(shè)置了監(jiān)聽器才能刷新 private boolean isRefreshable; // 刷新標(biāo)記 private PullMark mark; private OnRefreshListener refreshListener; private OnScrollButtomListener scrollButtomListener; public PullToRefreshListView(Context context) { super(context); init(context); } public PullToRefreshListView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } private void init(Context context) { //關(guān)閉硬件加速,否則PullMark的陰影不會(huì)出現(xiàn) setLayerType(View.LAYER_TYPE_SOFTWARE, null); setOnScrollListener(this); mark = new PullMark(this); state = DONE; isRefreshable = false; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollButtomListener != null) { if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) { if (view.getLastVisiblePosition() == view.getAdapter().getCount() - 1) { scrollButtomListener.onScrollToButtom(); } } } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { firstItemIndex = firstVisibleItem; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mark.onDraw(canvas); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); mark.setCenterX(width / 2); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override public boolean onTouchEvent(MotionEvent event) { if (!isRefreshable) { return super.onTouchEvent(event); } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: handleActionDown(event); break; case MotionEvent.ACTION_UP: handleActionUp(); break; case MotionEvent.ACTION_MOVE: handleActionMove(event); break; default: break; } return super.onTouchEvent(event); } private void handleActionMove(MotionEvent event) { int tempY = (int) event.getY(); if (!isRecored && firstItemIndex == 0) { isRecored = true; startY = tempY; } if (state != REFRESHING && isRecored) { if (state == RELEASE_TO_REFRESH) { setSelection(0); if ((tempY - startY < TOUCH_SLOP) && (tempY - startY) > 0) { state = PULL_TO_REFRESH; } } if (state == PULL_TO_REFRESH) { setSelection(0); if (tempY - startY >= TOUCH_SLOP) { state = RELEASE_TO_REFRESH; } else if (tempY - startY <= 0) { state = DONE; } } if (state == DONE) { if (tempY - startY > 0) { state = PULL_TO_REFRESH; } } mark.change(state, tempY - startY); } } private void handleActionUp() { if (state == PULL_TO_REFRESH) { state = DONE; mark.changeByAnimation(state); } else if (state == RELEASE_TO_REFRESH) { state = REFRESHING; mark.changeByAnimation(state); onRefresh(); } isRecored = false; } private void handleActionDown(MotionEvent event) { if (firstItemIndex == 0 && !isRecored) { isRecored = true; startY = (int) event.getY(); } } private void onRefresh() { if (refreshListener != null) { refreshListener.onRefresh(); } } public void startRefresh() { state = REFRESHING; mark.changeByAnimation(state); onRefresh(); } public void stopRefresh() { state = DONE; mark.changeByAnimation(state); } public void setOnRefreshListener(OnRefreshListener refreshListener) { this.refreshListener = refreshListener; isRefreshable = true; } /** * 刷新監(jiān)聽器 */ public interface OnRefreshListener { public void onRefresh(); } public void setOnScrollButtomListener(OnScrollButtomListener scrollButtomListener) { this.scrollButtomListener = scrollButtomListener; } /** * 滑動(dòng)到最低端觸發(fā)監(jiān)聽器 */ public interface OnScrollButtomListener { public void onScrollToButtom(); } }
刷新標(biāo)志類:
package cc.wxf.view.pull; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.os.Handler; /** * Created by ccwxf on 2016/3/30. */ public class PullMark { //背景面板的半徑、顏色 private static final int RADIUS_PAN = 40; private static final int COLOR_PAN = Color.parseColor("#fafafa"); //面板陰影的半徑、顏色 private static final int RADIUS_SHADOW = 5; private static final int COLOR_SHADOW = Color.parseColor("#d9d9d9"); //面板中間的圓弧的半徑、顏色、粗度、開始繪制角度 private static final int RADIUS_ARROWS = 20; private static final int COLOR_ARROWS = Color.GREEN; private static final int BOUND_ARROWS = 6; private static final int START_ANGLE = 0; // 開始繪制角度的變化率、總體繪制角度、總體繪制透明度 private static final int RATIO_SATRT_ANGLE = 3; private static final int ALL_ANGLE = 270; private static final int ALL_ALPHA = 255; // 動(dòng)畫的高度漸變比率、時(shí)間刷新間隔 private static final float RATIO_TOUCH_SLOP = 7f; private static final long RATIO_ANIMATION_DURATION = 10; private PullToRefreshListView listView; // 中點(diǎn)的X、Y坐標(biāo)、初始隱藏時(shí)的Y坐標(biāo) private float doneCenterY = -(RADIUS_PAN + RADIUS_SHADOW) / 2; private float centerX; private float centerY = doneCenterY; // 開始繪制的角度、需要繪制的角度、透明度 private int startAngle = START_ANGLE; private int sweepAngle = startAngle; private int alpha; // 弧度變化比率,根據(jù)總體高度與總體弧度角度的比例決定 private float radioAngle = ALL_ANGLE * 1.0f / PullToRefreshListView.TOUCH_SLOP; // 透明度變化比率,根據(jù)總體高度與總體透明度的比例決定 private float radioAlpha = ALL_ALPHA * 1.0f / PullToRefreshListView.TOUCH_SLOP; // PullToRefreshListView的狀態(tài) private int state; // 當(dāng)前手指滑動(dòng)的距離 private float mTouchLength; // 是否啟動(dòng)旋轉(zhuǎn)動(dòng)畫 private boolean isRotateAnimation = false; // 畫筆 private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Handler handler = new Handler(); public PullMark(PullToRefreshListView listView) { this.listView = listView; } /** * 設(shè)置繪制的中點(diǎn)X坐標(biāo),在PullToRefreshListView的onMeasure中實(shí)現(xiàn) * @param centerX */ public void setCenterX(int centerX){ this.centerX = centerX; } /** * 表示一次普通的數(shù)據(jù)變化,在onTouchEvent中的ACTION_MOVE中觸發(fā) * @param state * @param mTouchLength */ public void change(int state, float mTouchLength){ this.state = state; this.mTouchLength = mTouchLength; // 改變繪制的Y坐標(biāo) centerY = doneCenterY + mTouchLength; // 改變繪制的透明度 alpha = (int) (mTouchLength * radioAlpha); if(alpha > ALL_ALPHA){ alpha = ALL_ALPHA; }else if(alpha < 0){ alpha = 0; } //改變繪制的起始角度 startAngle = startAngle + RATIO_SATRT_ANGLE; if(startAngle >= 360){ startAngle = 0; } //改變繪制的弧度角度 sweepAngle = (int) (mTouchLength * radioAngle); if(sweepAngle > ALL_ANGLE){ sweepAngle = ALL_ANGLE; }else if(sweepAngle < 0){ sweepAngle = 0; } listView.invalidate(); } /** * 表示一次動(dòng)畫的變化,在onTouchEvent的ACTION_UP中或者手動(dòng)startRefresh以及手動(dòng)stopRefresh中觸發(fā) * @param state */ public void changeByAnimation(final int state){ this.state = state; if(state == PullToRefreshListView.DONE){ //結(jié)束旋轉(zhuǎn)動(dòng)畫(關(guān)閉正在刷新的效果) isRotateAnimation = false; } //慢慢變化到起始位置 handler.postDelayed(new RunnableMove(state), RATIO_ANIMATION_DURATION); } /** * 啟動(dòng)移動(dòng)的處理 */ public class RunnableMove implements Runnable{ private int state; private int destination; private float slop; public RunnableMove(int state) { this.state = state; if(state == PullToRefreshListView.DONE){ destination = 0; slop = RATIO_TOUCH_SLOP; }else if(state == PullToRefreshListView.REFRESHING){ destination = PullToRefreshListView.TOUCH_SLOP; slop = RATIO_TOUCH_SLOP * 5; } } @Override public void run() { if(mTouchLength > destination){ mTouchLength -= slop; change(state, mTouchLength); handler.postDelayed(this, RATIO_ANIMATION_DURATION); }else{ if(state == PullToRefreshListView.DONE){ // 直接將坐標(biāo)初始化,否則會(huì)有一點(diǎn)點(diǎn)誤差 centerY = doneCenterY; listView.invalidate(); }else if(state == PullToRefreshListView.REFRESHING){ //啟動(dòng)旋轉(zhuǎn)的動(dòng)畫效果 isRotateAnimation = true; handler.postDelayed(new RunnableRotate(), RATIO_ANIMATION_DURATION); } } } } /** * 旋轉(zhuǎn)動(dòng)畫的處理 */ public class RunnableRotate implements Runnable{ @Override public void run() { if(isRotateAnimation){ //啟動(dòng)動(dòng)畫旋轉(zhuǎn)效果 startAngle = startAngle + RATIO_SATRT_ANGLE; if(startAngle >= 360){ startAngle = 0; } listView.invalidate(); handler.postDelayed(this, RATIO_ANIMATION_DURATION); }else{ //回到初始位置 handler.postDelayed(new RunnableMove(state), RATIO_ANIMATION_DURATION); } } } /** * 繪制刷新圖標(biāo)的標(biāo)志 * @param mCanvas */ public void onDraw(Canvas mCanvas){ //繪制背景圓盤和陰影 mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(COLOR_PAN); mPaint.setShadowLayer(RADIUS_SHADOW, 0, 0, COLOR_SHADOW); mCanvas.drawCircle(centerX, centerY, RADIUS_PAN, mPaint); //繪制圓弧 mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(COLOR_ARROWS); mPaint.setStrokeWidth(BOUND_ARROWS); mPaint.setAlpha(alpha); mCanvas.drawArc(new RectF(centerX - RADIUS_ARROWS, centerY - RADIUS_ARROWS, centerX + RADIUS_ARROWS, centerY + RADIUS_ARROWS), startAngle, sweepAngle, false, mPaint); } }
使用的時(shí)候,必須要設(shè)置了監(jiān)聽器才能有效的滑動(dòng):
final PullToRefreshListView listView = (PullToRefreshListView) findViewById(R.id.listView); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new String[]{ "測(cè)試1","測(cè)試2","測(cè)試3","測(cè)試4","測(cè)試5","測(cè)試6", }); listView.setAdapter(adapter); listView.setOnRefreshListener(new PullToRefreshListView.OnRefreshListener() { @Override public void onRefresh() { new Handler().postDelayed(new Runnable() { @Override public void run() { listView.stopRefresh(); } }, 2000); } });
兩個(gè)源代碼文件就搞定了,demo工程就不提供了,很簡(jiǎn)單的。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android-自定義控件之ListView下拉刷新的實(shí)現(xiàn)
- Android自定義漸變式炫酷ListView下拉刷新動(dòng)畫
- Android ListView實(shí)現(xiàn)上拉加載下拉刷新和滑動(dòng)刪除功能
- Android自定義listview布局實(shí)現(xiàn)上拉加載下拉刷新功能
- Android XListView下拉刷新和上拉加載更多
- Android ListView下拉刷新上拉自動(dòng)加載更多DEMO示例
- Android實(shí)現(xiàn)上拉加載更多以及下拉刷新功能(ListView)
- Android ListView實(shí)現(xiàn)上拉加載更多和下拉刷新功能
- Android開發(fā)之ListView列表刷新和加載更多實(shí)現(xiàn)方法
- Android仿XListView支持下拉刷新和上劃加載更多的自定義RecyclerView
- Android使用ListView實(shí)現(xiàn)下拉刷新及上拉顯示更多的方法
相關(guān)文章
Android獲取應(yīng)用程序名稱(ApplicationName)示例
本文以實(shí)例方式為大家介紹下獲取應(yīng)用程序名稱(ApplicationName)的具體實(shí)現(xiàn),感興趣的各位可以參考下哈2013-06-06Android基于騰訊云實(shí)時(shí)音視頻仿微信視頻通話最小化懸浮
這篇文章主要為大家詳細(xì)介紹了Android基于騰訊云實(shí)時(shí)音視頻實(shí)現(xiàn)類似微信視頻通話最小化懸浮,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11android將圖片轉(zhuǎn)換存到數(shù)據(jù)庫再從數(shù)據(jù)庫讀取轉(zhuǎn)換成圖片實(shí)現(xiàn)代碼
有時(shí)候我們想把圖片存入到數(shù)據(jù)庫中,盡管這不是一種明智的選擇,但有時(shí)候還是不得以會(huì)用到,下面說說將圖片轉(zhuǎn)換成byte[]數(shù)組存入到數(shù)據(jù)庫中去,并從數(shù)據(jù)庫中取出來轉(zhuǎn)換成圖像顯示出來2013-11-11Android編程實(shí)現(xiàn)大圖滾動(dòng)顯示的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)大圖滾動(dòng)顯示的方法,涉及Android使用imageView配合onTouch事件操作圖片顯示的相關(guān)技巧,需要的朋友可以參考下2016-10-10vscode通過wifi調(diào)試真機(jī)的Flutter應(yīng)用的教程
這篇文章主要介紹了vscode通過wifi調(diào)試真機(jī)的Flutter應(yīng)用的教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04Android如何用自定義View實(shí)現(xiàn)雪花效果
這篇文章主要介紹了Android如何用自定義View實(shí)現(xiàn)雪花效果,對(duì)特效感興趣的同學(xué)可以參考下2021-04-04Android中協(xié)調(diào)滾動(dòng)布局的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android中協(xié)調(diào)滾動(dòng)常用的布局實(shí)現(xiàn),類似這樣的協(xié)調(diào)滾動(dòng)布局,當(dāng)?shù)撞苛斜砘瑒?dòng)的時(shí)候,頂部的布局做響應(yīng)的動(dòng)作,我們都可以通過?AppBarLayout?和?MotionLayout?來實(shí)現(xiàn),本文通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友參考下吧2022-06-06