欧美一级特黄大片做受成人-亚洲成人一区二区电影-激情熟女一区二区三区-日韩专区欧美专区国产专区

用代碼解析Android如何實(shí)現(xiàn)仿抖音右滑清屏左滑列表功能

這篇文章主要用代碼解析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中的效果

用代碼解析Android如何實(shí)現(xiàn)仿抖音右滑清屏左滑列表功能

閱讀文章需要提前熟悉些事件分發(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

用代碼解析Android如何實(shí)現(xiàn)仿抖音右滑清屏左滑列表功能

以上就是功能在實(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布局位置

用代碼解析Android如何實(shí)現(xiàn)仿抖音右滑清屏左滑列表功能

  • 由于我們自定義的Container布局是繼承子RelativeLayout實(shí)現(xiàn)的,內(nèi)部三個(gè)子View 又全部是占滿父布局的,所以就是三層覆蓋的效果,類似抖音直播間效果
  •  這里我們盡量將覆蓋層/清屏控件,封裝成一個(gè)ViewGroup 內(nèi)部包含了上邊細(xì)分的各個(gè)子View,例如頭部個(gè)人信息,頭像列表等等;中間彈幕,SVGA禮物展示區(qū)域;底部聊天評(píng)論區(qū)域方便管理
  • 還有右側(cè)滑塊我們也做成繼承自RelativeLayout形式,解析自定義布局,方便擴(kuò)展

 這樣我們調(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)滑出去的部分

用代碼解析Android如何實(shí)現(xiàn)仿抖音右滑清屏左滑列表功能

計(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)變成了如下圖所示

用代碼解析Android如何實(shí)現(xiàn)仿抖音右滑清屏左滑列表功能

每個(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)

外貿(mào)網(wǎng)站制作
国产爆操美女在线观看| 国产国产成人精品久久| 雪白肥臀视频一区二区三区| 蜜臀久久精品国产综合| 午夜性生活视频免费看| 中文字幕日韩精品亚洲精品| 亚洲精品不卡一区二区| 久久国产欧美日韩精品| 手机在线看国产后入| 国产毛片一区二区在线 | 成人av久久一区二区三区| 国产女主播在线观看一区| 日本国产一区二区三区在线观看 | 久久精品国产亚洲av高清观看| 日韩精品亚洲一区二区三区免费| 亚洲av香蕉综合一区| 亚洲邻家人妻一区二区| 婷婷色中文字幕综合在线| 日韩亚洲av在线免费观看| 特色特色欧美黄色影院| 香蕉夜夜草草久久亚洲香蕉| 五月婷婷色丁香综合激情| 亚洲av毛片免费在线| 国产伦理免费精品中文字幕| 亚洲成av人亚洲av| 亚洲综合色一区二区三区四区| 欧美一区二区三区人妻熟妇| 亚洲男人天堂在线视频| 久久精品熟女亚洲av韩国| 最新日本免费久久精品| 91一区二区三区在线| 97碰碰视频在线观看| 亚洲欧美另类熟女丝袜| av天堂网站在线观看| 亚洲男人的天堂久久精品| 超碰欧美黄色免费在线| 激情亚洲欧美日韩精品| 女性裸体无遮挡啪啪网站| 亚洲国产精品自拍视频| 亚洲视频一区视频二区| 国产性生活大片免费看|