Android自訂ListView實現下拉重新整理_Android

來源:互聯網
上載者:User

首先呈上效果圖


當今APP,哪個沒有點滑動重新整理功能,簡直就太落伍了。正因為需求多,因此自然而然開源的也就多。但是若想引用開源庫,則很麻煩,比如PullToRefreshView這個庫,如果把開原始碼都移植到項目中,這是件很繁瑣的事,如果用依賴功能的話,對於強迫症的我,又很不爽。現在也有各種自訂ListView實現PullToRefreshListView的控制項,無非就是在header加入一個控制項,通過setPadding的方式來改變顯示效果。效果已經太out了,如意中發現google內建的swiperefreshlayout實現的效果挺不錯,但是我發現這個控制項在部分手機上的效果不一樣,估計和v7包相關。因此就有了這篇文章自訂這個喜歡的效果。
 首先大概描述一下實現原理: 
1、重寫ListView的onTouchEvent,在方法中根據手指滑動的距離與臨界值判斷,決定當前的狀態,分為四個狀態:RELEASE_TO_REFRESH、PULL_TO_REFRESH、REFRESHING、DONE四個狀態,分別代表釋放重新整理、拉動重新整理、正在重新整理、預設狀態。 
2、重寫ListView的onDraw方法,根據不同的狀態值,顯示不同的圖形表示。 
3、根據滑動距離不同,顯示不同的透明度、圓弧角度值、整體圖形的座標等等。 
4、圖形的變化分為兩種:1、手動觸發,滑動一點距離就更新一點座標。比如PULL_TO_REFRESH狀態,適合在onTouchEvent中的ACTION_MOVE中觸發。2、動畫自動觸發,比如REFRESHING狀態和DONE狀態,適合在onTouchEvent中的ACTION_UP方法中觸發,手指一鬆開就自動觸發動畫效果。 
5、必須在設定了重新整理監聽器才可以滑動,否則就是一個普通的LIstView。

代碼很簡單,只有兩個檔案,並且有很詳細的注釋:
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;   // 達到重新整理條件的滑動距離  public final static int TOUCH_SLOP = 160;  // 判斷是否記錄了最開始按下時的Y座標  private boolean isRecored;  // 記錄最開始按下時的Y座標  private int startY;  // ListView第一個Item  private int firstItemIndex;  // 目前狀態  private int state;  // 是否可重新整理,只有設定了監聽器才能重新整理  private boolean isRefreshable;  // 重新整理標記  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) {    //關閉硬體加速,否則PullMark的陰影不會出現    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;  }   /**   * 重新整理監聽器   */  public interface OnRefreshListener {    public void onRefresh();  }   public void setOnScrollButtomListener(OnScrollButtomListener scrollButtomListener) {    this.scrollButtomListener = scrollButtomListener;  }   /**   * 滑動到最低端觸發監聽器   */  public interface OnScrollButtomListener {    public void onScrollToButtom();  } }

重新整理標誌類:

 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;  // 動畫的高度漸層比率、時間重新整理間隔  private static final float RATIO_TOUCH_SLOP = 7f;  private static final long RATIO_ANIMATION_DURATION = 10;   private PullToRefreshListView listView;  // 中點的X、Y座標、初始隱藏時的Y座標  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;  // 弧度變化比率,根據總體高度與總體弧度角度的比例決定  private float radioAngle = ALL_ANGLE * 1.0f / PullToRefreshListView.TOUCH_SLOP;  // 透明度變化比率,根據總體高度與總體透明度的比例決定  private float radioAlpha = ALL_ALPHA * 1.0f / PullToRefreshListView.TOUCH_SLOP;  // PullToRefreshListView的狀態  private int state;  // 當前手指滑動的距離  private float mTouchLength;  // 是否啟動旋轉動畫  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;  }   /**   * 設定繪製的中點X座標,在PullToRefreshListView的onMeasure中實現   * @param centerX   */  public void setCenterX(int centerX){    this.centerX = centerX;  }   /**   * 表示一次普通的資料變化,在onTouchEvent中的ACTION_MOVE中觸發   * @param state   * @param mTouchLength   */  public void change(int state, float mTouchLength){    this.state = state;    this.mTouchLength = mTouchLength;    // 改變繪製的Y座標    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();  }   /**   * 表示一次動畫的變化,在onTouchEvent的ACTION_UP中或者手動startRefresh以及手動stopRefresh中觸發   * @param state   */  public void changeByAnimation(final int state){    this.state = state;    if(state == PullToRefreshListView.DONE){      //結束旋轉動畫(關閉正在重新整理的效果)      isRotateAnimation = false;    }    //慢慢變化到起始位置    handler.postDelayed(new RunnableMove(state), RATIO_ANIMATION_DURATION);  }   /**   * 啟動移動的處理   */  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){          // 直接將座標初始化,否則會有一點點誤差          centerY = doneCenterY;          listView.invalidate();        }else if(state == PullToRefreshListView.REFRESHING){          //啟動旋轉的動畫效果          isRotateAnimation = true;          handler.postDelayed(new RunnableRotate(), RATIO_ANIMATION_DURATION);        }      }    }  }   /**   * 旋轉動畫的處理   */  public class RunnableRotate implements Runnable{     @Override    public void run() {      if(isRotateAnimation){        //啟動動畫旋轉效果        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);      }    }  }   /**   * 繪製重新整理表徵圖的標誌   * @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);  }}

使用的時候,必須要設定了監聽器才能有效滑動: 

final PullToRefreshListView listView = (PullToRefreshListView) findViewById(R.id.listView);    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new String[]{      "測試1","測試2","測試3","測試4","測試5","測試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);      }    });

 兩個原始碼檔案就搞定了,demo工程就不提供了,很簡單的。

以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援雲棲社區。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.