使用ViewDragHelper自定義左右可滑動內容的ViewGroup

經過在自定義的ViewGroup內部使用ViewDragHelper,使得給自定義的ViewGroup在水平方向上並排按序添加多個子View(ViewGroup),能夠實現水平左右滾動的效果,相似於ViewPager.java

官方解釋以下(不作翻譯,原汁原味的英語更易理解):git

/**
 * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number
 * of useful operations and state tracking for allowing a user to drag and reposition
 * views within their parent ViewGroup.
 */

使用

ViewDragHelper內部定義了一個靜態內部類Callback,咱們須要重寫Callback.github

val helper : ViewDragHelper =   ViewDragHelper.create(this, object : ViewDragHelper.Callback(){
        //根據須要,重寫相關的方法.
})

在你的自定義ViewGroup的onTouchEvent(event)方法內調用ViewDragHelper.processTouchEvent(event).app

override fun onTouchEvent(event: MotionEvent): Boolean {
        helper.processTouchEvent(event)
        return true
    }

在ViewDragHelper.processTouchEvent(event)方法內部調用了Callback的回調方法.這樣你只須要重寫Callback的回調方法便可.ide

Callback

先看一下咱們須要用到的Callback的方法.this

tryCaptureView

當前觸摸到的是哪一個View,咱們定義的這個ViewGroup能夠添加多個子Viewidea

override fun tryCaptureView(capturedView: View, pointerId: Int): Boolean {
                for (x in 0 until childCount) {
                    val child = getChildAt(x)
                    if (child.visibility == View.GONE) continue
                    if (child == capturedView) return true;
                }
                return false
            }

clampViewPositionHorizontal(@NonNull View child, int left, int dx)

約束水平方向上左右可滾動的邊界位置.對於經過tryCaptureView觸摸的任意一個view,須要對它的左右兩個方向作邊界約束.翻譯

override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
                for (x in 0 until childCount) {
                    if (getChildAt(x) == child) {
                        //左邊界約束,在ScrollerLayout未發生滑動的狀況下,當前觸摸的子View距離ScrollerLayout的左邊界的距離值.
                        var clampLeft = 0
                        //右邊界約束,在ScrollerLayout未發生滑動的狀況下,當前觸摸的子View距離ScrollerLayout的右邊界的距離值.
                        var clampRight = 0
                        for (y in 0 until x) {
                            clampLeft += getChildAt(y).width
                        }
                        for (y in x + 1 until childCount) {
                            clampRight += getChildAt(y).width
                        }
                        //當前觸摸的子View距離ScrollerLayout的左邊界不能超過clampLeft的約束值,子View向右滑動的極限
                        if (left > clampLeft) return clampLeft
                       //當前觸摸的子View距離ScrollerLayout的右邊界不能超過clampRight的約束值,子View向左滑動的極限 
                        if (left + clampRight < 0) return clampRight
                    }
                }
                return left
            }

clampViewPositionVertical(@NonNull View child, int top, int dy)

豎直方向上的頂部和底部的邊界約束.咱們這裏不作處理,直接返回0.code

onViewPositionChanged(@NonNull View changedView, int left, int top, int dx,int dy)

當前觸摸的view位置發生改變時的回調.須要對每一個子view都從新更改其位置.get

override fun onViewPositionChanged(changedView: View, left: Int, top: Int, dx: Int, dy: Int) {
                super.onViewPositionChanged(changedView, left, top, dx, dy)
                for (x in 0 until childCount) {
                    if (getChildAt(x) == changedView) {
                        changedView.layout(left, 0, left + changedView.width, height)
                        //當前觸摸的子View左右兩邊的View的left值,也就是距離ScrollerLayout的左邊界的距離.
                        var totalChildWidth: Int = 0
                        //對於changedView左側的View,採用由右至左的順序來改變每一個view的位置.方便totalChildWidth作累加操做
                        for (y in x - 1 downTo 0) {
                            val child = getChildAt(y)
                            totalChildWidth += child.width
                            child.layout(left - totalChildWidth, top, left - (totalChildWidth - child.width), height)
                        }
                        //changedView右側的第一個View距離ScrollerLayout的左邊界的默認距離
                        totalChildWidth = changedView.width+left
                        //對於changedView右側的,採用由左至右的順序來改變每一個view的位置.
                        for (y in x + 1 until childCount) {
                            val child = getChildAt(y)
                            child.layout(totalChildWidth, 0, child.width + totalChildWidth, height)
                            totalChildWidth += child.width
                        }
                        break
                    }
                }
            }

onViewReleased(@NonNull View releasedChild, float xvel, float yvel)

鬆開手指後的回調.

  • releaseChild.getLeft() > 0;向右滾動.須要判斷releaseChild滾動的距離有沒有超過其前一個View的寬度的一半.
  • releaseChild.getLeft() < 0;向左滾動.須要判斷releaseChild滾動的距離有沒有超過自身的寬度的一半.

int getViewHorizontalDragRange(@NonNull View child);

水平滾動的範圍.這裏等於各個子view寬度之和.

int getViewVerticalDragRange(@NonNull View child);

豎直方向上不作滾動,直接返回0便可.
具體源碼看這裏ScrollerLayout

相關文章
相關標籤/搜索