1.側拉刪除_實現滑動
1.初始化ViewDragHelper----*
- /**
- * 側拉刪除控件
- * @author poplar
- *
- */
- public class SwipeLayout extends FrameLayout {
- public SwipeLayout(Context context) {
- this(context, null);
- }
- public SwipeLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
- public SwipeLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- //a.初始化ViewDragHelper
- mDragHelper = ViewDragHelper.create(this, 1.0f, mCallback);
- }
- ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
- @Override
- public boolean tryCaptureView(View view, int id) {
- return false;
- }
- }
- }
2.傳遞觸摸事件---*
- // b. 傳遞觸摸事件
- @Override
- public boolean onInterceptTouchEvent(android.view.MotionEvent ev) {
- return mDragHelper.shouldInterceptTouchEvent(ev);
- };
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- try {
- mDragHelper.processTouchEvent(event);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return true;
- }
3.通過layout方法把佈局放在某個位置
(1)佈局
Button會攔截觸摸事件,所以使用TextView
- <com.itheima.swipelayout.ui.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/sl"
- android:layout_width="match_parent"
- android:layout_height="60dp"
- android:minHeight="60dp"
- android:background="#44000000" >
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:orientation="horizontal" >
- <TextView
- android:id="@+id/tv_call"
- android:layout_width="60dp"
- android:layout_height="match_parent"
- android:background="#666666"
- android:gravity="center"
- android:text="Call"
- android:textColor="#ffffff" />
- <TextView
- android:id="@+id/tv_del"
- android:layout_width="60dp"
- android:layout_height="match_parent"
- android:background="#ff0000"
- android:gravity="center"
- android:text="Delete"
- android:textColor="#ffffff" />
- </LinearLayout>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#44ffffff"
- android:gravity="center_vertical"
- android:orientation="horizontal" >
- <ImageView
- android:id="@+id/iv_image"
- android:layout_width="40dp"
- android:layout_height="40dp"
- android:layout_marginLeft="15dp"
- android:src="@drawable/head_1" />
- <TextView
- android:id="@+id/tv_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="15dp"
- android:text="Name" />
- </LinearLayout>
- </com.itheima.swipelayout.ui.SwipeLayout>
(2)找到子View的ID
- private View mBackView;
- private View mFrontView;
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- // 當xml被填充完畢時調用
- mBackView = getChildAt(0);
- mFrontView = getChildAt(1);
- }
(3)獲得控件寬高,可以在onMeasure方法中獲得,最好在onSizeChanged方法
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- mHeight = mFrontView.getMeasuredHeight();
- mWidth = mFrontView.getMeasuredWidth();
- mRange = mBackView.getMeasuredWidth();
- }
(4)onLayout
- @Override
- protected void onLayout(boolean changed, int left, int top, int right,
- int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- // 擺放位置
- layoutContent(false);
- }
- private void layoutContent(boolean isOpen) {
- // 擺放前View----前View的位置決定後View的位置
- Rect frontRect = computeFrontViewRect(isOpen);
- mFrontView.layout(frontRect.left, frontRect.top, frontRect.right, frontRect.bottom);
- // 擺放後View
- Rect backRect = computeBackViewViaFront(frontRect);
- mBackView.layout(backRect.left, backRect.top, backRect.right, backRect.bottom);
- // 調整順序, 把mFrontView前置
- bringChildToFront(mFrontView);
- }
- //前View,用矩形封裝位置
- private Rect computeFrontViewRect(boolean isOpen) {
- int left = 0;
- if(isOpen){//打開
- left = -mRange;
- }
- //關閉
- return new Rect(left, 0, left + mWidth, 0 + mHeight);
- }
- //後View
- private Rect computeBackViewViaFront(Rect frontRect) {
- int left = frontRect.right;
- return new Rect(left, 0, left + mRange, 0 + mHeight);
- }
4.重寫監聽CallBack
(1)onViewPositionChanged-----callBack
因爲後View隱藏了,不能拖拽,所以要把前View的偏移量傳給後View
把後View的也傳遞給前View
讓兩個View首尾相連
- //因爲後View隱藏了,不能拖拽,所以要把前View的偏移量傳給後View
- //把後View的也傳遞給前View
- //讓兩個View首尾相連
- public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
- // 傳遞事件
- if(changedView == mFrontView){
- mBackView.offsetLeftAndRight(dx);
- }else if (changedView == mBackView) {
- mFrontView.offsetLeftAndRight(dx);
- }
- //dispatchSwipeEvent();
- // 兼容老版本
- invalidate();
- };
2.側拉刪除_回調
1.限定滑動範圍--------CallBack中
- // 限定移動範圍
- public int clampViewPositionHorizontal(View child, int left, int dx) {
- // left left代表控件的左上角橫座標
- if(child == mFrontView){
- if(left > 0){
- return 0;
- }else if(left < -mRange){
- return -mRange;
- }
- }else if (child == mBackView) {
- if(left > mWidth){
- return mWidth;
- }else if (left < mWidth - mRange) {
- return mWidth - mRange;
- }
- }
- return left;
- };
2.當停止滑動時判斷是否開啓-------------CallBack中
- //當停止滑動時判斷是否開啓
- public void onViewReleased(View releasedChild, float xvel, float yvel) {
- //速度爲0
- if (xvel == 0 && mFrontView.getLeft() < -mRange / 2.0f) {
- open();
- }else if (xvel < 0) {//速度爲負數,即向左滑動
- open();
- }else {//速度爲正,即向右滑動
- close();
- }
- };
3.開啓和關閉和動畫
- public void close() {
- Utils.showToast(getContext(), "Close");
- close(true);
- }
- public void close(boolean isSmooth){
- int finalLeft = 0;
- if(isSmooth){//是否平滑滑動
- //開始動畫
- if(mDragHelper.smoothSlideViewTo(mFrontView, finalLeft, 0)){
- ViewCompat.postInvalidateOnAnimation(this);//界面重繪
- }
- }else {
- //直接跳轉
- layoutContent(false);
- }
- }
- public void open() {
- Utils.showToast(getContext(), "Open");
- open(true);
- }
- public void open(boolean isSmooth){
- int finalLeft = -mRange;
- if(isSmooth){
- //開始動畫
- if(mDragHelper.smoothSlideViewTo(mFrontView, finalLeft, 0)){
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }else {
- layoutContent(true);
- }
- }
- @Override
- //持續動畫-----模板代碼直接用
- public void computeScroll() {
- super.computeScroll();
- if(mDragHelper.continueSettling(true)){
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }
4.回調監聽
(1)設定監聽和狀態
- private Status status = Status.Close;
- private OnSwipeLayoutListener swipeLayoutListener;
- public Status getStatus() {
- return status;
- }
- public void setStatus(Status status) {
- this.status = status;
- }
- public OnSwipeLayoutListener getSwipeLayoutListener() {
- return swipeLayoutListener;
- }
- public void setSwipeLayoutListener(OnSwipeLayoutListener swipeLayoutListener) {
- this.swipeLayoutListener = swipeLayoutListener;
- }
- public static enum Status{
- Close, Open, Draging
- }
- public static interface OnSwipeLayoutListener {
- void onClose(SwipeLayout mSwipeLayout);
- void onOpen(SwipeLayout mSwipeLayout);
- void onDraging(SwipeLayout mSwipeLayout);
- // 要去關閉
- void onStartClose(SwipeLayout mSwipeLayout);
- // 要去開啓
- void onStartOpen(SwipeLayout mSwipeLayout);
- }
(2)監聽位置
onViewPositionChanged方法:每次界面變化都會調用,高頻調用dispatchSwipeEvent
- public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
- // 傳遞事件
- if(changedView == mFrontView){
- mBackView.offsetLeftAndRight(dx);
- }else if (changedView == mBackView) {
- mFrontView.offsetLeftAndRight(dx);
- }
- dispatchSwipeEvent();
- // 兼容老版本
- invalidate();
- };
(3)狀態的判斷和變化
- protected void dispatchSwipeEvent() {
- if(swipeLayoutListener != null){
- swipeLayoutListener.onDraging(this);
- }
- // 記錄上一次的狀態
- Status preStatus = status;
- // 更新當前狀態
- status = updateStatus();
- if (preStatus != status && swipeLayoutListener != null) {
- if (status == Status.Close) {
- swipeLayoutListener.onClose(this);
- } else if (status == Status.Open) {
- swipeLayoutListener.onOpen(this);
- } else if (status == Status.Draging) {
- if(preStatus == Status.Close){
- swipeLayoutListener.onStartOpen(this);
- }else if (preStatus == Status.Open) {
- swipeLayoutListener.onStartClose(this);
- }
- }
- }
- }
- private Status updateStatus() {
- int left = mFrontView.getLeft();
- if(left == 0){
- return Status.Close;
- }else if (left == -mRange) {
- return Status.Open;
- }
- return Status.Draging;
- }
3.側拉刪除_放入ListView
1.SwipeLayout完整
- /**
- * 側拉刪除控件
- * @author poplar
- *
- */
- public class SwipeLayout extends FrameLayout {
- private Status status = Status.Close;
- private OnSwipeLayoutListener swipeLayoutListener;
- public Status getStatus() {
- return status;
- }
- public void setStatus(Status status) {
- this.status = status;
- }
- public OnSwipeLayoutListener getSwipeLayoutListener() {
- return swipeLayoutListener;
- }
- public void setSwipeLayoutListener(OnSwipeLayoutListener swipeLayoutListener) {
- this.swipeLayoutListener = swipeLayoutListener;
- }
- public static enum Status{
- Close, Open, Draging
- }
- public static interface OnSwipeLayoutListener {
- void onClose(SwipeLayout mSwipeLayout);
- void onOpen(SwipeLayout mSwipeLayout);
- void onDraging(SwipeLayout mSwipeLayout);
- // 要去關閉
- void onStartClose(SwipeLayout mSwipeLayout);
- // 要去開啓
- void onStartOpen(SwipeLayout mSwipeLayout);
- }
- public SwipeLayout(Context context) {
- this(context, null);
- }
- public SwipeLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
- public SwipeLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mDragHelper = ViewDragHelper.create(this, 1.0f, mCallback);
- }
- ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
- // c. 重寫監聽
- @Override
- public boolean tryCaptureView(View view, int id) {
- return true;
- }
- // 限定移動範圍
- public int clampViewPositionHorizontal(View child, int left, int dx) {
- // left
- if(child == mFrontView){
- if(left > 0){
- return 0;
- }else if(left < -mRange){
- return -mRange;
- }
- }else if (child == mBackView) {
- if(left > mWidth){
- return mWidth;
- }else if (left < mWidth - mRange) {
- return mWidth - mRange;
- }
- }
- return left;
- };
- public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
- // 傳遞事件
- if(changedView == mFrontView){
- mBackView.offsetLeftAndRight(dx);
- }else if (changedView == mBackView) {
- mFrontView.offsetLeftAndRight(dx);
- }
- dispatchSwipeEvent();
- // 兼容老版本
- invalidate();
- };
- public void onViewReleased(View releasedChild, float xvel, float yvel) {
- if (xvel == 0 && mFrontView.getLeft() < -mRange / 2.0f) {
- open();
- }else if (xvel < 0) {
- open();
- }else {
- close();
- }
- };
- };
- private ViewDragHelper mDragHelper;
- private View mBackView;
- private View mFrontView;
- private int mHeight;
- private int mWidth;
- private int mRange;
- // b. 傳遞觸摸事件
- @Override
- public boolean onInterceptTouchEvent(android.view.MotionEvent ev) {
- return mDragHelper.shouldInterceptTouchEvent(ev);
- };
- protected void dispatchSwipeEvent() {
- if(swipeLayoutListener != null){
- swipeLayoutListener.onDraging(this);
- }
- // 記錄上一次的狀態
- Status preStatus = status;
- // 更新當前狀態
- status = updateStatus();
- if (preStatus != status && swipeLayoutListener != null) {
- if (status == Status.Close) {
- swipeLayoutListener.onClose(this);
- } else if (status == Status.Open) {
- swipeLayoutListener.onOpen(this);
- } else if (status == Status.Draging) {
- if(preStatus == Status.Close){
- swipeLayoutListener.onStartOpen(this);
- }else if (preStatus == Status.Open) {
- swipeLayoutListener.onStartClose(this);
- }
- }
- }
- }
- private Status updateStatus() {
- int left = mFrontView.getLeft();
- if(left == 0){
- return Status.Close;
- }else if (left == -mRange) {
- return Status.Open;
- }
- return Status.Draging;
- }
- public void close() {
- Utils.showToast(getContext(), "Close");
- close(true);
- }
- public void close(boolean isSmooth){
- int finalLeft = 0;
- if(isSmooth){
- //開始動畫
- if(mDragHelper.smoothSlideViewTo(mFrontView, finalLeft, 0)){
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }else {
- layoutContent(false);
- }
- }
- public void open() {
- Utils.showToast(getContext(), "Open");
- open(true);
- }
- public void open(boolean isSmooth){
- int finalLeft = -mRange;
- if(isSmooth){
- //開始動畫
- if(mDragHelper.smoothSlideViewTo(mFrontView, finalLeft, 0)){
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }else {
- layoutContent(true);
- }
- }
- @Override
- public void computeScroll() {
- super.computeScroll();
- if(mDragHelper.continueSettling(true)){
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- try {
- mDragHelper.processTouchEvent(event);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return true;
- }
- @Override
- protected void onLayout(boolean changed, int left, int top, int right,
- int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- // 擺放位置
- layoutContent(false);
- }
- private void layoutContent(boolean isOpen) {
- // 擺放前View
- Rect frontRect = computeFrontViewRect(isOpen);
- mFrontView.layout(frontRect.left, frontRect.top, frontRect.right, frontRect.bottom);
- // 擺放後View
- Rect backRect = computeBackViewViaFront(frontRect);
- mBackView.layout(backRect.left, backRect.top, backRect.right, backRect.bottom);
- // 調整順序, 把mFrontView前置
- bringChildToFront(mFrontView);
- }
- private Rect computeBackViewViaFront(Rect frontRect) {
- int left = frontRect.right;
- return new Rect(left, 0, left + mRange, 0 + mHeight);
- }
- private Rect computeFrontViewRect(boolean isOpen) {
- int left = 0;
- if(isOpen){
- left = -mRange;
- }
- return new Rect(left, 0, left + mWidth, 0 + mHeight);
- }
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- // 當xml被填充完畢時調用
- mBackView = getChildAt(0);
- mFrontView = getChildAt(1);
- }
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- mHeight = mFrontView.getMeasuredHeight();
- mWidth = mFrontView.getMeasuredWidth();
- mRange = mBackView.getMeasuredWidth();
- }
- }
2.ListView
(1)ListView中item佈局
- <?xml version="1.0" encoding="utf-8"?>
- <com.itheima.swipelayout.ui.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/sl"
- android:layout_width="match_parent"
- android:layout_height="60dp"
- android:minHeight="60dp"
- android:background="#44000000" >
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:orientation="horizontal" >
- <TextView
- android:id="@+id/tv_call"
- android:layout_width="60dp"
- android:layout_height="match_parent"
- android:background="#666666"
- android:gravity="center"
- android:text="Call"
- android:textColor="#ffffff" />
- <TextView
- android:id="@+id/tv_del"
- android:layout_width="60dp"
- android:layout_height="match_parent"
- android:background="#ff0000"
- android:gravity="center"
- android:text="Delete"
- android:textColor="#ffffff" />
- </LinearLayout>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#44ffffff"
- android:gravity="center_vertical"
- android:orientation="horizontal" >
- <ImageView
- android:id="@+id/iv_image"
- android:layout_width="40dp"
- android:layout_height="40dp"
- android:layout_marginLeft="15dp"
- android:src="@drawable/head_1" />
- <TextView
- android:id="@+id/tv_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="15dp"
- android:text="Name" />
- </LinearLayout>
- </com.itheima.swipelayout.ui.SwipeLayout>
(2)MyAdapter
- public class MyAdapter extends BaseAdapter {
- protected static final String TAG = "TAG";
- public MyAdapter(Context context) {
- super();
- this.context = context;
- opendItems = new ArrayList<SwipeLayout>();
- }
- private Context context;
- private ArrayList<SwipeLayout> opendItems;
- @Override
- public int getCount() {
- return NAMES.length;
- }
- @Override
- public Object getItem(int position) {
- return NAMES[position];
- }
- @Override
- public long getItemId(int position) {
- // TODO Auto-generated method stub
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View view = convertView;
- if(convertView == null){
- view = View.inflate(context, R.layout.item_list, null);
- }
- ViewHolder mHolder = ViewHolder.getHolder(view);
- SwipeLayout sl = (SwipeLayout)view;
- sl.setSwipeLayoutListener(new OnSwipeLayoutListener() {
- @Override
- public void onStartOpen(SwipeLayout mSwipeLayout) {
- Log.d(TAG, "onStartOpen");
- // 要去開啓時,先遍歷所有已打開條目, 逐個關閉
- for (SwipeLayout layout : opendItems) {
- layout.close();
- }
- opendItems.clear();
- }
- @Override
- public void onStartClose(SwipeLayout mSwipeLayout) {
- Log.d(TAG, "onStartClose");
- }
- @Override
- public void onOpen(SwipeLayout mSwipeLayout) {
- Log.d(TAG, "onOpen");
- // 添加進集合
- opendItems.add(mSwipeLayout);
- }
- @Override
- public void onDraging(SwipeLayout mSwipeLayout) {
- }
- @Override
- public void onClose(SwipeLayout mSwipeLayout) {
- Log.d(TAG, "onClose");
- // 移除集合
- opendItems.remove(mSwipeLayout);
- }
- });
- return view;
- }
- static class ViewHolder {
- TextView tv_call;
- TextView tv_del;
- public static ViewHolder getHolder(View view) {
- Object tag = view.getTag();
- if(tag == null){
- ViewHolder viewHolder = new ViewHolder();
- viewHolder.tv_call = (TextView)view.findViewById(R.id.tv_call);
- viewHolder.tv_del = (TextView)view.findViewById(R.id.tv_del);
- tag = viewHolder;
- view.setTag(tag);
- }
- return (ViewHolder)tag;
- }
- }
- }
以上主要主要摘自http://blog.csdn.net/u012360634/article/details/49663557
優化:監聽ListView滾動時關閉所有打開的
接下來是原創解決方法
1.解決listview的上下拉動和自定義view的左右拉動時的事件衝突
在自定義view初始化的時候,也初始化手勢監聽器
public leftDeleteView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// a.初始化
viewDragHelper = ViewDragHelper.create(this, 1.0f, mCallback);
gestureDetector = new GestureDetector(context, getstureListener);
}
GestureDetector.SimpleOnGestureListener getstureListener = new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// 當橫向移動距離大於等於縱向時,返回true
return Math.abs(distanceX) >= Math.abs(distanceY);
}
};
然後在onInterceptTouchEvent中根據上下和左右的位移差來判斷是否對觸摸事件進行攔截
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 決定當前的SwipeLayout是否要把touch事件攔截下來,直接交由自己的onTouchEvent處理
// 返回true則爲攔截
return viewDragHelper.shouldInterceptTouchEvent(ev) & gestureDetector.onTouchEvent(ev);
}
如果返回的時true的話,那麼就會進入該view的onTouchEvent方法中,我們再在這個方法中上下和左右的位移差判斷是否響應觸摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// 當處理touch事件時,不希望被父類onInterceptTouchEvent的代碼所影響。
// 比如處理向右滑動關閉已打開的條目時,如果進行以下邏輯,則不會在關閉的同時引發左邊菜單的打開。
switch (MotionEventCompat.getActionMasked(event)) {
case MotionEvent.ACTION_DOWN:
mDownX = event.getRawX();
break;
case MotionEvent.ACTION_MOVE:
float deltaX = event.getRawX() - mDownX;
//viewDragHelper.getTouchSlop()//viewDragHelper向上滑動的距離
//Log.i("xuqunxing", "deltaX:"+deltaX+",heigh:"+ viewDragHelper.getTouchSlop());
if (Math.abs(deltaX)> viewDragHelper.getTouchSlop()) {
Log.i("xuqunxing", "requestDisallowInterceptTouchEvent");
// 請求父級View不攔截touch事件
requestDisallowInterceptTouchEvent(true);
}
break;
case MotionEvent.ACTION_UP:
mDownX = 0;
default:
break;
}
viewDragHelper.processTouchEvent(event);
return true;// 設置爲true才能觸摸到,才能拖動
}
這樣就解決了,源碼地址:http://download.csdn.net/detail/iblue007/9369406
最後上圖