Android自定義View-SlideListView
在 android 中 ListView 可以說是使用最多的控件之一,ListView 有很多的用法和處理事件,比如 item 的點(diǎn)擊和長(zhǎng)按事件,在比較多的應(yīng)用中,點(diǎn)擊就是跳轉(zhuǎn),長(zhǎng)按會(huì)彈出一些選擇菜單等。 這里我要介紹的是一個(gè) ListView 側(cè)滑出菜單的自定義控件
效果圖如下:
正常狀態(tài)
側(cè)滑出菜單狀態(tài)
分析
主要用到了 Scroller 這個(gè)滑動(dòng)類,剛開始攔截觸摸事件在 action ==MotionEvent.ACTION_DOWN的時(shí)候,根據(jù)出點(diǎn)獲取我們點(diǎn)擊的itemView 然后根據(jù)滑動(dòng)模式(左滑動(dòng) or 右滑動(dòng))來自動(dòng)獲取左側(cè)或者右側(cè)的寬度;
在 action == MotionEvent.ACTION_MOVE 中根據(jù)移動(dòng)判斷是否可以側(cè)滑,以及側(cè)滑的方向,并使用 itemView.scrollTo(deltaX, 0); 來移動(dòng)itemView ;
***在 ction == MotionEvent.ACTION_UP 中判斷模式和移動(dòng)的距離完成側(cè)滑或者還原到初始狀態(tài)。
實(shí)現(xiàn)
***步 初始化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);
 - }
 - // 如果處于側(cè)滑完成狀態(tài),側(cè)滑回去,并直接返回
 - if (isSlided) {
 - scrollBack();
 - return false;
 - }
 - // 假如scroller滾動(dòng)還沒有結(jié)束,我們直接返回
 - 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);
 - }
 - // 獲取我們點(diǎn)擊的item view
 - itemView = getChildAt(slidePosition - getFirstVisiblePosition());
 - /*此處根據(jù)設(shè)置的滑動(dòng)模式,自動(dòng)獲取左側(cè)或右側(cè)菜單的長(zhǎng)度*/
 - 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;
 - }
 - /*此段代碼是為了避免我們?cè)趥?cè)向滑動(dòng)時(shí)同時(shí)觸發(fā)ListView的OnItemClickListener時(shí)間*/
 - MotionEvent cancelEvent = MotionEvent.obtain(ev);
 - cancelEvent
 - .setAction(MotionEvent.ACTION_CANCEL
 - | (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
 - onTouchEvent(cancelEvent);
 - }
 - if (canMove) {
 - /*設(shè)置此屬性,可以在側(cè)向滑動(dòng)時(shí),保持ListView不會(huì)上下滾動(dòng)*/
 - requestDisallowInterceptTouchEvent(true);
 - // 手指拖動(dòng)itemView滾動(dòng), deltaX大于0向左滾動(dòng),小于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;
 - /**
 - * 當(dāng)前類注釋: ListView 側(cè)滑出菜單
 - * 項(xiàng)目名:fastdev
 - * 包名:com.jwenfeng.fastdev.view
 - * 作者:jinwenfeng on 16/4/11 10:55
 - * 郵箱:823546371@qq.com
 - * QQ: 823546371
 - * 公司:南京穆尊信息科技有限公司
 - * © 2016 jinwenfeng
 - * ©版權(quán)所有,未經(jīng)允許不得傳播
 - */
 - public class SlideListView extends ListView {
 - /**下拉刷新view*/
 - private SwipeRefreshLayout mSwipeLayout;
 - /**
 - * 禁止側(cè)滑模式
 - */
 - public static int MOD_FORBID = 0;
 - /**
 - * 從左向右滑出菜單模式
 - */
 - public static int MOD_LEFT = 1;
 - /**
 - * 從右向左滑出菜單模式
 - */
 - public static int MOD_RIGHT = 2;
 - /**
 - * 左右均可以滑出菜單模式
 - */
 - public static int MOD_BOTH = 3;
 - /**
 - * 當(dāng)前的模式
 - */
 - private int mode = MOD_FORBID;
 - /**
 - * 左側(cè)菜單的長(zhǎng)度
 - */
 - private int leftLength = 0;
 - /**
 - * 右側(cè)菜單的長(zhǎng)度
 - */
 - private int rightLength = 0;
 - /**
 - * 當(dāng)前滑動(dòng)的ListView position
 - */
 - private int slidePosition;
 - /**
 - * 手指按下X的坐標(biāo)
 - */
 - private int downY;
 - /**
 - * 手指按下Y的坐標(biāo)
 - */
 - private int downX;
 - /**
 - * ListView的item
 - */
 - private View itemView;
 - /**
 - * 滑動(dòng)類
 - */
 - private Scroller scroller;
 - /**
 - * 認(rèn)為是用戶滑動(dòng)的最小距離
 - */
 - private int mTouchSlop;
 - /**
 - * 判斷是否可以側(cè)向滑動(dòng)
 - */
 - private boolean canMove = false;
 - /**
 - * 標(biāo)示是否完成側(cè)滑
 - */
 - 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;
 - }
 - /**
 - * 處理我們拖動(dòng)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);
 - }
 - // 如果處于側(cè)滑完成狀態(tài),側(cè)滑回去,并直接返回
 - if (isSlided) {
 - scrollBack();
 - return false;
 - }
 - // 假如scroller滾動(dòng)還沒有結(jié)束,我們直接返回
 - 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);
 - }
 - // 獲取我們點(diǎn)擊的item view
 - itemView = getChildAt(slidePosition - getFirstVisiblePosition());
 - /*此處根據(jù)設(shè)置的滑動(dòng)模式,自動(dòng)獲取左側(cè)或右側(cè)菜單的長(zhǎng)度*/
 - 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;
 - }
 - /*此段代碼是為了避免我們?cè)趥?cè)向滑動(dòng)時(shí)同時(shí)觸發(fā)ListView的OnItemClickListener時(shí)間*/
 - MotionEvent cancelEvent = MotionEvent.obtain(ev);
 - cancelEvent
 - .setAction(MotionEvent.ACTION_CANCEL
 - | (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
 - onTouchEvent(cancelEvent);
 - }
 - if (canMove) {
 - /*設(shè)置此屬性,可以在側(cè)向滑動(dòng)時(shí),保持ListView不會(huì)上下滾動(dòng)*/
 - requestDisallowInterceptTouchEvent(true);
 - // 手指拖動(dòng)itemView滾動(dòng), deltaX大于0向左滾動(dòng),小于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();
 - }
 - }
 - /**
 - * 往右滑動(dòng),getScrollX()返回的是左邊緣的距離,就是以View左邊緣為原點(diǎn)到開始滑動(dòng)的距離,所以向右邊滑動(dòng)為負(fù)值
 - */
 - private void scrollRight() {
 - isSlided = true;
 - final int delta = (leftLength + itemView.getScrollX());
 - // 調(diào)用startScroll方法來設(shè)置一些滾動(dòng)的參數(shù),我們?cè)赾omputeScroll()方法中調(diào)用scrollTo來滾動(dòng)item
 - scroller.startScroll(itemView.getScrollX(), 0, -delta, 0,
 - Math.abs(delta));
 - postInvalidate(); // 刷新itemView
 - }
 - /**
 - * 向左滑動(dòng),根據(jù)上面我們知道向左滑動(dòng)為正值
 - */
 - private void scrollLeft() {
 - isSlided = true;
 - final int delta = (rightLength - itemView.getScrollX());
 - // 調(diào)用startScroll方法來設(shè)置一些滾動(dòng)的參數(shù),我們?cè)赾omputeScroll()方法中調(diào)用scrollTo來滾動(dòng)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() {
 - // 調(diào)用startScroll的時(shí)候scroller.computeScrollOffset()返回true,
 - if (scroller.computeScrollOffset()) {
 - // 讓ListView item根據(jù)當(dāng)前的滾動(dòng)偏移量進(jìn)行滾動(dòng)
 - itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY());
 - postInvalidate();
 - }
 - }
 - /**
 - * 提供給外部調(diào)用,用以將側(cè)滑出來的滑回去
 - */
 - public void slideBack() {
 - this.scrollBack();
 - }
 - public void setSwipeLayout(SwipeRefreshLayout mSwipeLayout) {
 - this.mSwipeLayout = mSwipeLayout;
 - }
 - }
 

















 
 
 




 
 
 
 