我尝试在我的回收站视图的某个项目上实现动画,并进行自定义滑动,为此,我尝试使用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>
我的列表开头如何:
最后,当我单击一个项目时:
当我切换到OnSwipe触发器时,动画只有在我严格水平滑动时才起作用,如果错误地使我的手指稍微向上或向下滑动,动画就会停止并且甚至无法完成播放:
我认为出现问题是因为回收站视图也可以处理滑动手势,因为当动画停止时(如果我稍微向上/向下移动),回收站通常会移动。
如果我在滑动时玩得太多,则会出现此错误:
java.lang.NullPointerException:尝试在空对象引用上调用虚拟方法'void android.view.VelocityTracker.addMovement(android.view.MotionEvent)' 在androidx.constraintlayout.motion.widget.MotionLayout $ MyTracker.addMovement(MotionLayout.java:985) 在androidx.constraintlayout.motion.widget.MotionScene.processTouchEvent(MotionScene.java:1043) 在androidx.constraintlayout.motion.widget.MotionLayout.onTouchEvent(MotionLayout.java:2992) 在android.view.View.dispatchTouchEvent(View.java:12515) 在android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3024) 在android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2705) 在android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030) 在android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662) 在android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030) 在android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662) 在android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030) 在android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662) 在android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030) 在android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662) 在android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030) 在android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662) 在android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030) 在android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662) 在android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030) 在android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662) 在com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:444) 在com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1829) 在android.app.Activity.dispatchTouchEvent(Activity.java:3397) 在androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69) 在com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:402) 在android.view.View.dispatchPointerEvent(View.java:12754) 在android.view.ViewRootImpl $ ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5052) 在android.view.ViewRootImpl $ ViewPostImeInputStage.onProcess(ViewRootImpl.java:4855) 在android.view.ViewRootImpl $ InputStage.deliver(ViewRootImpl.java:4372) 在android.view.ViewRootImpl $ InputStage.onDeliverToNext(ViewRootImpl.java:4425) 在android.view.ViewRootImpl $ InputStage.forward(ViewRootImpl.java:4391) 在android.view.ViewRootImpl $ AsyncInputStage.forward(ViewRootImpl.java:4531) 在android.view.ViewRootImpl $ InputStage.apply(ViewRootImpl.java:4399) 在android.view.ViewRootImpl $ AsyncInputStage.apply(ViewRootImpl.java:4588) 在android.view.ViewRootImpl $ InputStage.deliver(ViewRootImpl.java:4372) 在android.view.ViewRootImpl $ InputStage.onDeliverToNext(ViewRootImpl.java:4425) 在android.view.ViewRootImpl $ InputStage.forward(ViewRootImpl.java:4391) 在android.view.ViewRootImpl $ InputStage.apply(ViewRootImpl.java:4399) 在android.view.ViewRootImpl $ InputStage.deliver(ViewRootImpl.java:4372) 在android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7034) 在android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:7007) 在android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6968) 在android.view.ViewRootImpl $ WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:7137) 在android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:186) 在android.os.MessageQueue.nativePollOnce(本地方法) 在android.os.MessageQueue.next(MessageQueue.java:326) 在android.os.Looper.loop(Looper.java:142) 在android.app.ActivityThread.main(ActivityThread.java:6649) 在java.lang.reflect.Method.invoke(本机方法) 在com.android.internal.os.RuntimeInit $ MethodAndArgsCaller.run(RuntimeInit.java:493)
我不知道如何在MotionLayout和RecyclerView之间正确分配触摸事件,甚至在当前阶段甚至不可以通过运动布局来分配触摸事件。
如果有人有想法,请先谢谢!
更新:
这是处理触摸事件的代码(在recyclerView上禁用/启用)
recyclerView项上的onTouchListener
holder.itemView.setOnTouchListener { view, motionEvent ->
val progress = holder.motion.progress
when(motionEvent.action){
MotionEvent.ACTION_DOWN -> {
x = motionEvent.x
y = motionEvent.y
Log.d("Event Down", "X : $x, Y : $y")
}
MotionEvent.ACTION_UP->{
finalx = motionEvent.x
finaly = motionEvent.y
Log.d("Event Up", "X : $finalx, Y : $finaly")
if(progress > 0.5 && progress != 1.0F ){
holder.motion?.transitionToEnd()
}else{
holder.motion?.transitionToStart()
}
holder.itemView.parent.requestDisallowInterceptTouchEvent(false)
}
MotionEvent.ACTION_MOVE ->{
val currentX = motionEvent.x
val currentY = motionEvent.y
val deltaX = abs(x - currentX)
val deltaY = abs(y - currentY)
if (deltaX > 1 && deltaY > 1 && deltaX > deltaY ){
holder.itemView.parent.requestDisallowInterceptTouchEvent(true)
}
Log.d("Event Move", "X : $currentX, Y : $currentY")
}
}
false
}
过渡侦听器
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)
}
})
我到处都使用requestDisallowInterceptTouchEvent
是因为我注意到了不同的行为,并且这段代码是效果最好的代码,但是在处理单元格/滚动/动画后仍然会崩溃。
答案 0 :(得分:1)
我相信是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()
}
上方是布局管理器。它非常简单,只需覆盖垂直滚动条,然后再设置我的标志即可。
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中,则不需要回叫。我要做的就是检查OnChanged是否不在起始或结束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;
}
}
}
}