最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android自定義view實現列表內左滑刪除Item

 更新時間:2023年02月09日 09:55:07   作者:撿一晌貪歡  
這篇文章主要介紹了微信小程序列表中item左滑刪除功能,本文分步驟給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下

前言

上一篇文章自定義了一個左滑刪除的RecyclerView,把view事件分發(fā)三個函數dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent實際運用了一下,一些原理通過出現的bug還是挺能加深印象,并且后面還在優(yōu)化上用上了TouchSlop、VelocityTracker以及GestureDetector,但是真不配那個一個控件搞定安卓自定義view,所以我把上篇博客標題改了,并且希望在接下來的時間里,通過幾個自定義view較全面的去學習自定義view的相關知識,話不多說,下面開始1

需求

上篇文章通過RecyclerView去實現了一個左滑的效果,后面突發(fā)奇想,既然能通過列表去實現item的左滑,那能不能通過item自己去實現左滑呢?這樣我們把item內容寫在自定義的layout里面就可以實現左滑了,聽起來挺方便,于是就動手做了,少說多做總還是好的。

有了第一篇的內容,item的左滑還是簡單多了,主要就是讓item跟隨滑動,右邊自動添加一個刪除按鈕就夠了吧,開始我是這么想的,并總結了三點核心思想:

  • 一個容器,左右兩部分,左邊外部導入,右邊刪除框自動增加
  • 在 View 右邊追加一個刪除框 ,需要在 View 內攔截事件,根據 x 軸滑動距離滑動
  • 在 ConstraintLayout 內部添加一個刪除框,左邊對其 parent 右邊

這里取巧了一下,繼承的 ConstraintLayout,這樣讓添加的刪除框對齊 ConstraintLayout的右邊就行了。

運行效果

編寫代碼

代碼不多,就直接上代碼了,注釋寫的很詳細,后面再提下出現的主要問題:

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.os.Build
import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.widget.Scroller
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.constraintlayout.widget.ConstraintLayout
import kotlin.math.abs
/**
 * 左劃刪除控件
 * 能在控件實現左滑嗎?如何傳入自定義的布局?
 * 思路:
 * 1、一個容器,左右兩部分,左邊外部導入,右邊刪除框 x 增加層級
 * 2、在 View 右邊追加一個刪除款 x 需要在 View 內攔截事件
 * 3、在 ConstraintLayout 內部添加一個刪除框,左邊對其 parent 右邊
 *
 * @author silence
 * @date 2022-09-27
 */
class LeftDeleteItemLayout : ConstraintLayout {
    private val mDeleteView: View?
    var mDeleteClickListener: OnClickListener? = null
    //流暢滑動
    private var mScroller = Scroller(context)
    //上次事件的橫坐標
    private var mLastX = -1f
    //控制控件結束的runnable
    private val stopMoveRunnable: Runnable = Runnable { stopMove() }
    constructor(context: Context) : this(context, null, 0)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
            super(context, attrs, defStyleAttr)
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
            super(context, attrs, defStyleAttr, defStyleRes)
    init {
        //kotlin的初始化函數
        mDeleteView = makeDeleteView(context)
        addView(mDeleteView)
    }
    //創(chuàng)建刪除框,設置好位置對齊自身最右邊
    private fun makeDeleteView(context: Context): View {
        val deleteView = TextView(context)
        //給當前控件一個id,用于刪除控件約束
        this.id = generateViewId()
        //設置布局參數
        deleteView.layoutParams = LayoutParams(
            dp2px(context, 100f), 0
        ).apply {
            //設置約束條件
            leftToRight = id
            topToTop = id
            bottomToBottom = id
        }
        //設置其他參數
        deleteView.text = "刪除"
        deleteView.gravity = Gravity.CENTER
        deleteView.setTextColor(Color.WHITE)
        deleteView.textSize = sp2px(context,18f).toFloat()
        deleteView.setBackgroundColor(Color.RED)
        //設置點擊回調
        deleteView.setOnClickListener(mDeleteClickListener)
        return deleteView
    }
    //攔截事件
    override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
        event?.let {
            when(event.action) {
                //down事件記錄x,不攔截,當move的時候才會用到
                MotionEvent.ACTION_DOWN -> mLastX = event.x
                //攔截本控件內的移動事件
                MotionEvent.ACTION_MOVE -> return true
            }
        }
        return super.onInterceptTouchEvent(event)
    }
    //處理事件
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        event?.let {
            when(event.action) {
                MotionEvent.ACTION_MOVE -> moveItem(event)
                MotionEvent.ACTION_UP -> stopMove()
            }
        }
        return super.onTouchEvent(event)
    }
    private fun moveItem(e: MotionEvent) {
        //Log.e("TAG", "moveItem: mLastX=$mLastX")
        //如果沒有收到down事件,不應該移動
        if (mLastX == -1f) return
        val dx = mLastX - e.x
        //更新點擊的橫坐標
        mLastX = e.x
        //檢查mItem移動后應該在[-deleteLength, 0]內
        val deleteWidth = mDeleteView!!.width
        if ((scrollX + dx) <= deleteWidth && (scrollX + dx) >= 0) {
            //觸發(fā)移動
            scrollBy(dx.toInt(), 0)
        }
        //如果一段時間沒有移動時間,mLastX還沒被stopMove重置為-1,那就是移動到其他地方了
        //設置200毫秒沒有新事件就觸發(fā)stopMove
        removeCallbacks(stopMoveRunnable)
        postDelayed(stopMoveRunnable, 200)
    }
    private fun stopMove() {
        //如果移動過半了,應該判定左滑成功
        val deleteWidth = mDeleteView!!.width
        if (abs(scrollX) >= deleteWidth / 2f) {
            //觸發(fā)移動至完全展開
            mScroller.startScroll(scrollX, 0, deleteWidth - scrollX, 0)
        }else {
            //如果移動沒過半應該恢復狀態(tài),則恢復到原來狀態(tài)
            mScroller.startScroll(scrollX, 0, - scrollX, 0)
        }
        invalidate()
        //清除狀態(tài)
        mLastX = -1f
    }
    //流暢地滑動
    override fun computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.currX, mScroller.currY)
            postInvalidate()
        }
    }
    //單位轉換
    @Suppress("SameParameterValue")
    private fun dp2px(context: Context, dpVal: Float): Int {
        return TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP, dpVal, context.resources
                .displayMetrics
        ).toInt()
    }
    @Suppress("SameParameterValue")
    private fun sp2px(context: Context, spVal: Float): Int {
        val fontScale = context.resources.displayMetrics.scaledDensity
        return (spVal * fontScale + 0.5f).toInt()
    }

主要問題

動態(tài)生成TextView

這個主要就是通過代碼生成一個TextView,不是很難,提一下。

將TextView對齊到當前容器右端

這里利用ConstraintLayout取巧做的還是不錯的,因為如果要自己去實現一個在屏幕外的對齊,至少要在onMeasure中獲得寬度,再去onLayout里面擺放到右側屏幕外。

這里也有一些問題,首先是設置動態(tài)生成的TextView參數,然后是設置ConstraintLayout內的約束條件,因為約束標記必須要用到id,還得為當前控件生成一個id,最后就是做一個回調接口了。

滑動出界問題

還有一個沒有預料到的問題是當滑動超過當前view的范圍時,ACTION_MOVE和ACTION_UP都無法接收到,這就沒法知道移動是否結束了。這里因為我們的自定義view是一個viewgroup,所以沒法消耗ACTION_DOWN事件,所以后續(xù)的事件序列并不會交到當前的item上,這就麻煩了,所以這個需求本質上就是不合理的,但是還是要解決問題吧!

這里我通過View類的postDelayed,延遲運行一個runnable去停止滑動,當每次滑動的時候又去停止這個runnable。整個邏輯運行起來就是,滑動沒有出界,移動的時候先移除延遲的停止邏輯,再發(fā)送延遲的停止邏輯,直到ACTION_UP觸發(fā)停止,若滑動出界了,沒有去移除延遲的停止邏輯,就會在一端時間后自動觸發(fā)停止。

有點繞,但是還是挺簡單的,里面的原理也簡單講一下。實際上View的postDelayed會通過主線程的handler去延遲執(zhí)行,如果有了解handler機制,可以知道handler并不僅僅可以發(fā)送message,同樣也可以發(fā)送runnable,類似移除message,同樣也可以移除runnable。

滑動開始判定

另一個預料之外的問題是當滑動從其他item移動到當前item的時候,即使沒有收到ACTION_DOWN事件,也會觸發(fā)滑動,這個很不符合邏輯。我這就在stopMove里面將mLastX改為了-1,初始值也是-1,如果在moveItem中值是-1,就說明沒有被ACTION_DOWN事件設定mLastX,即按下的時候并不在當前item,應當舍棄滑動。

后續(xù)訂正

onTouchEvent有誤

    //處理事件
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when(event.action) {
            MotionEvent.ACTION_DOWN -> return true
            MotionEvent.ACTION_MOVE -> moveItem(event)
            MotionEvent.ACTION_UP -> stopMove()
        }
        return super.onTouchEvent(event)
    }

增加對ACTION_DOWN的攔截,因為如果ACTION_DOWN沒在view處有被處理的話,會被丟棄,如果被view攔截了的話,move事件又不會經過onInterceptTouchEvent函數。真不知道當時寫的時候是怎么運行通過的。。。

到此這篇關于Android自定義view實現列表內左滑刪除Item的文章就介紹到這了,更多相關Android左滑刪除Item內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Android使用Gradle依賴配置compile、implementation與api的區(qū)別介紹

    Android使用Gradle依賴配置compile、implementation與api的區(qū)別介紹

    這篇文章主要給大家介紹了關于Android使用Gradle依賴配置compile、implementation與api區(qū)別的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起看看吧
    2018-09-09
  • Android編程之Button控件配合Toast控件用法分析

    Android編程之Button控件配合Toast控件用法分析

    這篇文章主要介紹了Android編程之Button控件配合Toast控件用法,結合實例形式分析了Button控件及Toast控件的功能及具體使用技巧,需要的朋友可以參考下
    2015-12-12
  • Android實現閃屏頁效果

    Android實現閃屏頁效果

    這篇文章主要為大家詳細介紹了Android實現閃屏頁效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-11-11
  • Android 使用viewpager實現無限循環(huán)(定時+手動)

    Android 使用viewpager實現無限循環(huán)(定時+手動)

    這篇文章主要介紹了Android 使用viewpager實現無限循環(huán)(定時+手動)的相關資料,需要的朋友可以參考下
    2015-11-11
  • Android Bitmap像素級操作詳解

    Android Bitmap像素級操作詳解

    這篇文章主要介紹了Android Bitmap像素級操作詳解,想了解Bitmap的同學可以參考下
    2021-04-04
  • Android利用DownloadManager實現文件下載

    Android利用DownloadManager實現文件下載

    這篇文章主要為大家詳細介紹了Android利用DownloadManager實現文件下載,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • android完美實現 拍照 選擇圖片 剪裁等代碼分享

    android完美實現 拍照 選擇圖片 剪裁等代碼分享

    本文給大家分享了2個安卓實現實現 拍照 選擇圖片 剪裁等的代碼,都是從正式項目中提取出來了,非常實用,有需要的小伙伴可以參考下。
    2016-01-01
  • Android開發(fā)手冊自定義Switch開關按鈕控件

    Android開發(fā)手冊自定義Switch開關按鈕控件

    這篇文章主要為大家介紹了Android開發(fā)手冊自定義Switch開關按鈕控件的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06
  • 基于Flutter制作一個圖像濾鏡

    基于Flutter制作一個圖像濾鏡

    很多時候,我們需要一些特效功能,比如給圖片做個濾鏡什么的,那么如果在flutter中,如果要實現這樣的濾鏡功能應該怎么處理呢?一起來看看吧
    2023-06-06
  • Flutter常用的布局和事件示例詳解

    Flutter常用的布局和事件示例詳解

    這篇文章主要給大家介紹了關于Flutter常用的布局和事件的相關資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Flutter具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-03-03

最新評論

治县。| 鄂尔多斯市| 福建省| 涿鹿县| 米脂县| 从化市| 睢宁县| 金川县| 长岭县| 衡阳市| 肥乡县| 阿尔山市| 旅游| 栾城县| 绥棱县| 荆门市| 安宁市| 甘肃省| 隆德县| 长葛市| 铅山县| 巴塘县| 东源县| 石首市| 南阳市| 阳原县| 承德市| 襄垣县| 遵化市| 台中县| 乌苏市| 白玉县| 临澧县| 奉贤区| 贵州省| 利川市| 北宁市| 新龙县| 泊头市| 抚远县| 崇信县|