Android自定義View-SlideListView
在 android 中 ListView 可以說是使用最多的控件之一,ListView 有很多的用法和處理事件,比如 item 的點擊和長按事件,在比較多的應用中,點擊就是跳轉,長按會彈出一些選擇菜單等。 這里我要介紹的是一個 ListView 側滑出菜單的自定義控件
效果圖如下:
正常狀態
側滑出菜單狀態
分析
主要用到了 Scroller 這個滑動類,剛開始攔截觸摸事件在 action ==MotionEvent.ACTION_DOWN的時候,根據出點獲取我們點擊的itemView 然后根據滑動模式(左滑動 or 右滑動)來自動獲取左側或者右側的寬度;
在 action == MotionEvent.ACTION_MOVE 中根據移動判斷是否可以側滑,以及側滑的方向,并使用 itemView.scrollTo(deltaX, 0); 來移動itemView ;
***在 ction == MotionEvent.ACTION_UP 中判斷模式和移動的距離完成側滑或者還原到初始狀態。
實現
***步 初始化Scroller
- scroller = new Scroller(context);
- mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
第二步 action ==MotionEvent.ACTION_DOWN
- case MotionEvent.ACTION_DOWN:
- 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;
第三步 action == MotionEvent.ACTION_MOVE
- case MotionEvent.ACTION_MOVE:
- if (!canMove
- && slidePosition != AdapterView.INVALID_POSITION
- && (Math.abs(ev.getX() - downX) > mTouchSlop && Math.abs(ev
- .getY() - downY) < mTouchSlop)) {
- if (mSwipeLayout != null)
- 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;
- }
- break;
第四步 action == MotionEvent.ACTION_UP
- case MotionEvent.ACTION_UP:
- if (mSwipeLayout != null)
- mSwipeLayout.setEnabled(true);
- //requestDisallowInterceptTouchEvent(false);
- if (canMove){
- canMove = false;
- scrollByDistanceX();
- }
- break;
完整代碼
以下是完整代碼
- package com.jwenfeng.fastdev.view;
- import android.content.Context;
- import android.support.v4.widget.SwipeRefreshLayout;
- import android.util.AttributeSet;
- 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 側滑出菜單
- * 項目名:fastdev
- * 包名:com.jwenfeng.fastdev.view
- * 作者:jinwenfeng on 16/4/11 10:55
- * 郵箱:823546371@qq.com
- * QQ: 823546371
- * 公司:南京穆尊信息科技有限公司
- * © 2016 jinwenfeng
- * ©版權所有,未經允許不得傳播
- */
- 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 defStyleAttr) {
- super(context, attrs, defStyleAttr);
- 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:
- 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)) {
- if (mSwipeLayout != null)
- 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;
- }
- break;
- case MotionEvent.ACTION_UP:
- if (mSwipeLayout != null)
- mSwipeLayout.setEnabled(true);
- //requestDisallowInterceptTouchEvent(false);
- if (canMove){
- canMove = false;
- scrollByDistanceX();
- }
- break;
- }
- return super.onTouchEvent(ev);
- }
- 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();
- }
- public void setSwipeLayout(SwipeRefreshLayout mSwipeLayout) {
- this.mSwipeLayout = mSwipeLayout;
- }
- }