下拉重新整理控制元件包裹下的ListView側滑出選單的實現
阿新 • • 發佈:2019-01-27
package com.moopoo.widget; import android.content.Context; import android.support.v4.widget.SwipeRefreshLayout; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.widget.AdapterView; import android.widget.ListView; import android.widget.Scroller; /** * 側向滑出選單的ListView * 使用請注意與ListView的Item的佈局配合, * 該效果的實現是基於在Item的佈局中通過設定PaddingLeft和PaddingRight來隱藏左右選單的, * 所以使用此ListView時,請務必在佈局Item時使用PaddingLeft和PaddingRight; * 或者自己改寫此ListView,已達到想要的實現方式 */ public class SlideListView extends ListView { /**下拉重新整理view*/ private SwipeRefreshLayout mSwipeLayout; /**禁止側滑模式*/ public static int MOD_FORBID = 0; /**從左向右滑出選單模式*/ public static int MOD_LEFT = 1; /**從右向左滑出選單模式*/ public static int MOD_RIGHT = 2; /**左右均可以滑出選單模式*/ public static int MOD_BOTH = 3; /**當前的模式*/ private int mode = MOD_FORBID; /**左側選單的長度*/ private int leftLength = 0; /**右側選單的長度*/ private int rightLength = 0; /** * 當前滑動的ListView position */ private int slidePosition; /** * 手指按下X的座標 */ private int downY; /** * 手指按下Y的座標 */ private int downX; /** * ListView的item */ private View itemView; /** * 滑動類 */ private Scroller scroller; /** * 認為是使用者滑動的最小距離 */ private int mTouchSlop; /** * 判斷是否可以側向滑動 */ private boolean canMove = false; /** * 標示是否完成側滑 */ private boolean isSlided = false; public SlideListView(Context context) { this(context, null); } public SlideListView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SlideListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); scroller = new Scroller(context); mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } /** * 初始化選單的滑出模式 * @param mode */ public void initSlideMode(int mode){ this.mode = mode; } /** * 處理我們拖動ListView item的邏輯 */ @Override public boolean onTouchEvent(MotionEvent ev) { final int action = ev.getAction(); int lastX = (int) ev.getX(); switch (action) { case MotionEvent.ACTION_DOWN: /*當前模式不允許滑動,則直接返回,交給ListView自身去處理*/ if(this.mode == MOD_FORBID){ return super.onTouchEvent(ev); } // 如果處於側滑完成狀態,側滑回去,並直接返回 if (isSlided) { scrollBack(); return false; } // 假如scroller滾動還沒有結束,我們直接返回 if (!scroller.isFinished()) { return false; } downX = (int) ev.getX(); downY = (int) ev.getY(); slidePosition = pointToPosition(downX, downY); // 無效的position, 不做任何處理 if (slidePosition == AdapterView.INVALID_POSITION) { return super.onTouchEvent(ev); } // 獲取我們點選的item view itemView = getChildAt(slidePosition - getFirstVisiblePosition()); /*此處根據設定的滑動模式,自動獲取左側或右側選單的長度*/ if(this.mode == MOD_BOTH){ this.leftLength = -itemView.getPaddingLeft(); this.rightLength = -itemView.getPaddingRight(); }else if(this.mode == MOD_LEFT){ this.leftLength = -itemView.getPaddingLeft(); }else if(this.mode == MOD_RIGHT){ this.rightLength = -itemView.getPaddingRight(); } break; case MotionEvent.ACTION_MOVE: if (!canMove && slidePosition != AdapterView.INVALID_POSITION && (Math.abs(ev.getX() - downX) > mTouchSlop && Math.abs(ev .getY() - downY) < mTouchSlop)) { // 禁用下拉重新整理控制元件,避免其攔截onTouch事件 mSwipeLayout.setEnabled(false); int offsetX = downX - lastX; if(offsetX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){ /*從右向左滑*/ canMove = true; }else if(offsetX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){ /*從左向右滑*/ canMove = true; }else{ canMove = false; } /*此段程式碼是為了避免我們在側向滑動時同時觸發ListView的OnItemClickListener時間*/ MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent .setAction(MotionEvent.ACTION_CANCEL | (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT)); onTouchEvent(cancelEvent); } if (canMove) { /*設定此屬性,可以在側向滑動時,保持ListView不會上下滾動*/ requestDisallowInterceptTouchEvent(true); // 手指拖動itemView滾動, deltaX大於0向左滾動,小於0向右滾 int deltaX = downX - lastX; if(deltaX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){ /*向左滑*/ itemView.scrollTo(deltaX, 0); }else if(deltaX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){ /*向右滑*/ itemView.scrollTo(deltaX, 0); }else{ itemView.scrollTo(0, 0); } return true; // 拖動的時候ListView不滾動 } case MotionEvent.ACTION_UP: mSwipeLayout.setEnabled(true); //requestDisallowInterceptTouchEvent(false); if (canMove){ canMove = false; scrollByDistanceX(); } break; } // 否則直接交給ListView來處理onTouchEvent事件 return super.onTouchEvent(ev); } /** * 根據手指滾動itemView的距離來判斷是滾動到開始位置還是向左或者向右滾動 */ private void scrollByDistanceX() { /*當前模式不允許滑動,則直接返回*/ if(this.mode == MOD_FORBID){ return; } if(itemView.getScrollX() > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){ /*從右向左滑*/ if (itemView.getScrollX() >= rightLength / 2) { scrollLeft(); } else { // 滾回到原始位置 scrollBack(); } }else if(itemView.getScrollX() < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){ /*從左向右滑*/ if (itemView.getScrollX() <= -leftLength / 2) { scrollRight(); } else { // 滾回到原始位置 scrollBack(); } }else{ // 滾回到原始位置 scrollBack(); } } /** * 往右滑動,getScrollX()返回的是左邊緣的距離,就是以View左邊緣為原點到開始滑動的距離,所以向右邊滑動為負值 */ private void scrollRight() { isSlided = true; final int delta = (leftLength + itemView.getScrollX()); // 呼叫startScroll方法來設定一些滾動的引數,我們在computeScroll()方法中呼叫scrollTo來滾動item scroller.startScroll(itemView.getScrollX(), 0, -delta, 0, Math.abs(delta)); postInvalidate(); // 重新整理itemView } /** * 向左滑動,根據上面我們知道向左滑動為正值 */ private void scrollLeft() { isSlided = true; final int delta = (rightLength - itemView.getScrollX()); // 呼叫startScroll方法來設定一些滾動的引數,我們在computeScroll()方法中呼叫scrollTo來滾動item scroller.startScroll(itemView.getScrollX(), 0, delta, 0, Math.abs(delta)); postInvalidate(); // 重新整理itemView } /** * 滑動會原來的位置 */ private void scrollBack() { isSlided = false; scroller.startScroll(itemView.getScrollX(), 0, -itemView.getScrollX(), 0, Math.abs(itemView.getScrollX())); postInvalidate(); // 重新整理itemView } @Override public void computeScroll() { // 呼叫startScroll的時候scroller.computeScrollOffset()返回true, if (scroller.computeScrollOffset()) { // 讓ListView item根據當前的滾動偏移量進行滾動 itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY()); postInvalidate(); } } /** * 提供給外部呼叫,用以將側滑出來的滑回去 */ public void slideBack() { this.scrollBack(); } /** * 外部傳進下拉重新整理view * by skypupil */ public void setParentView(SwipeRefreshLayout swipeLayout){ mSwipeLayout = swipeLayout; } }