日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

自定义控件:侧拉删除

發布時間:2025/4/16 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 自定义控件:侧拉删除 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

SwipeLayout 側拉刪除

  • 掌握ViewDragHelper 的用法
  • 掌握平滑動畫的原理及狀態更新事件回調

應用場景:QQ 聊天記錄,郵件管理,需要對條目進行功能擴展的場景,效果圖:

ViewDragHelper 初始化

創建自定義控件SwipeLayout 繼承FrameLayout

public class SwipeLayout extends FrameLayout {private ViewDragHelper mHelper;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);//1.創建ViewDragHelpermHelper = ViewDragHelper.create(this, mCallback);}//2.轉交觸摸事件,攔截判斷,處理觸摸事件@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {return mHelper.shouldInterceptTouchEvent(ev);};@Overridepublic boolean onTouchEvent(MotionEvent event) {try {//多點觸摸有一些小bug,最好catch 一下mHelper.processTouchEvent(event);} catch (Exception e) {}//消費事件,返回truereturn true;};//3.處理回調事件ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {@Overridepublic boolean tryCaptureView(View child, int pointerId) {return false;}}; }

第3-8 行通過構造方法互調,將三個構造方法串連起來,這樣初始化代碼只需要寫在第三個構造方法中即可
第11-37 行ViewDragHelper 使用三步曲

界面初始化

將SwipeLayout 布局到activity_main.xml 中

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity" ><com.example.swipe.SwipeLayout android:layout_width="match_parent"android:layout_height="60dp" ><LinearLayout android:layout_width="wrap_content"android:layout_gravity="right"android:layout_height="match_parent" ><TextView android:layout_width="60dp"android:layout_height="match_parent"android:background="#666666"android:gravity="center"android:text="Call"android:textColor="#FFFFFF" /><TextView 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="#33000000"android:gravity="center_vertical" ><ImageView android:layout_width="40dp"android:layout_height="40dp"android:layout_marginLeft="10dp"android:src="@drawable/head_1" /><TextView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="10dp"android:text="宋江" /></LinearLayout></com.example.swipe.SwipeLayout> </RelativeLayout>

重寫SwipeLayout 中mCallback 方法,實現簡單的拖拽

//3.處理回調事件 ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {//返回值決定了child 是否可以被拖拽@Overridepublic boolean tryCaptureView(View child, int pointerId) {//child 被用戶拖拽的孩子return true;}//返回值決定將要移動到的位置,此時還沒有發生真正的移動@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {//left 建議移動到的位置return left;} };

拖拽事件的傳遞

限定拖拽范圍

第一個子view 命名為后布局,第二個子view 命名為前布局

private View mBackView; private View mFrontView; //此方法中查找控件 @Override protected void onFinishInflate() {super.onFinishInflate();mBackView = getChildAt(0);mFrontView = getChildAt(1); };

獲取控件寬高及拖拽范圍

private int mRange; private int mWidth; private int mHeight; @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);//mBackView 的寬度就是mFrontView 的拖拽范圍mRange = mBackView.getMeasuredWidth();//控件的寬mWidth = getMeasuredWidth();//控件的高mHeight = getMeasuredHeight(); }

重寫mCallback 回調方法

ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {//返回值決定了child 是否可以被拖拽@Overridepublic boolean tryCaptureView(View child, int pointerId) {return true;}//返回拖拽的范圍,返回一個大于0 的值,計算動畫執行的時長,水平方向是否可以被滑開@Overridepublic int getViewHorizontalDragRange(View child) {return mRange;}//返回值決定將要移動到的位置,此時還沒有發生真正的移動@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {// left 建議移動到位置if (child == mFrontView) {//限定前布局的拖拽范圍if (left < -mRange) {//前布局最小的左邊位置不能小于-mRangeleft = -mRange;} else if (left > 0) {//前布局最大的左邊位置不能大于0left = 0;}} else if (child == mBackView) {//限定后布局的拖拽范圍if (left < mWidth - mRange) {//后布局最小左邊位置不能小于mWidth - mRangeleft = mWidth - mRange;} else if (left > mWidth) {//后布局最大的左邊位置不能大于mWidthleft = mWidth;}}return left;} };

第7-11 行需要返回一個大于0 的拖拽范圍
第14-37 行通過mRange 分別計算前后布局的拖拽范圍

傳遞拖拽事件

初始化前后布局的位置,重寫SwipeLayout 的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) {//設置前布局位置Rect rect = computeFrontRect(isOpen);mFrontView.layout(rect.left, rect.top, rect.right, rect.bottom);//根據前布局位置計算后布局位置Rect backRect = computeBackRectViaFront(rect);mBackView.layout(backRect.left, backRect.top, backRect.right, backRect.bottom); }private Rect computeBackRectViaFront(Rect rect) {int left = rect.right;return new Rect(left, 0, left + mRange, mHeight); } /*** 計算布局所在矩形區域* @param isOpen* @return*/ private Rect computeFrontRect(boolean isOpen) {int left = 0;if(isOpen){left = -mRange;}return new Rect(left, 0, left + mWidth, mHeight); }

第2-7 行重新擺放子view 的位置
第8-15 行由于后布局是連接在前布局后面一起滑動的,所以可以通過前布局的位置計算后布局的位置
前后布局在拖拽過程中互相傳遞變化量

ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {// 返回值決定了child 是否可以被拖拽@Overridepublic boolean tryCaptureView(View child, int pointerId) {return true;}// 返回拖拽的范圍,返回一個大于0 的值,計算動畫執行的時長,水平方向是否可以被滑開@Overridepublic int getViewHorizontalDragRange(View child) {return mRange;}// 返回值決定將要移動到的位置,此時還沒有發生真正的移動@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {// left 建議移動到位置if (child == mFrontView) {// 限定前布局的拖拽范圍if (left < -mRange) {// 前布局最小的左邊位置不能小于-mRangeleft = -mRange;} else if (left > 0) {// 前布局最大的左邊位置不能大于0left = 0;}} else if (child == mBackView) {// 限定后布局的拖拽范圍if (left < mWidth - mRange) {// 后布局最小左邊位置不能小于mWidth - mRangeleft = mWidth - mRange;} else if (left > mWidth) {// 后布局最大的左邊位置不能大于mWidthleft = mWidth;}}return left;}//位置發生改變時,前后布局的變化量互相傳遞@Overridepublic void onViewPositionChanged(View changedView, int left, int top,int dx, int dy) {super.onViewPositionChanged(changedView, left, top, dx, dy);//left 最新的水平位置//dx 剛剛發生的水平變化量//位置變化時,把水平變化量傳遞給另一個布局if(changedView == mFrontView){//拖拽的是前布局,把剛剛發生的變化量dx 傳遞給后布局mBackView.offsetLeftAndRight(dx);}else if(changedView == mBackView){//拖拽的是后布局,把剛剛發生的變化量dx 傳遞給前布局mFrontView.offsetLeftAndRight(dx);}//兼容低版本,重繪一次界面invalidate();} };

第38-54 行拖拽前布局時,將前布局的變化量傳遞給后布局,拖拽后布局時,把后布局的變化量傳遞給前布局,這樣前后布局就可以連動起來

結束動畫

跳轉動畫

// 3.處理回調事件 ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {// 返回值決定了child 是否可以被拖拽@Overridepublic boolean tryCaptureView(View child, int pointerId) {return true;}// 返回拖拽的范圍,返回一個大于0 的值,計算動畫執行的時長,水平方向是否可以被滑開@Overridepublic int getViewHorizontalDragRange(View child) {return mRange;}// 返回值決定將要移動到的位置,此時還沒有發生真正的移動@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {// left 建議移動到位置if (child == mFrontView) {// 限定前布局的拖拽范圍if (left < -mRange) {// 前布局最小的左邊位置不能小于-mRangeleft = -mRange;} else if (left > 0) {// 前布局最大的左邊位置不能大于0left = 0;}} else if (child == mBackView) {// 限定后布局的拖拽范圍if (left < mWidth - mRange) {// 后布局最小左邊位置不能小于mWidth - mRangeleft = mWidth - mRange;} else if (left > mWidth) {// 后布局最大的左邊位置不能大于mWidthleft = mWidth;}}return left;}@Overridepublic void onViewPositionChanged(View changedView, int left, int top,int dx, int dy) {super.onViewPositionChanged(changedView, left, top, dx, dy);//left 最新的水平位置//dx 剛剛發生的水平變化量//位置變化時,把水平變化量傳遞給另一個布局if(changedView == mFrontView){//拖拽的是前布局,把剛剛發生的變化量dx 傳遞給后布局mBackView.offsetLeftAndRight(dx);}else if(changedView == mBackView){//拖拽的是后布局,把剛剛發生的變化量dx 傳遞給前布局mFrontView.offsetLeftAndRight(dx);}//兼容低版本,重繪一次界面invalidate();}//松手時會被調用@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) {super.onViewReleased(releasedChild, xvel, yvel);//xvel 水平方向上的速度,向左為-,向右為+if(xvel == 0 && mFrontView.getLeft() < -mRange * 0.5f){//xvel 變0 時,并且前布局的左邊位置小于-mRange 的一半open();}else if (xvel < 0){//xvel 為-時,打開open();}else{//其它情況為關閉close();}} }; public void close() {//調用之前布局子view 的方法直接跳轉到關閉位置layoutContent(false); }public void open() {//調用之前布局子view 的方法直接跳轉到打開位置layoutContent(true); }

第55-70 行重寫Callback 的onViewReleased()方法,該方法在松手后被調用,結束動畫需要在此處做

平滑動畫

public void close() {close(true); } public void open() {open(true); } public void close(boolean isSmooth) {int finalLeft = 0;if(isSmooth){if(mHelper.smoothSlideViewTo(mFrontView, finalLeft, 0)){ViewCompat.postInvalidateOnAnimation(this);};}else{layoutContent(false);} } public void open(boolean isSmooth) {int finalLeft = -mRange;if (isSmooth) {//mHelper.smoothSlideViewTo(child, finalLeft, finalTop)開啟一個平滑動畫將child//移動到finalLeft,finalTop 的位置上。此方法返回true 說明當前位置不是最終位置需要重繪if(mHelper.smoothSlideViewTo(mFrontView, finalLeft, 0)){//調用重繪方法//invalidate();可能會丟幀,此處推薦使用ViewCompat.postInvalidateOnAnimation()//參數一定要傳child 所在的容器,因為只有容器才知道child 應該擺放在什么位置ViewCompat.postInvalidateOnAnimation(this);};} else {layoutContent(true);} } //重繪時computeScroll()方法會被調用 @Override public void computeScroll() {super.computeScroll();//mHelper.continueSettling(deferCallbacks)維持動畫的繼續,返回true 表示還需要重繪if(mHelper.continueSettling(true)){ViewCompat.postInvalidateOnAnimation(this);} }

第1-31 行重載open(),close()方法,保留跳轉動畫,添加平滑動畫
第32-39 行重寫computeScroll()方法維持動畫的繼續,此處必須重寫,否則沒有動畫效果

監聽回調

定義回調接口

在SwipeLayout 中定義公開的接口

//控件有三種狀態 public enum Status{Open,Close,Swiping } //初始狀態為關閉 private Status status = Status.Close; public Status getStatus() {return status; }public void setStatus(Status status) {this.status = status; }public interface OnSwipeListener{//通知外界已經打開public void onOpen();//通知外界已經關閉public void onClose();//通知外界將要打開public void onStartOpen();//通知外界將要關閉public void onStartClose(); } private OnSwipeListener onSwipeListener; public OnSwipeListener getOnSwipeListener() {return onSwipeListener; } public void setOnSwipeListener(OnSwipeListener onSwipeListener) {this.onSwipeListener = onSwipeListener; }

第20-23 行SwipeLayout 做為ListView 的item 時將要打開或關閉時需要通知其它item 做相應的處理,所以增加這兩個方法

更新狀態及回調監聽

修改Callback 的onViewPositionChanged()方法

@Override public void onViewPositionChanged(View changedView, int left, int top,int dx, int dy) {super.onViewPositionChanged(changedView, left, top, dx, dy);//left 最新的水平位置//dx 剛剛發生的水平變化量//位置變化時,把水平變化量傳遞給另一個布局if(changedView == mFrontView){//拖拽的是前布局,把剛剛發生的變化量dx 傳遞給后布局mBackView.offsetLeftAndRight(dx);}else if(changedView == mBackView){//拖拽的是后布局,把剛剛發生的變化量dx 傳遞給前布局mFrontView.offsetLeftAndRight(dx);}//更新狀態及調用監聽dispatchDragEvent();//兼容低版本,重繪一次界面invalidate(); }

第15-16 行調用更新狀態及回調監聽的方法
dispatchDragEvent()方法

/*** 更新狀態回調監聽*/ protected void dispatchDragEvent() {//需要記錄一下上次的狀態,對比當前狀態和上次狀態,在狀態改變時調用監聽Status lastStatus = status;//獲取更新狀態status = updateStatus();//在狀態改變時調用監聽if(lastStatus != status && onSwipeListener != null){if(status == Status.Open){onSwipeListener.onOpen();}else if(status == Status.Close){onSwipeListener.onClose();}else if(status == Status.Swiping){if(lastStatus == Status.Close){//如果上一次狀態為關閉,現在是拖拽狀態,說明正在打開onSwipeListener.onStartOpen();}else if(lastStatus == Status.Open){//如果上一次狀態為打開,現在是拖拽狀態,說明正在關閉onSwipeListener.onStartClose();}}} } private Status updateStatus() {//通過前布局左邊的位置可以判斷當前的狀態int left = mFrontView.getLeft();if(left == 0){return Status.Close;}else if(left == -mRange){return Status.Open;}return Status.Swiping; }

修改activity_main.xml,給SwipeLayout 加上id

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity" ><com.example.swipe.SwipeLayout android:id="@+id/sl"android:layout_width="match_parent"android:layout_height="60dp" ><LinearLayout android:layout_width="wrap_content"android:layout_gravity="right"android:layout_height="match_parent" ><TextView android:layout_width="60dp"android:layout_height="match_parent"android:background="#666666"android:gravity="center"android:text="Call"android:textColor="#FFFFFF" /><TextView 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="#33000000"android:gravity="center_vertical" ><ImageView android:layout_width="40dp"android:layout_height="40dp"android:layout_marginLeft="10dp"android:src="@drawable/head_1" /><TextView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="10dp"android:text="宋江" /></LinearLayout></com.example.swipe.SwipeLayout> </RelativeLayout>

MainActivity 中設置監聽回調

public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);SwipeLayout swipeLayout = (SwipeLayout) findViewById(R.id.sl);swipeLayout.setOnSwipeListener(new OnSwipeListener() {@Overridepublic void onStartOpen() {Utils.showToast(getApplicationContext(), "要去打開了");}@Overridepublic void onStartClose() {Utils.showToast(getApplicationContext(), "要去關閉了");}@Overridepublic void onOpen() {Utils.showToast(getApplicationContext(), "已經打開了");}@Overridepublic void onClose() {Utils.showToast(getApplicationContext(), "已經關閉了");}});} }

Utils 提供單例Toast 方法

public class Utils {private static Toast toast;public static void showToast(Context context, String msg) {if (toast == null) {toast = Toast.makeText(context, "", Toast.LENGTH_SHORT);}toast.setText(msg);toast.show();} }

整合到ListView

修改activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity" ><ListViewandroid:id="@+id/lv"android:layout_width="match_parent"android:layout_height="match_parent" ></ListView> </RelativeLayout>

ListView 需要的數據

public class Cheeses {public static final String[] NAMES = new String[]{"宋江", "盧俊義", "吳用","公孫勝", "關勝", "林沖", "秦明", "呼延灼", "花榮", "柴進", "李應", "朱仝", "魯智 深","武松", "董平", "張清", "楊志", "徐寧", "索超", "戴宗", "劉唐", "李逵", "史進", " 穆弘","雷橫", "李俊", "阮小二", "張橫", "阮小五", " 張順", "阮小七", "楊雄", "石秀", " 解珍"," 解寶", "燕青", "朱武", "黃信", "孫立", "宣贊", "郝思文", "韓滔", "彭玘", "單廷珪 ","魏定國", "蕭讓", "裴宣", "歐鵬", "鄧飛", " 燕順", "楊林", "凌振", "蔣敬", "呂方 ","郭盛", "安道全", "皇甫端", "王英", "扈三娘", "鮑旭", "樊瑞", "孔明", "孔亮", " 項充","李袞", "金大堅", "馬麟", "童威", "童猛", "孟康", "侯健", "陳達", "楊春", "鄭天壽 ","陶宗旺", "宋清", "樂和", "龔旺", "丁得孫", "穆春", "曹正", "宋萬", "杜遷", "薛永 ", "施恩","周通", "李忠", "杜興", "湯隆", "鄒淵", "鄒潤", "朱富", "朱貴", "蔡福", "蔡慶", " 李立","李云", "焦挺", "石勇", "孫新", "顧大嫂", "張青", "孫二娘", " 王定六", "郁保四", " 白勝","時遷", "段景柱"}; }

修改SwipeLayout 的OnSwipeListener 接口,在回調接口方法時把自己傳出去

public interface OnSwipeListener{//通知外界已經打開public void onOpen(SwipeLayout swipeLayout);//通知外界已經關閉public void onClose(SwipeLayout swipeLayout);//通知外界將要打開public void onStartOpen(SwipeLayout swipeLayout);//通知外界將要關閉public void onStartClose(SwipeLayout swipeLayout); }

ListView 的Adapter

public class MyAdapter extends BaseAdapter {private Context context;//記錄上一次被打開itemprivate SwipeLayout lastOpenedSwipeLayout;public MyAdapter(Context context) {super();this.context = context;}@Overridepublic int getCount() {return Cheeses.NAMES.length;}@Overridepublic Object getItem(int position) {return Cheeses.NAMES[position];}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {if(convertView == null){convertView = View.inflate(context, R.layout.list_item, null);}TextView name = (TextView) convertView.findViewById(R.id.name);name.setText(Cheeses.NAMES[position]);SwipeLayout swipeLayout = (SwipeLayout) convertView;swipeLayout.setOnSwipeListener(new OnSwipeListener() {@Overridepublic void onOpen(SwipeLayout swipeLayout) {//當前item 被打開時,記錄下此itemlastOpenedSwipeLayout = swipeLayout;}@Overridepublic void onClose(SwipeLayout swipeLayout) {}@Overridepublic void onStartOpen(SwipeLayout swipeLayout) {//當前item 將要打開時關閉上一次打開的itemif(lastOpenedSwipeLayout != null){lastOpenedSwipeLayout.close();}}@Overridepublic void onStartClose(SwipeLayout swipeLayout) {}});return convertView;} }

總結

以上是生活随笔為你收集整理的自定义控件:侧拉删除的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。