查看原文
其他

MotionLayout实现无限轮播图效果

fundroid AndroidPub 2022-05-15

ConstraintLayout自2.0起引入了MotionLayout

MotionLayout是ConstraintLayout的子类,它具有ConstraintLayout的所有能力,可以实现两个ConstraintSet之间的切换,并且可以通过Transition定义转场动画。

1. 模拟ViewPager效果

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


2. 实现思路

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

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

3. 核心代码

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

implementation 'androidx.constraintlayout:constraintlayout:$latest_version'

定义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]}"
        }
    }

4. 最后

MotionLayoutn能够方便实现场景切换的动画效果,AndroidStudio 4.0 以上还为MotionLayout提供了MotionEditor功能,无需通过Xml便可设置视图状态及转场动画。可以预见,未来MotionLayout必将作为Android开发中的一个重要角色被推广和使用。

代码地址:

https://github.com/vitaviva/MotionPager



↓关注公众号↓↓添加微信交流↓

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存