本篇文章已受權微信公衆號 guolin_blog (郭霖)獨家發佈javascript
轉載請標明出處:
gold.xitu.io/post/58285e…
本文出自:【張旭童的稀土掘金】(gold.xitu.io/user/56de21…)
代碼傳送門:喜歡的話,隨手點個star。多謝
github.com/mcxtzhang/S…java
本控件不依賴任何父佈局,不是針對 RecyclerView、ListView,而是任意的ViewGroup裏的childView均可以使用側滑(刪除)菜單。
支持任意ViewGroup、0耦合、史上最簡單。android
本控件從擼出來在項目使用至今已通過去7個月,距離第一次將它push至github上,也已經2月+。(以前,我發表過一篇文章。傳送門:blog.csdn.net/zxt0601/art…, 裏面詳細描述了本控件V1.0版本是怎麼實現的。)ios
期間有不少朋友在評論、issue裏提出了一些改進意見,例如支持設置滑動方向(左右)、高仿QQ的交互、支持GridLayoutManager等,以及一些bug。已經被我所有實、修復。而且將其打包至jitpack,引入更方便。和初版相比,改動挺多的。故將其整理,新發一版。git
那麼本文先從如何使用它講起,而後介紹它包含的特性、支持的屬性。最後就幾個難點和衝突的解決進行講解。github
代碼傳送門:喜歡的話,隨手點個star。多謝
github.com/mcxtzhang/S…微信
先上幾個gif給各位看官感覺一下最新版的魅力(如下版本都順便展現了可選的雙向滑動)app
本控件最大魅力就是0耦合,因此先上配合我另外一個庫組裝的效果(ItemDecorationIndexBar + SwipeMenuLayout):
(ItemDecorationIndexBar : github.com/mcxtzhang/I…)maven
Android Special Version (無阻塞式,側滑菜單展開時,依然能夠展開其餘側滑菜單,同時上一個菜單會自動關閉):ide
GridLayoutManager (和上圖的代碼比,只需修改RecyclerView的LayoutManager。):
LinearLayout (不需任何修改,連LinearLayout也能夠簡單的實現側滑菜單):
iOS interaction (阻塞式交互,高仿QQ,側滑菜單展開式,屏蔽其餘ITEM全部操做):
Step 1. 在項目根build.gradle文件中增長JitPack倉庫依賴。
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}複製代碼
Step 2. Add the dependency
dependencies {
compile 'com.github.mcxtzhang:SwipeDelMenuLayout:V1.2.1'
}複製代碼
Step 3. 在須要側滑刪除的ContentItem外面套上本控件,在本控件內依次排列ContentItem、菜單便可:
至此 您就可使用高仿IOS、QQ 側滑刪除菜單功能了
(側滑菜單的點擊事件等是經過設置的id取到,與其餘控件一致,再也不贅述)
Demo裏,個人ContentItem是一個TextView,那麼我就在其外嵌套本控件,而且以側滑菜單出現的順序,依次排列菜單控件便可。
<?xml version="1.0" encoding="utf-8"?>
<com.mcxtzhang.swipemenulib.SwipeMenuLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp"
android:clickable="true"
android:paddingBottom="1dp">
<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:gravity="center"
android:text="項目中我是任意複雜的原ContentItem佈局"/>
<!-- 如下都是側滑菜單的內容依序排列 -->
<Button
android:id="@+id/btnTop"
android:layout_width="60dp"
android:layout_height="match_parent"
android:background="#d9dee4"
android:text="置頂"
android:textColor="@android:color/white"/>
<Button
android:id="@+id/btnUnRead"
android:layout_width="120dp"
android:layout_height="match_parent"
android:background="#ecd50a"
android:clickable="true"
android:text="標記未讀"
android:textColor="@android:color/white"/>
<Button
android:id="@+id/btnDelete"
android:layout_width="60dp"
android:layout_height="match_parent"
android:background="@color/red_ff4a57"
android:text="刪除"
android:textColor="@android:color/white"/>
</com.mcxtzhang.swipemenulib.SwipeMenuLayout>複製代碼
1 經過 isIos 變量控制是不是IOS阻塞式交互,默認是打開的。
2 經過 isSwipeEnable 變量控制是否開啓右滑菜單,默認打開。(某些場景,複用item,沒有編輯權限的用戶不能右滑)
3 經過開關 isLeftSwipe支持左滑右滑
有兩種方式設置:
一:xml:
<com.mcxtzhang.swipemenulib.SwipeMenuLayout xmlns:app="http://schemas.android.com/apk/res-auto" app:ios="false" app:leftSwipe="true" app:swipeEnable="true">複製代碼
二: java代碼:
//這句話關掉IOS阻塞式交互效果 並依次打開左滑右滑 禁用掉側滑菜單
((SwipeMenuLayout) holder.itemView).setIos(false).setLeftSwipe(position % 2 == 0 ? true : false).setSwipeEnable(false);複製代碼
因爲持續迭代,會發生完成一個feature、fix一個bug後,致使新的bug。
so,整理一份checkList,供每次迭代後驗證,都經過,纔會push到github庫上。
項目 | 備註 | 驗證 |
---|---|---|
isIos | 切換至IOS阻塞交互模式、Android特點無阻塞交互模式 如下feature均可正常工做 | |
isSwipeEnable | 是否支持關閉側滑功能 | |
isLeftSwipe | 是否支持雙向滑動 | |
ContentItem內容可單擊 | ||
ContentItem內容可長按 | ||
側滑菜單顯示時,ContentItem不可點擊 | ||
側滑菜單顯示時,ContentItem不可長按 | ||
側滑菜單顯示時,側滑菜單能夠點擊 | ||
側滑菜單顯示時,點擊ContentItem區域關閉菜單 | ||
側滑過程當中,屏蔽長按事件 | ||
經過滑動關閉菜單,不該該觸發ContentItem點擊事件 |
1 ContentItem的長按和本控件側滑的衝突。
一開始我仍是老思路,一直都是經過判斷手指起始落點的座標,判斷手指落點處於何位置,屏蔽一些操做。當本控件功能愈來愈龐大,計算開始複雜,也容易出錯。但也跌跌撞撞的撐了過來,但就在今天,我想到了另外一種思路:經過禁用子View的longClickable屬性來完成這個功能。因而我重構這部分代碼,先利用git 查看以前的提交,去除掉那部分代碼,並
在側滑菜單展開smoothExpand()
、關閉smoothClose()
的函數中分別加入:
//2016 11 13 add 側滑菜單展開,屏蔽content長按
if (null != mContentView) {
mContentView.setLongClickable(true);
}複製代碼
//2016 11 13 add 側滑菜單展開,屏蔽content長按
if (null != mContentView) {
mContentView.setLongClickable(true);
}複製代碼
代碼就這麼簡單,幾小時前我纔想到這麼簡單的解決方法,這也是促使我新擼一遍文章,記錄本控件的一些改動的原因之一。
2 如何支持任意父控件
這算是本控件最酷炫的魅力之一吧,在以前的文章 ,我詳細描述了實現過程。
總結起來,我利用了一個static變量,保存了當前正在展開的View,
在進行各項操做時就可預先判斷,如若會引發衝突,例如界面上出現兩個側滑菜單,
便自動關閉上一個菜單。
代碼以下:
//存儲的是當前正在展開的View
private static SwipeMenuLayout mViewCache;複製代碼
dispatchTouchEvent()
的ActionDown裏:
//若是down,view和cacheview不同,則立馬讓它還原。且把它置爲null
if (mViewCache != null) {
if (mViewCache != this) {
mViewCache.smoothClose();
}
//只要有一個側滑菜單處於打開狀態, 就不給外層佈局上下滑動了
getParent().requestDisallowInterceptTouchEvent(true);
}複製代碼
在側滑菜單展開smoothExpand()
、關閉smoothClose()
的函數:
//展開就加入ViewCache:
mViewCache = SwipeMenuLayout.this;複製代碼
//關閉置爲null
mViewCache = null;複製代碼
onDetachedFromWindow()
裏:
//每次ViewDetach的時候,判斷一下 ViewCache是否是本身,若是是本身,關閉側滑菜單,且ViewCache設置爲null,
// 理由:1 防止內存泄漏(ViewCache是一個靜態變量)
// 2 側滑刪除後本身後,這個View被Recycler回收,複用,下一個進入屏幕的View的狀態應該是普通狀態,而不是展開狀態。
@Override
protected void onDetachedFromWindow() {
if (this == mViewCache) {
mViewCache.smoothClose();
mViewCache = null;
}
super.onDetachedFromWindow();
}複製代碼
3 解決多指滑動衝突:
利用一個布爾值 flag,在ActionDown時判斷是否繼續接受觸摸事件:
代碼以下:
//防止多隻手指一塊兒滑個人flag 在每次down裏判斷, touch事件結束清空
private static boolean isTouching;複製代碼
dispatchTouchEvent()
的ActionDown裏:
if (isTouching) {//若是有別的指頭摸過了,那麼就return false。這樣後續的move..等事件也不會再來找這個View了。
return false;
} else {
isTouching = true;//第一個摸的指頭,趕忙改變標誌,宣誓主權。
}複製代碼
ActionUp裏:
isTouching = false;//沒有手指在摸我了複製代碼
4 支持GridLayoutManager
畢竟項目中在網格佈局中使用側滑菜單還屬少數,因此一開始我將場景簡單化,給本控件設置的寬度都是父控件的寬度-padding。後來有童鞋提出但願支持網格佈局,一開始我思路也走了彎路,我還想着構建一個MatchParent的MeasureSpec,而後傳給父控件(GridView、RecyclerView)用於測量呢。
正確思路是,取第一個子View,即ContentView的寬度用做本控件的寬度便可,這樣在layout側滑菜單時,天然而然將側滑菜單layout在了不可見的區域,只有經過滑動才能顯示它。
代碼也沒啥好說的:
在onMeasure()
中:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//2016 11 09 add,適配GridLayoutManager,將以第一個子Item(即ContentItem)的寬度爲控件寬度
int contentWidth = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
if (childView.getVisibility() != GONE) {
measureChildWithMargins(childView, widthMeasureSpec, 0, heightMeasureSpec, 0);
final MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
mHeight = Math.max(mHeight, childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
if (i > 0) {//第一個佈局是Left item,從第二個開始纔是RightMenu
mRightMenuWidths += childView.getMeasuredWidth();
} else {
contentWidth = childView.getMeasuredWidth();
}
}
}
setMeasuredDimension(contentWidth, mHeight);//寬度取第一個Item(Content)的寬度
}複製代碼
在onLayout()
中,順序layoutchildView便可。
以上是本控件近期or你們感興趣的一些點的詳解,更詳細的講解可去上篇文章觀看,或者去github上下載源碼瀏覽。
上文地址:
blog.csdn.net/zxt0601/art…
代碼傳送門:喜歡的話,隨手點個star。多謝
github.com/mcxtzhang/S…
你們使用中若有問題,多多反饋。