MotionLayout实现无限滑动ViewPager

在这里插入图片描述ConstraintLayout2.0带来了MotionLayout ,MotionLayout是ConstraintLayout的子类,它具有ConstraintLayout的所有属性,用来处理两个ConstraintSet之间的切换,并且可以通过Transition定义两个ConstraintSet之间的切换动画。

模拟ViewPager


使用MotionLayout可以模拟实现一个无限Item的ViewPager切换效果:

实现思路

  • 定义三个子View,分别代表ViewPager当前显示中View以及前后两个View

  • 因为无限的Item只能复用这三个子View,所以在切换结束后,要将motion恢复到初始状态,即左右都存在可以进一步切换的子View


代码实现


要使用MotionLayout,必须将ConstraintLayout升级到2.0以上:

implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta6'

定义MotionScene

用三组ConstraintSet定义三个布局状态:起始状态、左滑后、右滑后

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">
    <ConstraintSet android:id="@+id/base_state">
        <Constraint android:id="@id/centerView">
            <Layout
                android:layout_width="@dimen/center_size"
                android:layout_height="@dimen/center_size"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintEnd_toEndOf="parent"
                motion:layout_constraintStart_toStartOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />

        </Constraint>

        <Constraint android:id="@id/leftView">
            <Layout
                android:layout_width="@dimen/side_size"
                android:layout_height="@dimen/side_size"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintEnd_toStartOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </Constraint>

        <Constraint android:id="@id/rightView">
            <Layout
                android:layout_width="@dimen/side_size"
                android:layout_height="@dimen/side_size"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintStart_toEndOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </Constraint>
    </ConstraintSet>

    <ConstraintSet android:id="@+id/move_left_to_right">
        <Constraint android:id="@id/centerView">
            <Layout
                android:layout_width="@dimen/side_size"
                android:layout_height="@dimen/side_size"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintStart_toEndOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </Constraint>

        <Constraint android:id="@id/leftView">
            <Layout
                android:layout_width="@dimen/center_size"
                android:layout_height="@dimen/center_size"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintEnd_toEndOf="parent"
                motion:layout_constraintStart_toStartOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </Constraint>

        <Constraint android:id="@id/rightView">
            <Layout
                android:layout_width="@dimen/side_size"
                android:layout_height="@dimen/side_size"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintStart_toEndOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </Constraint>
    </ConstraintSet>

    <ConstraintSet android:id="@+id/move_right_to_left">
        <Constraint android:id="@id/centerView">
            <Layout
                android:layout_width="@dimen/side_size"
                android:layout_height="@dimen/side_size"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintEnd_toStartOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </Constraint>

        <Constraint android:id="@id/leftView">
            <Layout
                android:layout_width="@dimen/side_size"
                android:layout_height="@dimen/side_size"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintEnd_toStartOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </Constraint>

        <Constraint android:id="@id/rightView">
            <Layout
                android:layout_width="@dimen/center_size"
                android:layout_height="@dimen/center_size"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintEnd_toEndOf="parent"
                motion:layout_constraintStart_toStartOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </Constraint>
    </ConstraintSet>

    <Transition
        motion:constraintSetEnd="@id/move_left_to_right"
        motion:constraintSetStart="@id/base_state">
        <OnSwipe
            motion:dragDirection="dragRight"
            motion:onTouchUp="autoCompleteToStart"
            motion:touchAnchorId="@id/centerView"
            motion:touchAnchorSide="right" />
    </Transition>

    <Transition
        motion:constraintSetEnd="@id/move_right_to_left"
        motion:constraintSetStart="@id/base_state">
        <OnSwipe
            motion:dragDirection="dragLeft"
            motion:onTouchUp="autoCompleteToStart"
            motion:touchAnchorId="@id/centerView"
            motion:touchAnchorSide="left" />
    </Transition>

</MotionScene>

<Transition/>中定义了左滑、右滑的切换

Item切换处理

如前所述,左右滑动导致动画切换结束后,为了调整显示中的View到中间,需要回复视图到初始状态 motionLayout?.progress = 0F

motionLayout.setTransitionListener(object : MotionLayout.TransitionListener {
    override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
        when (currentId) {
            R.id.move_left_to_right -> {
                if (currentPosition > 0) {
                    currentPosition--
                } else {
                    currentPosition = itemList.lastIndex
                }
                motionLayout?.progress = 0F
                updateView()
            }
            R.id.move_right_to_left -> {
                if (currentPosition < itemList.lastIndex) {
                    currentPosition++
                } else {
                    currentPosition = 0
                }
                motionLayout?.progress = 0F
                updateView()
            }
        }
    }
})

子View的位置虽然恢复初始状态,但是内容必须保持最新状态,所以在updateView中,更新视图内容:

private fun updateView() {
        centerTextView.text = "Item\n${itemList[currentPosition]}"

        rightTextView.text = if (currentPosition == itemList.lastIndex) {
            "Item\n${itemList.first()}"
        } else {
            "Item\n${itemList[currentPosition + 1]}"
        }

        leftTextView.text = if (currentPosition == 0) {
            "Item\n${itemList.last()}"
        } else {
            "Item\n${itemList[currentPosition - 1]}"
        }
    }

最后


MotionLayout可以方便地实现场景切换的动画效果,最新的AndroidStudio 4.0 还专门为MotionLayout提供了MotionEditor功能,无需通过Xml便可以设置视图状态及其过度动画,使用起来更加方便。可以预见未来MotionLayout将在Android应用开发中作为一个重要角色被广泛推广和使用。


代码地址

https://github.com/vitaviva/MotionPager

猜你喜欢

转载自blog.csdn.net/vitaviva/article/details/106587255