Android仿抖音右滑清屏左滑列表功能的實(shí)現(xiàn)代碼
概述
項(xiàng)目中要實(shí)現(xiàn)仿抖音直播間滑動清屏,側(cè)滑列表的功能,在此記錄下實(shí)現(xiàn)過程和踩坑記錄希望避免大家走些彎路,也當(dāng)作自己的一個總結(jié)
首先看下Demo中的效果

閱讀文章需要提前熟悉些事件分發(fā)的內(nèi)容,相信大家都已經(jīng)了解過了,網(wǎng)上也有很多優(yōu)秀的文章,這里推薦兩篇自己讀過印象較深的文章
http://m.fzitv.net/article/124249.htm
http://m.fzitv.net/article/124861.htm
關(guān)于這方面的知識,在Android中是再重要不過的了,是遲早都要掌握的知識,所以還是希望大家都能提早掌握,最好可以跟著源碼一起分析,理解掌握的更深刻一點(diǎn)
實(shí)踐
所以網(wǎng)上基于這部分內(nèi)容講解已經(jīng)很詳細(xì)了,這里就不再搬磚了,主要分享一下自己項(xiàng)目中結(jié)合這部分知識運(yùn)用過程中產(chǎn)生的一些想法和經(jīng)驗(yàn),解決的一些bug

以上就是功能在實(shí)現(xiàn)過程中要解決的問題,下面詳細(xì)展開
1. 布局結(jié)構(gòu)
布局結(jié)構(gòu)始終是界面設(shè)計(jì)時首先要考慮的一個問題,從接到一個需求開始,首先要根據(jù)項(xiàng)目中現(xiàn)有的布局結(jié)構(gòu),考慮如何更優(yōu)雅的嵌入布局層次。如果一不小心,走上了錯誤的實(shí)現(xiàn)道路,那么不好意思,即使功能最后實(shí)現(xiàn)了,到了后期,也有千萬種理由迫使你不得不走上重構(gòu)的道路。
比如實(shí)現(xiàn)不合理,導(dǎo)致的布局結(jié)構(gòu)復(fù)雜,嵌套冗余層次,比如代碼業(yè)務(wù)邏輯處理復(fù)雜蹩腳,比如資源浪費(fèi),內(nèi)存消耗過多等等。雖然功能好使,使用起來也沒有差別,但是,作為一個有追求的程序員,我們還是要避免這種情況的發(fā)生不是嗎
不巧的是,本文就屬于上述踩坑記錄,下面詳細(xì)分析
1.1 初步實(shí)現(xiàn)
上來以后,思路很直接明了的去想要實(shí)現(xiàn)清屏和滑屏的功能是每個房間都有的功能,每個房間又都是一個RecyclerView 的一個Item。所以,很明顯在Item的布局上包一層,實(shí)現(xiàn)清屏和側(cè)滑列表的功能就可以了,這樣每個房間都可以上下滑,切換房間。切換以后,滑屏的功能是在每個房間里的,互不影響,所以很好理解
我們項(xiàng)目中實(shí)現(xiàn)直播間上下滑切換的功能是RecyclerView + 自定義LinearLayoutManager實(shí)現(xiàn)的,這部分內(nèi)容網(wǎng)上demo很多,就不展開了
具體實(shí)施,是自定義布局繼承RelativeLayout,解析自定義的布局文件,里面包含,直播間的房間布局,和自己右側(cè)滑塊兒布局,然后用自己實(shí)現(xiàn)的布局替換之前的房間Item布局位置

- 由于我們自定義的Container布局是繼承子RelativeLayout實(shí)現(xiàn)的,內(nèi)部三個子View 又全部是占滿父布局的,所以就是三層覆蓋的效果,類似抖音直播間效果
- 這里我們盡量將覆蓋層/清屏控件,封裝成一個ViewGroup 內(nèi)部包含了上邊細(xì)分的各個子View,例如頭部個人信息,頭像列表等等;中間彈幕,SVGA禮物展示區(qū)域;底部聊天評論區(qū)域方便管理
- 還有右側(cè)滑塊我們也做成繼承自RelativeLayout形式,解析自定義布局,方便擴(kuò)展
這樣我們調(diào)用封裝的Container將清屏控件,和右側(cè)滑塊兒布局View分別添加到內(nèi)部即可
API提供如下
// 添加需要清屏的view fun addClearViews(vararg views: View?) // 添加需要滑入的view fun addSlideView(view: RightSlideLayout)
這樣我們在視頻播放頁面滑動,就可以在Container內(nèi)判斷手勢,處理清屏控件或者滑出右側(cè)滑塊兒了
右側(cè)滑塊再動態(tài)加載Fragment,展示列表布局,基本完成功能效果了
1.2 重構(gòu)
本來以為開開心心的可以上線了,誰知到下邊繼續(xù)體驗(yàn)和對比抖音到過程中還是發(fā)現(xiàn)不足:
第一個是,右側(cè)滑塊兒(后邊稱RightSlider)包含在房間,這樣上下切換房間(后邊稱Container),RightSlider布局也會隨著Container新建而新建,雖然有RecyclerView的布局緩存,但是至少也會新建Holder幾次,造成資源的浪費(fèi)。第二個是,RightSlider的新建就會導(dǎo)致里邊的Fragment的新建,所以又會重新請求加載列表數(shù)據(jù),再次造成資源浪費(fèi),而且,新建后右側(cè)列表又會重新頂?shù)筋^,之前滑動過的距離就會丟失。這樣就造成,用戶從右側(cè)列表點(diǎn)擊切換房間后,再次滑出RightSlider切換房間,發(fā)現(xiàn)又要從頭開始往下滑,這樣肯定不符合用戶體驗(yàn)。觀察抖音列表后發(fā)現(xiàn),每次滑動到固定位置點(diǎn)擊Item切換房間后,再次滑出滑塊兒,發(fā)現(xiàn)列表還是之前的位置,好像跟之前滑出的是一個滑塊兒的效果,于是恍然大悟,滑塊兒是跟Activity綁定的,也就是要把RightSlider放在跟Activity布局那一層
其實(shí)提出RightSlider到外層的過程中,還是走了不少彎路,因?yàn)橹爱吘挂呀?jīng)實(shí)現(xiàn)好的邏輯,如果改動布局結(jié)構(gòu),肯定要重寫滑動沖突、事件分發(fā)這部分代碼,工作量又不可預(yù)計(jì)。所以想著能不能不動布局結(jié)構(gòu)的情況下實(shí)現(xiàn)仿抖音效果
動態(tài)替換Fragment
首先想到的是滑出RightSlider里的列表每次都好像是同一個,那么保證里邊的Fragment是同一個不就好了,滑出的滑塊兒雖然不同,但是里邊裝載的Fragment列表是同一個,這樣就營造出同一個滑塊兒的效果。
但是實(shí)現(xiàn)過程中還是出現(xiàn)了問題,由于RecyclerView的預(yù)加載功能,導(dǎo)致我們項(xiàng)目中,從第一個房間上滑到下一個房間,過程中會新建兩個Holder,這樣Fragment替換就出了問題,切換房間后Fragment添加不上去,折騰一下午后最終放棄這個方案
固定List高度
然后想的,既然Fragment替換不了了,那么RecyclerView肯定不是同一個了,如果點(diǎn)擊后記錄當(dāng)前RecyclerView滑動的位置,下次滑出時,代碼固定到當(dāng)前位置不是也可以偽造出同一個滑塊兒的效果嘛,這部分也去找了一些資料,實(shí)現(xiàn)了個小demo。其中用到的主要方法是
/**
* 獲取滑動距離
*/
fun getScollYDistance(): Int {
// 獲取recyclerview 的layoutManager
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
// 獲取當(dāng)前第一個可見View的位置
val position = layoutManager.findFirstVisibleItemPosition()
// 根據(jù)position 獲取當(dāng)前View
val firstVisiableChildView = layoutManager.findViewByPosition(position)
// 獲取當(dāng)前View 高度
val itemHeight = firstVisiableChildView.height
// 滑動距離
return position * itemHeight - firstVisiableChildView.top
}
滑動距離計(jì)算的思想是:根據(jù)當(dāng)前可見View 的position * 每個ItemView 的高度 + 當(dāng)前View已經(jīng)滑出去的部分

計(jì)算出高度后,每次加載時,調(diào)用RecyclerView的API
recyclerView.scrollBy(0,scroll) //scroll 剛才計(jì)算的高度
還有其他幾個滑動的方法:
// 帶動畫移動距離 public void smoothScrollBy(int dx, int dy) // 帶動畫移動到position public void smoothScrollToPosition(int position) // 移動到adapter position ,由LayoutManager實(shí)現(xiàn) public void scrollToPosition(int position) // 空實(shí)現(xiàn),無效 public void scrollTo(int x, int y)
原理上可以實(shí)現(xiàn),但是最后綜合比較還是放棄了這種方式,因?yàn)榭偢杏X這種方法屬于投機(jī)取巧不是正道,還是老老實(shí)實(shí)將RightSlider 提到外面得了
2. 動畫
動畫也是這個功能中很重要的一個方面,因?yàn)閯赢嬓Ч牧鲿持苯佑绊懥擞脩趔w驗(yàn),所以這方面也是細(xì)扣了很久。首先這個功能主要分成三個動畫效果:
2.1 進(jìn)場出場
包含清屏控件入場、出場:
mClearAnimator = ValueAnimator.ofFloat(0f, 1.0f).setDuration(300)
mClearAnimator.addUpdateListener(ValueAnimator.AnimatorUpdateListener { valueAnimator ->
val value = valueAnimator.animatedValue as Float
translateClearChild((startX + value * (endX - startX)).toInt())
})
mClearAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
isCleared = !isCleared
}
})
這里使用了屬性動畫ValueAnimator,其中 translateClearChild 負(fù)責(zé)移動View 代碼如下:
/**
* 移動清屏控件
*/
private fun translateClearChild(translate: Int) {
for (i in mClearViews.indices) {
mClearViews[i].translationX = translate.toFloat()
}
}
滑塊兒的入場、出場:
mSlideInAnimator = ValueAnimator.ofFloat(0f, 1.0f).setDuration(500)
// 設(shè)置減速攔截器
mSlideInAnimator.interpolator = DecelerateInterpolator(3f)
mSlideInAnimator.addUpdateListener(ValueAnimator.AnimatorUpdateListener { valueAnimator ->
val value = valueAnimator.animatedValue as Float
translateSlideView((startX + value * (endX - startX)).toInt())
})
mSlideInAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator) {
mSlideView!!.visibility = View.VISIBLE
mBgColorView.isClickable = true
}
override fun onAnimationEnd(animation: Animator) {
if (!isSlideShow && translateX == 0) {
isSlideShow = !isSlideShow
} else if (isSlideShow && abs(translateX) == width - mSlideView!!.paddingLeft) {
isSlideShow = !isSlideShow
}
if (!isSlideShow) {
parent.requestDisallowInterceptTouchEvent(false)
mSlideView!!.visibility = View.GONE
removeView(mBgColorView)
addView(mBgColorView, childCount - 4)
}
isSliderGoning = false
}
})
這里startX,endX 分別代表入場和出場時候,動畫起止位置。由于清屏控件沒有中間位置狀態(tài),直接是從0 到屏幕寬度兩個值之間替換;而滑塊兒中間由于要跟隨手勢移動,所以要記錄中間translateX,標(biāo)記為startX
2.2 跟隨手勢
跟隨手勢實(shí)現(xiàn)主要是攔截移動手勢,根據(jù)按下手勢位置坐標(biāo)和Move移動位置坐標(biāo)的差值,調(diào)用移動SliderView的方法
val x = event.rawX.toInt()
// 標(biāo)記移動距離
val offsetX = x - mDownX
when (event.action) {
MotionEvent.ACTION_MOVE -> {
if ((isSlideShow) && offsetX > 0 && mSlideInAnimator.isRunning && !isSliderGoning) {
// 滑入情況下,向右滑一段松開,再向右滑,清除回彈動畫,跟隨手勢
mSlideInAnimator.cancel()
translateSlideView(offsetX)
}
if ((isSlideShow) && offsetX > 0 && !mSlideInAnimator.isRunning) {
// 滑入情況下,向右滑,跟隨手勢
translateSlideView(offsetX)
}
return true
}
}
2.3 顏色漸變
跟隨手勢滑動過程中還伴隨的左側(cè)空白區(qū)域顏色漸變,這部分可以在RightSlider移動過程中的距離值關(guān)聯(lián)起來,設(shè)置起始顏色透明和截止顏色灰色蒙層。再根據(jù)距離動態(tài)算出當(dāng)前顏色在區(qū)間范圍內(nèi)取值,主要代碼邏輯如下
/**
* 移動滑塊兒
*/
private fun translateSlideView(translate: Int) {
val percent = (mSlideView!!.width.toFloat() - translate) / mSlideView!!.width
// 根據(jù)百分比算出色值
val color = (MASK_DARK_COLOR * percent).toInt() shl 24
// 動態(tài)設(shè)置背景色漸變
mBgColorView.setBackgroundColor(color)
translateX = translate
mSlideView!!.translationX = translate.toFloat()
}
3 事件分發(fā)
這部分可以說是本功能實(shí)現(xiàn)的核心,也是耗費(fèi)了相當(dāng)時間的精力,從最開始的Container包含RightSlider布局處理經(jīng)典的事件分發(fā)順序,到最后重構(gòu)布局,將RightSlider提到外層變成不是包含關(guān)系,而是并列或者說是覆蓋關(guān)系,中間對事件傳遞的順序理解又深入了一層
3.1 傳遞順序
重構(gòu)之前的布局結(jié)構(gòu)是每個Container包含了一個RightSlider,兩個是一個整體使用的,滑動的邏輯都可以在Container層內(nèi)的onInterceptTouchEvent方法內(nèi)處理。判斷是否攔截事件即可,然后RightSlider內(nèi)想要禁止父層Container攔截事件,可以使用parent.requestDisallowInterceptTouchEvent(true)禁止父層攔截;是屬于經(jīng)典模式的事件分發(fā)模型,事件分發(fā)的順序在一個U型結(jié)構(gòu)里,比較好處理
然后重構(gòu)以后布局結(jié)構(gòu)變成了如下圖所示

每個Container 共用一個RightSlider,這樣屬于事件的分發(fā)處理不在一個ViewGroup的U型模型里了,這樣的分發(fā)順序也是屬于自己的一個大膽嘗試,想著實(shí)在不行,還是要把Activity內(nèi)布局包一層,將Container和RightSlider 放在一個U型結(jié)構(gòu)里去處理。
還好最后不斷踩坑,終于實(shí)現(xiàn)了事件從Activity分發(fā),到RightSlider,再分發(fā)到Container的過程
這里貼下Demo里的布局實(shí)現(xiàn):
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@mipmap/bg" tools:context=".MainActivity"> <com.fxf.slide.SlideContainerLayout android:id="@+id/layout_slider_container" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:id="@+id/ll12" android:layout_width="match_parent" android:layout_height="200dp" android:layout_gravity="center_horizontal" android:layout_marginTop="100dp" android:background="#00f" android:orientation="vertical"> <TextView android:id="@+id/tv111" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="111111111" android:textColor="#fff" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="222222222" android:textColor="#fff" /> </LinearLayout> </com.fxf.slide.SlideContainerLayout> <com.fxf.slide.RightSlideLayout android:id="@+id/layout_right_slider" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="60dp" android:visibility="gone"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/shape_slider_background"> <View android:id="@+id/live_slide_bar" android:layout_width="4.5dp" android:layout_height="90dp" android:layout_centerVertical="true" android:layout_marginLeft="5dp" android:layout_marginRight="5dp" android:background="@drawable/shape_slider_dark_bar" /> <FrameLayout android:id="@+id/list_fragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_toRightOf="@+id/live_slide_bar" /> </RelativeLayout> </com.fxf.slide.RightSlideLayout> </androidx.constraintlayout.widget.ConstraintLayout>
其中做了部分簡化,主要幫助大家理解布局層次
然后貼下RightSlider核心分發(fā)代碼:
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
// 獲取坐標(biāo),這里用rawX 相對屏幕絕對位置,不然隨手勢移動過程中父布局的移動,導(dǎo)致獲取的坐標(biāo)左右抖動,會出現(xiàn)移動過程中左右一直抖動現(xiàn)象
val x = event.rawX.toInt()
val y = event.rawY.toInt()
// X方向位移
val offsetX = x - mDownX
if (!mSlideContainerLayout.isSlideShow){
// Container滑塊兒沒滑出來不分發(fā)事件
return false
}
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// 記錄按下點(diǎn)坐標(biāo)
mDownX = x
mDownY = y
mSlideContainerLayout.setDownXY(mDownX,mDownY)
}
MotionEvent.ACTION_MOVE -> if (abs(x - mDownX) < abs(y - mDownY) && paddingLeft < x) { // 上下滑動情況處理
if (isSlideHorizontal) {
return mSlideContainerLayout.dispatchTouchEvent(event)
}
} else if ( offsetX < 0 && mSlideContainerLayout.isAlignLeftSide()) {
// 向左滑動,滑塊兒已經(jīng)靠最左邊了,不分發(fā)
return super.dispatchTouchEvent(event)
} else if (abs(x - mDownX) > abs(y - mDownY)){
// 水平方向移動,分發(fā)事件
isSlideHorizontal = true
return mSlideContainerLayout.dispatchTouchEvent(event)// 事件傳遞給Container處理
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL ->{
// 抬起時處理
if (offsetX < 0 && mSlideContainerLayout.isAlignLeftSide()){
return super.dispatchTouchEvent(event)
}
if (abs(x - mDownX) > abs(y - mDownY) || isSlideHorizontal){
isSlideHorizontal = false
return mSlideContainerLayout.dispatchTouchEvent(event)
}
isSlideHorizontal = false
}
}
return super.dispatchTouchEvent(event)
}
3.2 滑動沖突
因?yàn)榉块g是可以上下滑動的,所以可以判斷如果滑塊兒沒滑粗來時,直接返回分發(fā),不讓RightSlider和Container處理事件
if (!mSlideContainerLayout.isSlideShow){
return false
}
然后滑塊兒滑出來以后,因?yàn)槔镞呌辛斜?,所以要消費(fèi)上下滑動事件,可以處理如下:
MotionEvent.ACTION_MOVE -> if (abs(x - mDownX) < abs(y - mDownY) && paddingLeft < x) {
if (isSlideHorizontal) {
return mSlideContainerLayout.dispatchTouchEvent(event)
}
}
其中paddingLeft < x 是因?yàn)榛瑝K左邊有一部分空白區(qū)域 paddingLeft ,所以當(dāng)x坐標(biāo)在此區(qū)域右側(cè)時才處理事件
Container動畫執(zhí)行過程中,說明正在消費(fèi)事件,此時禁止父層攔截事件
if (mClearAnimator.isRunning || mSlideInAnimator.isRunning || isSlideShow) {
// 滑入情況下,禁止上下滑切換直播間
parent.requestDisallowInterceptTouchEvent(true)
}
Container處理事件時候和直播間上的進(jìn)入房間頭像列表沖突,解決方法是判斷mDownY 大于進(jìn)入頭像列表高度時才處理事件,因?yàn)檎H嘶牖瑝K都是在屏幕中下部操作的,所以太靠上的部分不處理事件也可以接受
MotionEvent.ACTION_MOVE -> {
if (!mClearAnimator.isRunning && mDownY > 200 && abs(x - mDownX) > abs(y - mDownY)) {
// 清屏不在執(zhí)行時 && 高度大于200dp(解決進(jìn)入房間頭像滑動沖突)&& 橫向滑動時攔截事件
if (abs(x - mDownX) > 10) {
return true
}
}
}
3.3 滑動優(yōu)化
這部分有很多細(xì)節(jié)處理的地方,包括動畫執(zhí)行到一半情況下,再次左右滑動,先向左后向右,左右滑一半再上下滑等等各種情況具體可以看代碼中SlideContainerLayout中onTouchEvent方法內(nèi)處理邏輯,都添加了注釋
override fun onTouchEvent(event: MotionEvent): Boolean {
mVelocityTracker!!.addMovement(event)
val x = event.rawX.toInt()
val offsetX = x - mDownX
if (mLastOffsetList.size > 2){
mLastOffsetList.removeFirst()
}
mLastOffsetList.add(offsetX)
var slideRight = (offsetX - mLastOffsetList.first) > 0
when (event.action) {
MotionEvent.ACTION_MOVE -> {
if ((isSlideShow) && offsetX > 0 && mSlideInAnimator.isRunning && !isSliderGoning) {
// 滑入情況下,向右滑一段松開,再向右滑,清除回彈動畫,跟隨手勢
mSlideInAnimator.cancel()
translateSlideView(offsetX)
}
if ((isSlideShow) && offsetX > 0 && !mSlideInAnimator.isRunning) {
// 滑入情況下,向右滑,跟隨手勢
translateSlideView(offsetX)
}
return true
}
MotionEvent.ACTION_UP -> {
mVelocityTracker!!.computeCurrentVelocity(10)
if (isSlideShow && offsetX > 0 && abs(offsetX) > width / 3 && !isSliderGoning && mVelocityTracker!!.xVelocity >= 0) {
// 滑入情況下,向右滑距離超過寬度1/3,滑出滑塊
startX = offsetX
endX = width - mSlideView!!.paddingLeft
isSliderGoning = true
mSlideInAnimator.start()
return true
}
if (abs(mVelocityTracker!!.xVelocity) > 1) {
if (isCleared && offsetX < 0) {
// 清屏情況下,左滑速度超過10個像素時 ===》滑入清屏控件
layerShowWithAnim()
} else if (!isCleared && offsetX > 0 && !isSlideShow && !mSlideInAnimator.isRunning) {
// 未清屏 && 向右速度 > 10 && 沒滑入滑塊 && 滑塊動畫沒執(zhí)行的時候 ===》清屏
layerGoneWithAnim()
} else if (isSlideShow && offsetX > 0 && slideRight) {
// 滑入情況下 && 向右速度 > 10 ===》滑出滑塊
mSlideInAnimator.cancel()
isSliderGoning = true
startX = translateX
endX = width - mSlideView!!.paddingLeft
mSlideInAnimator.start()
} else if (isSlideShow && offsetX < 0 && translateX != 0) {
// 滑入情況下 && 向左速度 > 10 && 已經(jīng)向右滑動了一段距離 ===》 滑塊回彈
startX = translateX
endX = 0
mSlideInAnimator.start()
} else if (!isSlideShow && offsetX < 0 && !mSlideInAnimator.isRunning) {
// 沒滑入情況下 && 向左滑速度 > 10 && 沒右正在滑入情況下 ===》 滑入滑塊
sliderShowWithAnim()
} else {
if (isSlideShow && translateX != 0) {
// 滑入情況下 && 已經(jīng)向右滑動過,速度沒達(dá)到松開 ===》回彈
startX = translateX
mSlideInAnimator.start()
}
}
}else {
if (isSlideShow && translateX != 0) {
// 滑入情況下 && 已經(jīng)向右滑動過,速度沒達(dá)到松開 ===》回彈
startX = translateX
mSlideInAnimator.start()
}
}
return super.onTouchEvent(event)
}
MotionEvent.ACTION_CANCEL -> {
if (isSlideShow) {
//取消事件時,滑入情況下回彈
startX = translateX
mSlideInAnimator.start()
}
}
}
return super.onTouchEvent(event)
}
總結(jié)
最后通過這次實(shí)踐,感觸比較深的是功能實(shí)現(xiàn)之前,一定要做好充分的調(diào)研,研究好需求的細(xì)節(jié),并預(yù)先想幾種實(shí)現(xiàn)策略,對比哪一種更合理。不要埋頭就寫,結(jié)果最后發(fā)現(xiàn)不符合需求還要重構(gòu)
感謝,這里Contanier內(nèi)的邏輯主要參考了gitHub上[這篇文章](https://github.com/lmxjw3/clearscreen )的處理不過里邊處理滑動沖突的邏輯比較少還是要自己結(jié)合項(xiàng)目處理
奉上GitHub 項(xiàng)目地址
總結(jié)
到此這篇關(guān)于Android仿抖音右滑清屏左滑列表功能的實(shí)現(xiàn)代碼的文章就介紹到這了,更多相關(guān)android 抖音右滑清屏左滑列表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android項(xiàng)目基本結(jié)構(gòu)詳解
這篇文章主要為大家詳細(xì)介紹了Android項(xiàng)目基本結(jié)構(gòu),從最基本的內(nèi)容講起,帶你逐步進(jìn)入用C#進(jìn)行Android應(yīng)用開發(fā)的樂園,感興趣的小伙伴們可以參考一下2016-06-06
Android編程開發(fā)之TextView文字顯示和修改方法(附TextView屬性介紹)
這篇文章主要介紹了Android編程開發(fā)之TextView文字顯示和修改方法,結(jié)合實(shí)例詳細(xì)分析了Android中TextView控件關(guān)于文字的顯示及修改技巧,并附帶了TextView屬性介紹,需要的朋友可以參考下2015-12-12
android獲取當(dāng)前運(yùn)行Activity名字的方法
這篇文章主要介紹了android獲取當(dāng)前運(yùn)行Activity名字的方法,對比分析了兩種實(shí)現(xiàn)方法供大家選擇,具有一定參考借鑒價值,需要的朋友可以參考下2015-01-01
Android實(shí)現(xiàn)手指觸控圖片縮放功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)手指觸控圖片縮放功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
Android利用Badge組件實(shí)現(xiàn)未讀消息小紅點(diǎn)
在?App?的運(yùn)營中,活躍度是一個重要的指標(biāo),日活/月活……為了提高活躍度,就發(fā)明了小紅點(diǎn)。這一篇,來介紹一個徽標(biāo)(Badge)組件,能夠快速搞定應(yīng)用內(nèi)的小紅點(diǎn),希望對大家有所幫助2023-01-01
Android自定義布局實(shí)現(xiàn)仿qq側(cè)滑部分代碼
這篇文章主要為大家詳細(xì)介紹了自定義布局實(shí)現(xiàn)仿qq側(cè)滑Android部分代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03
創(chuàng)建子線程對Android進(jìn)行網(wǎng)絡(luò)訪問
這篇文章介紹了Android中創(chuàng)建子線程進(jìn)行網(wǎng)絡(luò)訪問的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考2021-11-11
Android提高之Android手機(jī)與BLE終端通信
這篇文章主要介紹了Android手機(jī)與BLE終端通信的方法,有很大的實(shí)用價值,需要的朋友可以參考下2014-08-08
Android RecyclerView的焦點(diǎn)記憶封裝
這篇文章主要介紹了Android RecyclerView的焦點(diǎn)記憶封裝,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04

