這篇文章主要用代碼解析Android如何實(shí)現(xiàn)仿抖音右滑清屏左滑列表功能,內(nèi)容簡(jiǎn)而易懂,希望大家可以學(xué)習(xí)一下,學(xué)習(xí)完之后肯定會(huì)有收獲的,下面讓小編帶大家一起來(lái)看看吧。
企業(yè)建站必須是能夠以充分展現(xiàn)企業(yè)形象為主要目的,是企業(yè)文化與產(chǎn)品對(duì)外擴(kuò)展宣傳的重要窗口,一個(gè)合格的網(wǎng)站不僅僅能為公司帶來(lái)巨大的互聯(lián)網(wǎng)上的收集和信息發(fā)布平臺(tái),創(chuàng)新互聯(lián)公司面向各種領(lǐng)域:成都砂巖浮雕等網(wǎng)站設(shè)計(jì)、成都全網(wǎng)營(yíng)銷推廣解決方案、網(wǎng)站設(shè)計(jì)等建站排名服務(wù)。
概述
項(xiàng)目中要實(shí)現(xiàn)仿抖音直播間滑動(dòng)清屏,側(cè)滑列表的功能,在此記錄下實(shí)現(xiàn)過(guò)程和踩坑記錄希望避免大家走些彎路,也當(dāng)作自己的一個(gè)總結(jié)
首先看下Demo中的效果
閱讀文章需要提前熟悉些事件分發(fā)的內(nèi)容,相信大家都已經(jīng)了解過(guò)了
關(guān)于這方面的知識(shí),在Android中是再重要不過(guò)的了,是遲早都要掌握的知識(shí),所以還是希望大家都能提早掌握,最好可以跟著源碼一起分析,理解掌握的更深刻一點(diǎn)
實(shí)踐
所以網(wǎng)上基于這部分內(nèi)容講解已經(jīng)很詳細(xì)了,這里就不再搬磚了,主要分享一下自己項(xiàng)目中結(jié)合這部分知識(shí)運(yùn)用過(guò)程中產(chǎn)生的一些想法和經(jīng)驗(yàn),解決的一些bug
以上就是功能在實(shí)現(xiàn)過(guò)程中要解決的問(wèn)題,下面詳細(xì)展開(kāi)
1. 布局結(jié)構(gòu)
布局結(jié)構(gòu)始終是界面設(shè)計(jì)時(shí)首先要考慮的一個(gè)問(wèn)題,從接到一個(gè)需求開(kāi)始,首先要根據(jù)項(xiàng)目中現(xiàn)有的布局結(jié)構(gòu),考慮如何更優(yōu)雅的嵌入布局層次。如果一不小心,走上了錯(cuò)誤的實(shí)現(xiàn)道路,那么不好意思,即使功能最后實(shí)現(xiàn)了,到了后期,也有千萬(wàn)種理由迫使你不得不走上重構(gòu)的道路。
比如實(shí)現(xiàn)不合理,導(dǎo)致的布局結(jié)構(gòu)復(fù)雜,嵌套冗余層次,比如代碼業(yè)務(wù)邏輯處理復(fù)雜蹩腳,比如資源浪費(fèi),內(nèi)存消耗過(guò)多等等。雖然功能好使,使用起來(lái)也沒(méi)有差別,但是,作為一個(gè)有追求的程序員,我們還是要避免這種情況的發(fā)生不是嗎
不巧的是,本文就屬于上述踩坑記錄,下面詳細(xì)分析
1.1 初步實(shí)現(xiàn)
上來(lái)以后,思路很直接明了的去想要實(shí)現(xiàn)清屏和滑屏的功能是每個(gè)房間都有的功能,每個(gè)房間又都是一個(gè)RecyclerView 的一個(gè)Item。所以,很明顯在Item的布局上包一層,實(shí)現(xiàn)清屏和側(cè)滑列表的功能就可以了,這樣每個(gè)房間都可以上下滑,切換房間。切換以后,滑屏的功能是在每個(gè)房間里的,互不影響,所以很好理解
我們項(xiàng)目中實(shí)現(xiàn)直播間上下滑切換的功能是RecyclerView + 自定義LinearLayoutManager實(shí)現(xiàn)的,這部分內(nèi)容網(wǎng)上demo很多,就不展開(kāi)了
具體實(shí)施,是自定義布局繼承RelativeLayout,解析自定義的布局文件,里面包含,直播間的房間布局,和自己右側(cè)滑塊兒布局,然后用自己實(shí)現(xiàn)的布局替換之前的房間Item布局位置
這樣我們調(diào)用封裝的Container將清屏控件,和右側(cè)滑塊兒布局View分別添加到內(nèi)部即可
API提供如下
// 添加需要清屏的view fun addClearViews(vararg views: View?) // 添加需要滑入的view fun addSlideView(view: RightSlideLayout)
這樣我們?cè)谝曨l播放頁(yè)面滑動(dòng),就可以在Container內(nèi)判斷手勢(shì),處理清屏控件或者滑出右側(cè)滑塊兒了
右側(cè)滑塊再動(dòng)態(tài)加載Fragment,展示列表布局,基本完成功能效果了
1.2 重構(gòu)
本來(lái)以為開(kāi)開(kāi)心心的可以上線了,誰(shuí)知到下邊繼續(xù)體驗(yàn)和對(duì)比抖音到過(guò)程中還是發(fā)現(xiàn)不足:
第一個(gè)是,右側(cè)滑塊兒(后邊稱RightSlider)包含在房間,這樣上下切換房間(后邊稱Container),RightSlider布局也會(huì)隨著Container新建而新建,雖然有RecyclerView的布局緩存,但是至少也會(huì)新建Holder幾次,造成資源的浪費(fèi)。第二個(gè)是,RightSlider的新建就會(huì)導(dǎo)致里邊的Fragment的新建,所以又會(huì)重新請(qǐng)求加載列表數(shù)據(jù),再次造成資源浪費(fèi),而且,新建后右側(cè)列表又會(huì)重新頂?shù)筋^,之前滑動(dòng)過(guò)的距離就會(huì)丟失。這樣就造成,用戶從右側(cè)列表點(diǎn)擊切換房間后,再次滑出RightSlider切換房間,發(fā)現(xiàn)又要從頭開(kāi)始往下滑,這樣肯定不符合用戶體驗(yàn)。觀察抖音列表后發(fā)現(xiàn),每次滑動(dòng)到固定位置點(diǎn)擊Item切換房間后,再次滑出滑塊兒,發(fā)現(xiàn)列表還是之前的位置,好像跟之前滑出的是一個(gè)滑塊兒的效果,于是恍然大悟,滑塊兒是跟Activity綁定的,也就是要把RightSlider放在跟Activity布局那一層
其實(shí)提出RightSlider到外層的過(guò)程中,還是走了不少?gòu)澛?,因?yàn)橹爱吘挂呀?jīng)實(shí)現(xiàn)好的邏輯,如果改動(dòng)布局結(jié)構(gòu),肯定要重寫滑動(dòng)沖突、事件分發(fā)這部分代碼,工作量又不可預(yù)計(jì)。所以想著能不能不動(dòng)布局結(jié)構(gòu)的情況下實(shí)現(xiàn)仿抖音效果
動(dòng)態(tài)替換Fragment
首先想到的是滑出RightSlider里的列表每次都好像是同一個(gè),那么保證里邊的Fragment是同一個(gè)不就好了,滑出的滑塊兒雖然不同,但是里邊裝載的Fragment列表是同一個(gè),這樣就營(yíng)造出同一個(gè)滑塊兒的效果。
但是實(shí)現(xiàn)過(guò)程中還是出現(xiàn)了問(wèn)題,由于RecyclerView的預(yù)加載功能,導(dǎo)致我們項(xiàng)目中,從第一個(gè)房間上滑到下一個(gè)房間,過(guò)程中會(huì)新建兩個(gè)Holder,這樣Fragment替換就出了問(wèn)題,切換房間后Fragment添加不上去,折騰一下午后最終放棄這個(gè)方案
固定List高度
然后想的,既然Fragment替換不了了,那么RecyclerView肯定不是同一個(gè)了,如果點(diǎn)擊后記錄當(dāng)前RecyclerView滑動(dòng)的位置,下次滑出時(shí),代碼固定到當(dāng)前位置不是也可以偽造出同一個(gè)滑塊兒的效果嘛,這部分也去找了一些資料,實(shí)現(xiàn)了個(gè)小demo。其中用到的主要方法是
/** * 獲取滑動(dòng)距離 */ fun getScollYDistance(): Int { // 獲取recyclerview 的layoutManager val layoutManager = recyclerView.layoutManager as LinearLayoutManager // 獲取當(dāng)前第一個(gè)可見(jiàn)View的位置 val position = layoutManager.findFirstVisibleItemPosition() // 根據(jù)position 獲取當(dāng)前View val firstVisiableChildView = layoutManager.findViewByPosition(position) // 獲取當(dāng)前View 高度 val itemHeight = firstVisiableChildView.height // 滑動(dòng)距離 return position * itemHeight - firstVisiableChildView.top }
滑動(dòng)距離計(jì)算的思想是:根據(jù)當(dāng)前可見(jiàn)View 的position * 每個(gè)ItemView 的高度 + 當(dāng)前View已經(jīng)滑出去的部分
計(jì)算出高度后,每次加載時(shí),調(diào)用RecyclerView的API
recyclerView.scrollBy(0,scroll) //scroll 剛才計(jì)算的高度
還有其他幾個(gè)滑動(dòng)的方法:
// 帶動(dòng)畫移動(dòng)距離 public void smoothScrollBy(int dx, int dy) // 帶動(dòng)畫移動(dòng)到position public void smoothScrollToPosition(int position) // 移動(dòng)到adapter position ,由LayoutManager實(shí)現(xiàn) public void scrollToPosition(int position) // 空實(shí)現(xiàn),無(wú)效 public void scrollTo(int x, int y)
原理上可以實(shí)現(xiàn),但是最后綜合比較還是放棄了這種方式,因?yàn)榭偢杏X(jué)這種方法屬于投機(jī)取巧不是正道,還是老老實(shí)實(shí)將RightSlider 提到外面得了
2. 動(dòng)畫
動(dòng)畫也是這個(gè)功能中很重要的一個(gè)方面,因?yàn)閯?dòng)畫效果的流暢直接影響了用戶體驗(yàn),所以這方面也是細(xì)扣了很久。首先這個(gè)功能主要分成三個(gè)動(dòng)畫效果:
2.1 進(jìn)場(chǎng)出場(chǎng)
包含清屏控件入場(chǎng)、出場(chǎng):
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 } })
這里使用了屬性動(dòng)畫ValueAnimator,其中 translateClearChild 負(fù)責(zé)移動(dòng)View 代碼如下:
/** * 移動(dòng)清屏控件 */ private fun translateClearChild(translate: Int) { for (i in mClearViews.indices) { mClearViews[i].translationX = translate.toFloat() } }
滑塊兒的入場(chǎng)、出場(chǎng):
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 分別代表入場(chǎng)和出場(chǎng)時(shí)候,動(dòng)畫起止位置。由于清屏控件沒(méi)有中間位置狀態(tài),直接是從0 到屏幕寬度兩個(gè)值之間替換;而滑塊兒中間由于要跟隨手勢(shì)移動(dòng),所以要記錄中間translateX,標(biāo)記為startX
2.2 跟隨手勢(shì)
跟隨手勢(shì)實(shí)現(xiàn)主要是攔截移動(dòng)手勢(shì),根據(jù)按下手勢(shì)位置坐標(biāo)和Move移動(dòng)位置坐標(biāo)的差值,調(diào)用移動(dòng)SliderView的方法
val x = event.rawX.toInt() // 標(biāo)記移動(dòng)距離 val offsetX = x - mDownX when (event.action) { MotionEvent.ACTION_MOVE -> { if ((isSlideShow) && offsetX > 0 && mSlideInAnimator.isRunning && !isSliderGoning) { // 滑入情況下,向右滑一段松開(kāi),再向右滑,清除回彈動(dòng)畫,跟隨手勢(shì) mSlideInAnimator.cancel() translateSlideView(offsetX) } if ((isSlideShow) && offsetX > 0 && !mSlideInAnimator.isRunning) { // 滑入情況下,向右滑,跟隨手勢(shì) translateSlideView(offsetX) } return true } }
2.3 顏色漸變
跟隨手勢(shì)滑動(dòng)過(guò)程中還伴隨的左側(cè)空白區(qū)域顏色漸變,這部分可以在RightSlider移動(dòng)過(guò)程中的距離值關(guān)聯(lián)起來(lái),設(shè)置起始顏色透明和截止顏色灰色蒙層。再根據(jù)距離動(dòng)態(tài)算出當(dāng)前顏色在區(qū)間范圍內(nèi)取值,主要代碼邏輯如下
/** * 移動(dòng)滑塊兒 */ private fun translateSlideView(translate: Int) { val percent = (mSlideView!!.width.toFloat() - translate) / mSlideView!!.width // 根據(jù)百分比算出色值 val color = (MASK_DARK_COLOR * percent).toInt() shl 24 // 動(dòng)態(tài)設(shè)置背景色漸變 mBgColorView.setBackgroundColor(color) translateX = translate mSlideView!!.translationX = translate.toFloat() }
3 事件分發(fā)
這部分可以說(shuō)是本功能實(shí)現(xiàn)的核心,也是耗費(fèi)了相當(dāng)時(shí)間的精力,從最開(kāi)始的Container包含RightSlider布局處理經(jīng)典的事件分發(fā)順序,到最后重構(gòu)布局,將RightSlider提到外層變成不是包含關(guān)系,而是并列或者說(shuō)是覆蓋關(guān)系,中間對(duì)事件傳遞的順序理解又深入了一層
3.1 傳遞順序
重構(gòu)之前的布局結(jié)構(gòu)是每個(gè)Container包含了一個(gè)RightSlider,兩個(gè)是一個(gè)整體使用的,滑動(dòng)的邏輯都可以在Container層內(nèi)的onInterceptTouchEvent方法內(nèi)處理。判斷是否攔截事件即可,然后RightSlider內(nèi)想要禁止父層Container攔截事件,可以使用parent.requestDisallowInterceptTouchEvent(true)禁止父層攔截;是屬于經(jīng)典模式的事件分發(fā)模型,事件分發(fā)的順序在一個(gè)U型結(jié)構(gòu)里,比較好處理
然后重構(gòu)以后布局結(jié)構(gòu)變成了如下圖所示
每個(gè)Container 共用一個(gè)RightSlider,這樣屬于事件的分發(fā)處理不在一個(gè)ViewGroup的U型模型里了,這樣的分發(fā)順序也是屬于自己的一個(gè)大膽嘗試,想著實(shí)在不行,還是要把Activity內(nèi)布局包一層,將Container和RightSlider 放在一個(gè)U型結(jié)構(gòu)里去處理。
還好最后不斷踩坑,終于實(shí)現(xiàn)了事件從Activity分發(fā),到RightSlider,再分發(fā)到Container的過(guò)程
這里貼下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>
其中做了部分簡(jiǎn)化,主要幫助大家理解布局層次
然后貼下RightSlider核心分發(fā)代碼:
override fun dispatchTouchEvent(event: MotionEvent): Boolean { // 獲取坐標(biāo),這里用rawX 相對(duì)屏幕絕對(duì)位置,不然隨手勢(shì)移動(dòng)過(guò)程中父布局的移動(dòng),導(dǎo)致獲取的坐標(biāo)左右抖動(dòng),會(huì)出現(xiàn)移動(dòng)過(guò)程中左右一直抖動(dòng)現(xiàn)象 val x = event.rawX.toInt() val y = event.rawY.toInt() // X方向位移 val offsetX = x - mDownX if (!mSlideContainerLayout.isSlideShow){ // Container滑塊兒沒(méi)滑出來(lái)不分發(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) { // 上下滑動(dòng)情況處理 if (isSlideHorizontal) { return mSlideContainerLayout.dispatchTouchEvent(event) } } else if ( offsetX < 0 && mSlideContainerLayout.isAlignLeftSide()) { // 向左滑動(dòng),滑塊兒已經(jīng)靠最左邊了,不分發(fā) return super.dispatchTouchEvent(event) } else if (abs(x - mDownX) > abs(y - mDownY)){ // 水平方向移動(dòng),分發(fā)事件 isSlideHorizontal = true return mSlideContainerLayout.dispatchTouchEvent(event)// 事件傳遞給Container處理 } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL ->{ // 抬起時(shí)處理 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 滑動(dòng)沖突
因?yàn)榉块g是可以上下滑動(dòng)的,所以可以判斷如果滑塊兒沒(méi)滑粗來(lái)時(shí),直接返回分發(fā),不讓RightSlider和Container處理事件
if (!mSlideContainerLayout.isSlideShow){ return false }
然后滑塊兒滑出來(lái)以后,因?yàn)槔镞呌辛斜?,所以要消費(fèi)上下滑動(dòng)事件,可以處理如下:
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è)時(shí)才處理事件
Container動(dòng)畫執(zhí)行過(guò)程中,說(shuō)明正在消費(fèi)事件,此時(shí)禁止父層攔截事件
if (mClearAnimator.isRunning || mSlideInAnimator.isRunning || isSlideShow) { // 滑入情況下,禁止上下滑切換直播間 parent.requestDisallowInterceptTouchEvent(true) }
Container處理事件時(shí)候和直播間上的進(jìn)入房間頭像列表沖突,解決方法是判斷mDownY 大于進(jìn)入頭像列表高度時(shí)才處理事件,因?yàn)檎H嘶牖瑝K都是在屏幕中下部操作的,所以太靠上的部分不處理事件也可以接受
MotionEvent.ACTION_MOVE -> { if (!mClearAnimator.isRunning && mDownY > 200 && abs(x - mDownX) > abs(y - mDownY)) { // 清屏不在執(zhí)行時(shí) && 高度大于200dp(解決進(jìn)入房間頭像滑動(dòng)沖突)&& 橫向滑動(dòng)時(shí)攔截事件 if (abs(x - mDownX) > 10) { return true } } }
3.3 滑動(dòng)優(yōu)化
這部分有很多細(xì)節(jié)處理的地方,包括動(dòng)畫執(zhí)行到一半情況下,再次左右滑動(dòng),先向左后向右,左右滑一半再上下滑等等各種情況具體可以看代碼中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) { // 滑入情況下,向右滑一段松開(kāi),再向右滑,清除回彈動(dòng)畫,跟隨手勢(shì) mSlideInAnimator.cancel() translateSlideView(offsetX) } if ((isSlideShow) && offsetX > 0 && !mSlideInAnimator.isRunning) { // 滑入情況下,向右滑,跟隨手勢(shì) translateSlideView(offsetX) } return true } MotionEvent.ACTION_UP -> { mVelocityTracker!!.computeCurrentVelocity(10) if (isSlideShow && offsetX > 0 && abs(offsetX) > width / 3 && !isSliderGoning && mVelocityTracker!!.xVelocity >= 0) { // 滑入情況下,向右滑距離超過(guò)寬度1/3,滑出滑塊 startX = offsetX endX = width - mSlideView!!.paddingLeft isSliderGoning = true mSlideInAnimator.start() return true } if (abs(mVelocityTracker!!.xVelocity) > 1) { if (isCleared && offsetX < 0) { // 清屏情況下,左滑速度超過(guò)10個(gè)像素時(shí) ===》滑入清屏控件 layerShowWithAnim() } else if (!isCleared && offsetX > 0 && !isSlideShow && !mSlideInAnimator.isRunning) { // 未清屏 && 向右速度 > 10 && 沒(méi)滑入滑塊 && 滑塊動(dòng)畫沒(méi)執(zhí)行的時(shí)候 ===》清屏 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)向右滑動(dòng)了一段距離 ===》 滑塊回彈 startX = translateX endX = 0 mSlideInAnimator.start() } else if (!isSlideShow && offsetX < 0 && !mSlideInAnimator.isRunning) { // 沒(méi)滑入情況下 && 向左滑速度 > 10 && 沒(méi)右正在滑入情況下 ===》 滑入滑塊 sliderShowWithAnim() } else { if (isSlideShow && translateX != 0) { // 滑入情況下 && 已經(jīng)向右滑動(dòng)過(guò),速度沒(méi)達(dá)到松開(kāi) ===》回彈 startX = translateX mSlideInAnimator.start() } } }else { if (isSlideShow && translateX != 0) { // 滑入情況下 && 已經(jīng)向右滑動(dòng)過(guò),速度沒(méi)達(dá)到松開(kāi) ===》回彈 startX = translateX mSlideInAnimator.start() } } return super.onTouchEvent(event) } MotionEvent.ACTION_CANCEL -> { if (isSlideShow) { //取消事件時(shí),滑入情況下回彈 startX = translateX mSlideInAnimator.start() } } } return super.onTouchEvent(event) }
以上就是關(guān)于用代碼解析Android如何實(shí)現(xiàn)仿抖音右滑清屏左滑列表功能的內(nèi)容,如果你們有學(xué)習(xí)到知識(shí)或者技能,可以把它分享出去讓更多的人看到。
當(dāng)前文章:用代碼解析Android如何實(shí)現(xiàn)仿抖音右滑清屏左滑列表功能
地址分享:http://aaarwkj.com/article14/gpiide.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信小程序、外貿(mào)建站、網(wǎng)站設(shè)計(jì)、網(wǎng)站營(yíng)銷、網(wǎng)站改版、營(yíng)銷型網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)