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