Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/android/186.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Android 在RecyclerView的项目上使用MotionLayout OnSwipe_Android_Android Recyclerview_Android Animation_Android Motionlayout - Fatal编程技术网

Android 在RecyclerView的项目上使用MotionLayout OnSwipe

Android 在RecyclerView的项目上使用MotionLayout OnSwipe,android,android-recyclerview,android-animation,android-motionlayout,Android,Android Recyclerview,Android Animation,Android Motionlayout,我尝试在我的回收器视图的一个项目上实现一个动画,一个自定义滑动,为此,我尝试使用MotionLayout。 实际上,如果动画的触发器是单击,则效果很好,但是当我要进行滑动时,它会干扰循环器视图的滑动。以下是我的场景: <?xml version="1.0" encoding="utf-8"?> <MotionScene xmlns:motion="http://schemas.android.com/apk/res-auto"> <Transition

我尝试在我的回收器视图的一个项目上实现一个动画,一个自定义滑动,为此,我尝试使用MotionLayout。 实际上,如果动画的触发器是单击,则效果很好,但是当我要进行滑动时,它会干扰循环器视图的滑动。以下是我的场景:

<?xml version="1.0" encoding="utf-8"?>
<MotionScene
    xmlns:motion="http://schemas.android.com/apk/res-auto">

<Transition
    motion:constraintSetStart="@layout/chat_item_start"
    motion:constraintSetEnd="@layout/chat_item_end"
    motion:duration="500">

    <OnClick
        motion:targetId="@+id/background"
        motion:clickAction="toggle" />

    <!-- <OnSwipe
        motion:touchAnchorId="@+id/background"
        motion:touchAnchorSide="right"
        motion:dragDirection="dragLeft" /> -->

    </Transition>

</MotionScene>
过渡监听器

holder.motion.setTransitionListener(object: MotionLayout.TransitionListener{

        override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) {}
        override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) {
            Log.d("Animation started", "Start id : $p1")
            holder.itemView.parent.requestDisallowInterceptTouchEvent(true)
        }

        override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) {
            Log.d("Animation change ", "progress : $p3")
        }

        override fun onTransitionCompleted(p0: MotionLayout?, p1: Int) {
            val progress = holder.motion.progress
            opened = progress == 1.0f
            Log.d("Animation Completed", "progress : $progress")
            holder.itemView.parent.requestDisallowInterceptTouchEvent(false)
        }
    })

我在任何地方都会使用
requestDisallowWinterCeptTouchEvent
,因为我注意到了不同的行为,这段代码工作得最好,但在使用单元格/滚动/动画后仍然会崩溃。

我相信是recyclerview的滚动打断了你的动画。我的建议是在物品被刷卡时锁定recyclerviews滚动条

至于这次坠机,我也在努力找到解决办法。崩溃似乎是由于调用addMovement(android.view.MotionEvent)时未检查对VelocityTracker对象的引用是否为null造成的。这是一个问题的议案布局本身

编辑:添加代码

class CustomLayoutManager(val context: Context?) : LinearLayoutManager(context) {
private var isScrollEnabled = true
fun setScrollEnabled(flag: Boolean) {
    isScrollEnabled = flag
}

override fun canScrollVertically(): Boolean {
    return isScrollEnabled && super.canScrollVertically()
}
上面是布局管理器。它非常简单,只需覆盖垂直滚动,将其设置为my flag(稍后将设置)

MotionLayout.setTransitionListener(object : MotionLayout.TransitionListener {
        override fun onTransitionTrigger(motionLayout: MotionLayout?, idStart: Int, idEnd: Boolean, progress: Float) {
        }

        override fun onTransitionStarted(motionLayout: MotionLayout?, idStart: Int, idEnd: Int) {
        }

        override fun onTransitionChange(motionLayout: MotionLayout?, idStart: Int, idEnd: Int, progress: Float) {
            //Timber.d("ML: CHANGED ${p0?.currentState} endId:$p2 startid:$p1")
            if (motionLayout?.currentState != idStart && motionLayout?.currentState != idEnd) motionListener?.onTriggered()

        }

        override fun onTransitionCompleted(motionLayout: MotionLayout?, idCurrent: Int) {
            motionListener?.onComplete()

        }
    })
上面是转换侦听器。它位于我的自定义视图中,因此如果您的视图位于recyclerview中,则不需要回拨。我所做的只是在更改后检查它是否不在开始或结束id处。这就是您知道项目正在运行的方式。然后在我的适配器中有这个

interface ScrollLockListener {
    fun onLockUnlock(shouldLock: Boolean)
}
fun setScrollLockListener(listener: (Boolean) -> Unit) {
    scrollLockListener = object : ScrollLockListener {
        override fun onLockUnlock(shouldLock: Boolean) = listener(shouldLock)
    }
}

holder.item.setMotionListener(
                    { scrollLockListener?.onLockUnlock(false) },
                    { scrollLockListener?.onLockUnlock(true) })
这将为适配器创建一个回调,允许您从活动或片段设置滚动锁。然后在你的活动/片段中

cardLayoutRecycler.layoutManager = CustomLayoutManager(context).also {
            recyclerAdapter.setScrollLockListener { isLocked -> it.setScrollEnabled(isLocked) }
        }
编辑:为了修复崩溃,我最后做了这个

public class CustomMotionLayout extends MotionLayout {

public CustomMotionLayout(Context context) {
    super(context);
}

public CustomMotionLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public CustomMotionLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

@Override
protected MotionTracker obtainVelocityTracker() {
    return CustomMotionLayout.MyTracker.obtain();
}

/**
 * The only functional changes to this class are added null-checks and recursion avoidance in
 * @see MyTracker#getYVelocity(int)
 *
 * A possible desync issue in the official MotionLayout class can trigger a NullPointerException
 * here when the VelocityTracker object is accessed immediately after being recycled.
 */
private static class MyTracker implements MotionLayout.MotionTracker {
    VelocityTracker tracker;
    private static CustomMotionLayout.MyTracker me = new CustomMotionLayout.MyTracker();

    private MyTracker() {
    }

    public static CustomMotionLayout.MyTracker obtain() {
        me.tracker = VelocityTracker.obtain();
        return me;
    }

    public void recycle() {
        if (this.tracker != null) {
            this.tracker.recycle();
            this.tracker = null;
        }
    }

    public void clear() {
        if (this.tracker != null) {
            this.tracker.clear();
        }
    }

    public void addMovement(MotionEvent event) {
        if (this.tracker != null) {
            this.tracker.addMovement(event);
        }
    }

    public void computeCurrentVelocity(int units) {
        if (this.tracker != null) {
            this.tracker.computeCurrentVelocity(units);
        }
    }

    public void computeCurrentVelocity(int units, float maxVelocity) {
        if (this.tracker != null) {
            this.tracker.computeCurrentVelocity(units, maxVelocity);
        }
    }

    public float getXVelocity() {
        if (this.tracker != null) {
            return this.tracker.getXVelocity();
        } else {
            return 0f;
        }
    }

    public float getYVelocity() {
        if (this.tracker != null) {
            return this.tracker.getYVelocity();
        } else {
            return 0f;
        }
    }

    public float getXVelocity(int id) {
        if (this.tracker != null) {
            return this.tracker.getXVelocity(id);
        } else {
            return 0f;
        }
    }

    public float getYVelocity(int id) {
        if (this.tracker != null) {
            return this.tracker.getYVelocity(id);
        } else {
            return 0f;
        }
    }
}

}

这也是我的想法,我就是这么做的(我用相关代码进行了更新),但问题仍然存在。。。我想也许MotionLayout还没有准备好放在一个recyclerView中并处理项目的生命周期,我采用了一种不同的方法。我创建了一个自定义布局管理器,它允许我锁定recyclerview的滚动。然后在转换监听器中,我在卡片滑动时锁定滚动条,在滑动完成时解锁滚动条。效果相当不错。如果您愿意,我可以用代码更新我的答案。是的,我想检查您的代码,也许我的实现不是处理列表和motionLayout之间的运动的正确方法
public class CustomMotionLayout extends MotionLayout {

public CustomMotionLayout(Context context) {
    super(context);
}

public CustomMotionLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public CustomMotionLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

@Override
protected MotionTracker obtainVelocityTracker() {
    return CustomMotionLayout.MyTracker.obtain();
}

/**
 * The only functional changes to this class are added null-checks and recursion avoidance in
 * @see MyTracker#getYVelocity(int)
 *
 * A possible desync issue in the official MotionLayout class can trigger a NullPointerException
 * here when the VelocityTracker object is accessed immediately after being recycled.
 */
private static class MyTracker implements MotionLayout.MotionTracker {
    VelocityTracker tracker;
    private static CustomMotionLayout.MyTracker me = new CustomMotionLayout.MyTracker();

    private MyTracker() {
    }

    public static CustomMotionLayout.MyTracker obtain() {
        me.tracker = VelocityTracker.obtain();
        return me;
    }

    public void recycle() {
        if (this.tracker != null) {
            this.tracker.recycle();
            this.tracker = null;
        }
    }

    public void clear() {
        if (this.tracker != null) {
            this.tracker.clear();
        }
    }

    public void addMovement(MotionEvent event) {
        if (this.tracker != null) {
            this.tracker.addMovement(event);
        }
    }

    public void computeCurrentVelocity(int units) {
        if (this.tracker != null) {
            this.tracker.computeCurrentVelocity(units);
        }
    }

    public void computeCurrentVelocity(int units, float maxVelocity) {
        if (this.tracker != null) {
            this.tracker.computeCurrentVelocity(units, maxVelocity);
        }
    }

    public float getXVelocity() {
        if (this.tracker != null) {
            return this.tracker.getXVelocity();
        } else {
            return 0f;
        }
    }

    public float getYVelocity() {
        if (this.tracker != null) {
            return this.tracker.getYVelocity();
        } else {
            return 0f;
        }
    }

    public float getXVelocity(int id) {
        if (this.tracker != null) {
            return this.tracker.getXVelocity(id);
        } else {
            return 0f;
        }
    }

    public float getYVelocity(int id) {
        if (this.tracker != null) {
            return this.tracker.getYVelocity(id);
        } else {
            return 0f;
        }
    }
}