diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/utils/StopLogic.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/utils/StopLogic.java index b3877f045..207d1cea9 100644 --- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/utils/StopLogic.java +++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/utils/StopLogic.java @@ -16,6 +16,8 @@ package androidx.constraintlayout.motion.utils; +import androidx.constraintlayout.core.motion.utils.SpringStopEngine; +import androidx.constraintlayout.core.motion.utils.StopEngine; import androidx.constraintlayout.core.motion.utils.StopLogicEngine; import androidx.constraintlayout.motion.widget.MotionInterpolator; @@ -28,7 +30,9 @@ * @hide */ public class StopLogic extends MotionInterpolator { - private StopLogicEngine engine = new StopLogicEngine(); + private StopLogicEngine mStopLogicEngine = new StopLogicEngine(); + private SpringStopEngine mSpringStopEngine; + private StopEngine mEngine = mStopLogicEngine; /** * Debugging logic to log the state. @@ -39,24 +43,56 @@ public class StopLogic extends MotionInterpolator { */ public String debug(String desc, float time) { - return engine.debug(desc, time); + return mEngine.debug(desc, time); } public float getVelocity(float x) { - return engine.getVelocity(x); + return mEngine.getVelocity(x); } public void config(float currentPos, float destination, float currentVelocity, float maxTime, float maxAcceleration, float maxVelocity) { - engine.config(currentPos, destination, currentVelocity, maxTime, maxAcceleration, maxVelocity); + mEngine = mStopLogicEngine; + mStopLogicEngine.config(currentPos, destination, currentVelocity, maxTime, maxAcceleration, maxVelocity); + } + + /** + * This configure the stop logic to be a spring. + * Moving from currentPosition(P0) to destination with an initial velocity of currentVelocity (V0) + * moving as if it has a mass (m) with spring constant stiffness(k), and friction(c) + * It moves with the equation acceleration a = (-k.x-c.v)/m. + * x = current position - destination + * v is velocity + * + * @param currentPos The current position + * @param destination The destination position + * @param currentVelocity the initial velocity + * @param mass the mass + * @param stiffness the stiffness or spring constant (the force by which the spring pulls) + * @param damping the stiffness or spring constant. (the resistance to the motion) + * @param stopThreshold (When the max velocity of the movement is below this it stops) + * @param boundaryMode This will allow you to control if it overshoots or bounces when it hits 0 and 1 + */ + public void springConfig(float currentPos, float destination, float currentVelocity, + float mass, float stiffness, float damping, float stopThreshold, + int boundaryMode) { + if (mSpringStopEngine == null) { + mSpringStopEngine = new SpringStopEngine(); + } + mEngine = mSpringStopEngine; + mSpringStopEngine.springConfig(currentPos, destination, currentVelocity, mass, stiffness, damping, stopThreshold, boundaryMode); } @Override public float getInterpolation(float v) { - return engine.getInterpolation(v); + return mEngine.getInterpolation(v); } @Override public float getVelocity() { - return engine.getVelocity(); + return mEngine.getVelocity(); + } + + public boolean isStopped() { + return mEngine.isStopped(); } } diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionLayout.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionLayout.java index 927279468..b58321977 100644 --- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionLayout.java +++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionLayout.java @@ -998,6 +998,8 @@ public class MotionLayout extends ConstraintLayout implements public static final int TOUCH_UP_STOP = 3; public static final int TOUCH_UP_DECELERATE = 4; public static final int TOUCH_UP_DECELERATE_AND_COMPLETE = 5; + public static final int TOUCH_UP_NEVER_TO_START = 6; + public static final int TOUCH_UP_NEVER_TO_END = 7; static final String TAG = "MotionLayout"; private final static boolean DEBUG = false; @@ -1888,15 +1890,24 @@ public void touchAnimateTo(int touchUpMode, float position, float currentVelocit switch (touchUpMode) { case TOUCH_UP_COMPLETE: + case TOUCH_UP_NEVER_TO_START: + case TOUCH_UP_NEVER_TO_END: case TOUCH_UP_COMPLETE_TO_START: case TOUCH_UP_COMPLETE_TO_END: { - if (touchUpMode == TOUCH_UP_COMPLETE_TO_START) { + if (touchUpMode == TOUCH_UP_COMPLETE_TO_START || touchUpMode == TOUCH_UP_NEVER_TO_END) { position = 0; - } else if (touchUpMode == TOUCH_UP_COMPLETE_TO_END) { + } else if (touchUpMode == TOUCH_UP_COMPLETE_TO_END || touchUpMode == TOUCH_UP_NEVER_TO_START) { position = 1; } - mStopLogic.config(mTransitionLastPosition, position, currentVelocity, - mTransitionDuration, mScene.getMaxAcceleration(), mScene.getMaxVelocity()); + float stiff = mScene.getSpringStiffiness(); + if(Float.isNaN(stiff)) { + mStopLogic.config(mTransitionLastPosition, position, currentVelocity, + mTransitionDuration, mScene.getMaxAcceleration(), mScene.getMaxVelocity()); + } else { + mStopLogic.springConfig(mTransitionLastPosition, position, currentVelocity, + mScene.getSpringMass(), mScene.getSpringStiffiness(), mScene.getSpringDamping(), + mScene.getSpringStopThreshold(), mScene.getSpringBoundary()); + } int currentState = mCurrentState; // TODO: we really should remove setProgress(), this is a temporary fix mTransitionGoalPosition = position; @@ -1935,6 +1946,46 @@ public void touchAnimateTo(int touchUpMode, float position, float currentVelocit invalidate(); } + /** + * Allows you to use trigger spring motion touch behaviour. + * You must have configured all the spring parameters in the Transition's OnSwipe + * + * @param position the position 0 - 1 + * @param currentVelocity the current velocity rate of change in position per second + */ + public void touchSpringTo(float position, float currentVelocity) { + if (DEBUG) { + Log.v(TAG, " " + Debug.getLocation() + " touchAnimateTo " + position + " " + currentVelocity); + } + if (mScene == null) { + return; + } + if (mTransitionLastPosition == position) { + return; + } + + mTemporalInterpolator = true; + mAnimationStartTime = getNanoTime(); + mTransitionDuration = mScene.getDuration() / 1000f; + + mTransitionGoalPosition = position; + mInTransition = true; + + mStopLogic.springConfig(mTransitionLastPosition, position, currentVelocity, + mScene.getSpringMass(), mScene.getSpringStiffiness(), mScene.getSpringDamping(), + mScene.getSpringStopThreshold(), mScene.getSpringBoundary()); + + int currentState = mCurrentState; // TODO: we really should remove setProgress(), this is a temporary fix + mTransitionGoalPosition = position; + mCurrentState = currentState; + mInterpolator = mStopLogic; + + + mTransitionInstantly = false; + mAnimationStartTime = getNanoTime(); + invalidate(); + } + private static boolean willJump(float velocity, float position, float maxAcceleration) { if (velocity > 0) { float time = velocity / maxAcceleration; @@ -2158,6 +2209,25 @@ public boolean isInRotation() { return mInRotation; } + /** + * This jumps to a state + * It will be at that state after one repaint cycle + * If the current transition contains that state. + * It setsProgress 0 or 1 to that state. + * If not in the current transition itsl + * + * @param id state to set + */ + public void jumpToState(int id) { + if (mBeginState == id) { + setProgress(0); + } else if (mEndState == id) { + setProgress(1); + } else { + setTransition(id, id); + } + } + /** * Animate to the state defined by the id. * Width and height may be used in the picking of the id using this StateSet. @@ -2195,10 +2265,16 @@ public void transitionToState(int id, int screenWidth, int screenHeight, int dur } if (mBeginState == id) { animateTo(0.0f); + if (duration > 0) { + mTransitionDuration = duration / 1000f; + } return; } if (mEndState == id) { animateTo(1.0f); + if (duration > 0) { + mTransitionDuration = duration / 1000f; + } return; } mEndState = id; @@ -3500,11 +3576,19 @@ void evaluate(boolean force) { mTransitionLastPosition = position; mTransitionPosition = position; mTransitionLastTime = currentTime; - + int NOT_STOP_LOGIC = 0; + int STOP_LOGIC_CONTINUE = 1; + int STOP_LOGIC_STOP = 2; + int stopLogicDone = NOT_STOP_LOGIC; if (mInterpolator != null && !done) { if (mTemporalInterpolator) { float time = (currentTime - mAnimationStartTime) * 1E-9f; position = mInterpolator.getInterpolation(time); + if (mInterpolator == mStopLogic) { + boolean dp = mStopLogic.isStopped(); + stopLogicDone = (dp)?STOP_LOGIC_STOP:STOP_LOGIC_CONTINUE; + } + if (DEBUG) { Log.v(TAG, Debug.getLocation() + " mTransitionLastPosition = " + mTransitionLastPosition + " position = " + position); } @@ -3514,7 +3598,7 @@ void evaluate(boolean force) { if (mInterpolator instanceof MotionInterpolator) { float lastVelocity = ((MotionInterpolator) mInterpolator).getVelocity(); mLastVelocity = lastVelocity; - if (Math.abs(lastVelocity) * mTransitionDuration <= EPSILON) { + if (Math.abs(lastVelocity) * mTransitionDuration <= EPSILON && stopLogicDone==STOP_LOGIC_STOP) { mInTransition = false; } if (lastVelocity > 0 && position >= 1.0f) { @@ -3544,15 +3628,17 @@ void evaluate(boolean force) { setState(TransitionState.MOVING); } - if ((dir > 0 && position >= mTransitionGoalPosition) - || (dir <= 0 && position <= mTransitionGoalPosition)) { - position = mTransitionGoalPosition; - mInTransition = false; - } + if ( stopLogicDone != STOP_LOGIC_CONTINUE) { + if ((dir > 0 && position >= mTransitionGoalPosition) + || (dir <= 0 && position <= mTransitionGoalPosition)) { + position = mTransitionGoalPosition; + mInTransition = false; + } - if (position >= 1.0f || position <= 0.0f) { - mInTransition = false; - setState(TransitionState.FINISHED); + if (position >= 1.0f || position <= 0.0f) { + mInTransition = false; + setState(TransitionState.FINISHED); + } } int n = getChildCount(); @@ -3994,7 +4080,10 @@ public boolean onTouchEvent(MotionEvent event) { protected void onAttachedToWindow() { super.onAttachedToWindow(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - mPreviouseRotation = getDisplay().getRotation(); + Display display = getDisplay(); + if (display != null) { + mPreviouseRotation = display.getRotation(); + } } if (mScene != null && mCurrentState != UNSET) { ConstraintSet cSet = mScene.getConstraintSet(mCurrentState); diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionScene.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionScene.java index dfe3dbb33..b7782ffa7 100644 --- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionScene.java +++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionScene.java @@ -455,6 +455,7 @@ public boolean isViewTransitionEnabled(int id) { public boolean applyViewTransition(int viewTransitionId, MotionController motionController) { return mViewTransitionController.applyViewTransition(viewTransitionId, motionController); } + /////////////////////////////////////////////////////////////////////////////// // ====================== Transition ========================================== @@ -758,7 +759,8 @@ public TransitionOnClick(Context context, Transition transition, XmlPullParser p } a.recycle(); } - public TransitionOnClick( Transition transition, int id, int action) { + + public TransitionOnClick(Transition transition, int id, int action) { mTransition = transition; mTargetId = id; mMode = action; @@ -1664,6 +1666,40 @@ float getMaxVelocity() { return 0; } + float getSpringStiffiness() { + if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { + return mCurrentTransition.mTouchResponse.getSpringStiffness(); + } + return 0; + } + + float getSpringMass() { + if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { + return mCurrentTransition.mTouchResponse.getSpringMass(); + } + return 0; + } + + float getSpringDamping() { + if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { + return mCurrentTransition.mTouchResponse.getSpringDamping(); + } + return 0; + } + + float getSpringStopThreshold() { + if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { + return mCurrentTransition.mTouchResponse.getSpringStopThreshold(); + } + return 0; + } + int getSpringBoundary() { + if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { + return mCurrentTransition.mTouchResponse.getSpringBoundary(); + } + return 0; + } + void setupTouch() { if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { mCurrentTransition.mTouchResponse.setupTouch(); diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/OnSwipe.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/OnSwipe.java index fca7457ec..8e13d9b50 100644 --- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/OnSwipe.java +++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/OnSwipe.java @@ -33,7 +33,15 @@ public class OnSwipe { private float mDragScale = 1f; private int mFlags = 0; private float mDragThreshold = 10; - + private float mSpringDamping = Float.NaN; + private float mSpringMass = 1; + private float mSpringStiffness = Float.NaN; + private float mSpringStopThreshold = Float.NaN; + private int mSpringBoundary = 0; + public static final int SPRING_BOUNDARY_OVERSHOOT = 0; + public static final int SPRING_BOUNDARY_BOUNCESTART = 1; + public static final int SPRING_BOUNDARY_BOUNCEEND = 2; + public static final int SPRING_BOUNDARY_BOUNCEBOTH = 3; public static final int DRAG_UP = 0; public static final int DRAG_DOWN = 1; public static final int DRAG_LEFT = 2; @@ -60,6 +68,8 @@ public class OnSwipe { public static final int ON_UP_STOP = 3; public static final int ON_UP_DECELERATE = 4; public static final int ON_UP_DECELERATE_AND_COMPLETE = 5; + public static final int ON_UP_NEVER_TO_START = 6; + public static final int ON_UP_NEVER_TO_END = 7; /** * The id of the view who's movement is matched to your drag @@ -255,7 +265,7 @@ public int getLimitBoundsTo() { * The view to center the rotation about * * @param rotationCenterId - * @return + * @return this */ public OnSwipe setRotateCenter(int rotationCenterId) { mRotationCenterId = rotationCenterId; @@ -265,4 +275,112 @@ public OnSwipe setRotateCenter(int rotationCenterId) { public int getRotationCenterId() { return mRotationCenterId; } + + + public float getSpringDamping() { + return mSpringDamping; + } + + /** + * Set the damping of the spring if using spring. + * c in "a = (-k*x-c*v)/m" equation for the acceleration of a spring + * + * @param springDamping + * @return this + */ + public OnSwipe setSpringDamping(float springDamping) { + mSpringDamping = springDamping; + return this; + } + + /** + * Get the mass of the spring. + * the m in "a = (-k*x-c*v)/m" equation for the acceleration of a spring + * + * @return + */ + public float getSpringMass() { + return mSpringMass; + } + + /** + * Set the Mass of the spring if using spring. + * m in "a = (-k*x-c*v)/m" equation for the acceleration of a spring + * + * @param springMass + * @return this + */ + public OnSwipe setSpringMass(float springMass) { + mSpringMass = springMass; + return this; + } + + /** + * get the stiffness of the spring + * + * @return NaN if not set + */ + public float getSpringStiffness() { + return mSpringStiffness; + } + + /** + * set the stiffness of the spring if using spring. + * If this is set the swipe will use a spring return system. + * If set to NaN it will revert to the norm system. + * K in "a = (-k*x-c*v)/m" equation for the acceleration of a spring + * + * @param springStiffness + * @return + */ + public OnSwipe setSpringStiffness(float springStiffness) { + mSpringStiffness = springStiffness; + return this; + } + + /** + * The threshold for spring motion to stop. + * + * @return + */ + public float getSpringStopThreshold() { + return mSpringStopThreshold; + } + + /** + * set the threshold for spring motion to stop. + * This is in change in progress / second + * If the spring will never go above that threshold again it will stop. + * + * @param springStopThreshold + * @return + */ + public OnSwipe setSpringStopThreshold(float springStopThreshold) { + mSpringStopThreshold = springStopThreshold; + return this; + } + + /** + * The behaviour at the boundaries 0 and 1 + * + * @return + */ + public int getSpringBoundary() { + return mSpringBoundary; + } + + /** + * The behaviour at the boundaries 0 and 1. + * SPRING_BOUNDARY_OVERSHOOT = 0; + * SPRING_BOUNDARY_BOUNCE_START = 1; + * SPRING_BOUNDARY_BOUNCE_END = 2; + * SPRING_BOUNDARY_BOUNCE_BOTH = 3; + * + * @param springBoundary + * @return + */ + public OnSwipe setSpringBoundary(int springBoundary) { + mSpringBoundary = springBoundary; + return this; + } } diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/TouchResponse.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/TouchResponse.java index 57f388344..c68b6dfb5 100644 --- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/TouchResponse.java +++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/TouchResponse.java @@ -104,6 +104,11 @@ class TouchResponse { static final int FLAG_DISABLE_POST_SCROLL = 1; static final int FLAG_DISABLE_SCROLL = 2; private float mDragThreshold = 10; + private float mSpringDamping = Float.NaN; + private float mSpringMass = 1; + private float mSpringStiffness = Float.NaN; + private float mSpringStopThreshold = Float.NaN; + private int mSpringBoundary = 0; TouchResponse(Context context, MotionLayout layout, XmlPullParser parser) { mMotionLayout = layout; @@ -136,6 +141,11 @@ public TouchResponse(MotionLayout layout, OnSwipe onSwipe) { mFlags = onSwipe.getNestedScrollFlags(); mLimitBoundsTo = onSwipe.getLimitBoundsTo(); mRotationCenterId = onSwipe.getRotationCenterId(); + mSpringBoundary= onSwipe.getSpringBoundary(); + mSpringDamping= onSwipe.getSpringDamping(); + mSpringMass= onSwipe.getSpringMass(); + mSpringStiffness= onSwipe.getSpringStiffness(); + mSpringStopThreshold= onSwipe.getSpringStopThreshold(); } public void setRTL(boolean rtl) { @@ -205,6 +215,17 @@ private void fill(TypedArray a) { mLimitBoundsTo = a.getResourceId(attr, 0); } else if (attr == R.styleable.OnSwipe_rotationCenterId) { mRotationCenterId = a.getResourceId(attr, mRotationCenterId); + } else if (attr == R.styleable.OnSwipe_springDamping) { + mSpringDamping = a.getFloat(attr, mSpringDamping); + } else if (attr == R.styleable.OnSwipe_springMass) { + mSpringMass = a.getFloat(attr, mSpringMass); + } else if (attr == R.styleable.OnSwipe_springStiffness) { + mSpringStiffness = a.getFloat(attr, mSpringStiffness); + } else if (attr == R.styleable.OnSwipe_springStopThreshold) { + mSpringStopThreshold = a.getFloat(attr, mSpringStopThreshold); + } else if (attr == R.styleable.OnSwipe_springBoundary) { + mSpringBoundary = a.getInt(attr, mSpringBoundary); + } } @@ -344,7 +365,21 @@ void processTouchRotateEvent(MotionEvent event, MotionLayout.MotionTracker veloc } if (pos != 0.0f && pos != 1.0f && mOnTouchUp != MotionLayout.TOUCH_UP_STOP) { angularVelocity = (float) angularVelocity * mDragScale / mAnchorDpDt[1]; - mMotionLayout.touchAnimateTo(mOnTouchUp, (pos < 0.5) ? 0.0f : 1.0f, 3 * angularVelocity); + float target = (pos < 0.5) ? 0.0f : 1.0f; + + if (mOnTouchUp == MotionLayout.TOUCH_UP_NEVER_TO_START) { + if (currentPos + angularVelocity < 0) { + angularVelocity = Math.abs(angularVelocity); + } + target = 1; + } + if (mOnTouchUp == MotionLayout.TOUCH_UP_NEVER_TO_END) { + if (currentPos + angularVelocity > 1) { + angularVelocity = -Math.abs(angularVelocity); + } + target = 0; + } + mMotionLayout.touchAnimateTo(mOnTouchUp, target , 3 * angularVelocity); if (0.0f >= currentPos || 1.0f <= currentPos) { mMotionLayout.setState(MotionLayout.TransitionState.FINISHED); } @@ -437,8 +472,16 @@ void processTouchEvent(MotionEvent event, MotionLayout.MotionTracker velocityTra if (DEBUG) { Log.v(TAG, "# ACTION_MOVE CHANGE = " + change); } + pos = Math.max(Math.min(pos + change, 1), 0); + if (mOnTouchUp == MotionLayout.TOUCH_UP_NEVER_TO_START) { + pos = Math.max( pos , 0.01f); + } + if (mOnTouchUp == MotionLayout.TOUCH_UP_NEVER_TO_END) { + pos = Math.min(pos , 0.99f); + } + float current = mMotionLayout.getProgress(); if (pos != current) { if (current == 0.0f || current == 1.0f) { @@ -495,7 +538,22 @@ void processTouchEvent(MotionEvent event, MotionLayout.MotionTracker velocityTra pos += velocity / 3; // TODO calibration & animation speed based on velocity } if (pos != 0.0f && pos != 1.0f && mOnTouchUp != MotionLayout.TOUCH_UP_STOP) { - mMotionLayout.touchAnimateTo(mOnTouchUp, (pos < 0.5) ? 0.0f : 1.0f, velocity); + float target = (pos < 0.5) ? 0.0f : 1.0f; + + if (mOnTouchUp == MotionLayout.TOUCH_UP_NEVER_TO_START) { + if (currentPos + velocity < 0) { + velocity = Math.abs(velocity); + } + target = 1; + } + if (mOnTouchUp == MotionLayout.TOUCH_UP_NEVER_TO_END) { + if (currentPos + velocity > 1) { + velocity = -Math.abs(velocity); + } + target = 0; + } + + mMotionLayout.touchAnimateTo(mOnTouchUp, target, velocity); if (0.0f >= currentPos || 1.0f <= currentPos) { mMotionLayout.setState(MotionLayout.TransitionState.FINISHED); } @@ -754,4 +812,53 @@ public int getFlags() { public void setTouchUpMode(int touchUpMode) { mOnTouchUp = touchUpMode; } + + /** + * the stiffness of the spring if using spring + * K in "a = (-k*x-c*v)/m" equation for the acceleration of a spring + * @return NaN if not set + */ + public float getSpringStiffness() { + return mSpringStiffness; + } + + /** + * the Mass of the spring if using spring + * m in "a = (-k*x-c*v)/m" equation for the acceleration of a spring + * @return default is 1 + */ + public float getSpringMass() { + return mSpringMass; + } + + /** + * the damping of the spring if using spring + * c in "a = (-k*x-c*v)/m" equation for the acceleration of a spring + * @return NaN if not set + */ + public float getSpringDamping() { + return mSpringDamping; + } + + /** + * The threshold below + * @return NaN if not set + */ + public float getSpringStopThreshold() { + return mSpringStopThreshold; + } + + /** + * The spring's behaviour when it hits 0 or 1. It can be made ot overshoot or bounce + * overshoot = 0 + * bounceStart = 1 + * bounceEnd = 2 + * bounceBoth = 3 + * @return Bounce mode + */ + public int getSpringBoundary() { + return mSpringBoundary; + } + + } diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/utils/widget/ImageFilterButton.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/utils/widget/ImageFilterButton.java index f5fb5aefd..d02a38f60 100644 --- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/utils/widget/ImageFilterButton.java +++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/utils/widget/ImageFilterButton.java @@ -26,11 +26,13 @@ import android.graphics.drawable.LayerDrawable; import android.os.Build; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import android.view.ViewOutlineProvider; import androidx.annotation.RequiresApi; import androidx.appcompat.content.res.AppCompatResources; +import androidx.constraintlayout.motion.widget.Debug; import androidx.constraintlayout.widget.R; /** diff --git a/constraintlayout/constraintlayout/src/main/res/values/attrs.xml b/constraintlayout/constraintlayout/src/main/res/values/attrs.xml index 9bc2e1e1d..1b7e5a274 100644 --- a/constraintlayout/constraintlayout/src/main/res/values/attrs.xml +++ b/constraintlayout/constraintlayout/src/main/res/values/attrs.xml @@ -1285,6 +1285,16 @@ + + + + + + + + + + @@ -1304,6 +1314,8 @@ + + diff --git a/constraintlayout/core/src/main/java/androidx/constraintlayout/core/motion/utils/SpringStopEngine.java b/constraintlayout/core/src/main/java/androidx/constraintlayout/core/motion/utils/SpringStopEngine.java new file mode 100644 index 000000000..8364bf7fd --- /dev/null +++ b/constraintlayout/core/src/main/java/androidx/constraintlayout/core/motion/utils/SpringStopEngine.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.constraintlayout.core.motion.utils; + +/** + * This contains the class to provide the logic for an animation to come to a stop using a spring + * model. + * + * @hide + */ +public class SpringStopEngine implements StopEngine { + double mDamping = 0.5f; + private static final double UNSET = Double.MAX_VALUE; + private boolean mInitialized = false; + private double mStiffness; + private double mTargetPos; + private double mLastVelocity; + private float mLastTime; + private float mPos; + private float mV; + private float mMass; + private float mStopThreshold; + private int mBoundaryMode = 0; + + @Override + public String debug(String desc, float time) { + return null; + } + + void log(String str) { + StackTraceElement s = new Throwable().getStackTrace()[1]; + String line = ".(" + s.getFileName() + ":" + s.getLineNumber() + ") " + s.getMethodName() + "() "; + System.out.println(line + str); + } + + public void springConfig(float currentPos, float target, float currentVelocity, float mass, + float stiffness, float damping, float stopThreshold, int boundaryMode) { + mTargetPos = target; + mDamping = damping; + mInitialized = false; + mPos = currentPos; + mLastVelocity = currentVelocity; + mStiffness = stiffness; + mMass = mass; + mStopThreshold = stopThreshold; + mBoundaryMode = boundaryMode; + mLastTime = 0; + } + + @Override + public float getVelocity(float t) { + return (float) mV; + } + + @Override + public float getInterpolation(float time) { + compute(time - mLastTime); + mLastTime = time; + return (float) (mPos); + } + + public float getAcceleration() { + double k = mStiffness; + double c = mDamping; + double x = (mPos - mTargetPos); + return (float) (-k * x - c * mV) / mMass; + } + + @Override + public float getVelocity() { + return 0; + } + + @Override + public boolean isStopped() { + double x = (mPos - mTargetPos); + double k = mStiffness; + double v = mV; + double m = mMass; + double energy = v * v * m + k * x * x; + double max_def = Math.sqrt(energy / k); + return max_def <= mStopThreshold; + } + + private void compute(double dt) { + double x = (mPos - mTargetPos); + double a = getAcceleration(); + double dv = a * dt; + double avgV = mV + dv / 2; + mV += dv / 2; + mPos += avgV * dt; + if (mBoundaryMode > 0) { + if (mPos < 0 && ((mBoundaryMode & 1) == 1)) { + mPos = -mPos; + mV = -mV; + } + if (mPos > 1 && ((mBoundaryMode & 2) == 2)) { + mPos = 2 - mPos; + mV = -mV; + } + } + } +} diff --git a/constraintlayout/core/src/main/java/androidx/constraintlayout/core/motion/utils/StopEngine.java b/constraintlayout/core/src/main/java/androidx/constraintlayout/core/motion/utils/StopEngine.java new file mode 100644 index 000000000..8c3847123 --- /dev/null +++ b/constraintlayout/core/src/main/java/androidx/constraintlayout/core/motion/utils/StopEngine.java @@ -0,0 +1,13 @@ +package androidx.constraintlayout.core.motion.utils; + +public interface StopEngine { + String debug(String desc, float time); + + float getVelocity(float x); + + float getInterpolation(float v); + + float getVelocity(); + + boolean isStopped(); +} diff --git a/constraintlayout/core/src/main/java/androidx/constraintlayout/core/motion/utils/StopLogicEngine.java b/constraintlayout/core/src/main/java/androidx/constraintlayout/core/motion/utils/StopLogicEngine.java index 4611eadd1..cfd3cc3d7 100644 --- a/constraintlayout/core/src/main/java/androidx/constraintlayout/core/motion/utils/StopLogicEngine.java +++ b/constraintlayout/core/src/main/java/androidx/constraintlayout/core/motion/utils/StopLogicEngine.java @@ -24,7 +24,7 @@ * * @hide */ -public class StopLogicEngine { +public class StopLogicEngine implements StopEngine { private float mStage1Velocity, mStage2Velocity, mStage3Velocity; // the velocity at the start of each period private float mStage1Duration, mStage2Duration, mStage3Duration; // the time for each period private float mStage1EndPosition, mStage2EndPosition, mStage3EndPosition; // ending position @@ -33,6 +33,8 @@ public class StopLogicEngine { private boolean mBackwards = false; private float mStartPosition; private float mLastPosition; + private boolean mDone = false; + private static final float EPSILON = 0.00001f; /** * Debugging logic to log the state. @@ -106,6 +108,7 @@ public float getVelocity(float x) { } private float calcY(float time) { + mDone = false; if (time <= mStage1Duration) { return mStage1Velocity * time + (mStage2Velocity - mStage1Velocity) * time * time / (2 * mStage1Duration); } @@ -121,16 +124,17 @@ private float calcY(float time) { return mStage2EndPosition; } time -= mStage2Duration; - if (time < mStage3Duration) { + if (time <= mStage3Duration) { return mStage2EndPosition + mStage3Velocity * time - mStage3Velocity * time * time / (2 * mStage3Duration); } + mDone = true; return mStage3EndPosition; } public void config(float currentPos, float destination, float currentVelocity, float maxTime, float maxAcceleration, float maxVelocity) { - + mDone = false; mStartPosition = currentPos; mBackwards = (currentPos > destination); if (mBackwards) { @@ -150,8 +154,14 @@ public float getVelocity() { return (mBackwards) ? -getVelocity(mLastPosition) : getVelocity(mLastPosition); } + @Override + public boolean isStopped() { + return getVelocity() < EPSILON && Math.abs(mStage3EndPosition-mLastPosition) < EPSILON; + } + private void setup(float velocity, float distance, float maxAcceleration, float maxVelocity, float maxTime) { + mDone = false; if (velocity == 0) { velocity = 0.0001f; } diff --git a/projects/MotionLayoutExperiments/app/build.gradle b/projects/MotionLayoutExperiments/app/build.gradle index 9b1f895e0..15579662c 100644 --- a/projects/MotionLayoutExperiments/app/build.gradle +++ b/projects/MotionLayoutExperiments/app/build.gradle @@ -40,6 +40,7 @@ dependencies { implementation project(path: ':constraintlayout') implementation 'org.jetbrains:annotations:15.0' testImplementation 'junit:junit:4.13' + testImplementation project(path: ':core') androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } \ No newline at end of file diff --git a/projects/MotionLayoutExperiments/app/src/androidTest/java/androidx/constraintlayout/validation/SampleTest.kt b/projects/MotionLayoutExperiments/app/src/androidTest/java/androidx/constraintlayout/validation/SampleTest.kt new file mode 100644 index 000000000..ef05042fd --- /dev/null +++ b/projects/MotionLayoutExperiments/app/src/androidTest/java/androidx/constraintlayout/validation/SampleTest.kt @@ -0,0 +1,27 @@ +package com.example.developertemplate + +import android.util.Log +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class SampleTest { + + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + Log.v("FOO", appContext.packageName); + assertEquals("androidx.constraintlayout.experiments.motionlayout", appContext.packageName) + } +} diff --git a/projects/MotionLayoutExperiments/app/src/main/res/layout/demo_110_menu.xml b/projects/MotionLayoutExperiments/app/src/main/res/layout/demo_110_menu.xml index 424b58005..73ca531b1 100644 --- a/projects/MotionLayoutExperiments/app/src/main/res/layout/demo_110_menu.xml +++ b/projects/MotionLayoutExperiments/app/src/main/res/layout/demo_110_menu.xml @@ -103,6 +103,7 @@ android:id="@+id/fade" android:layout_width="wrap_content" android:layout_height="wrap_content" + app:motionEffect_viewTransition="@+id/foo" app:motionEffect_translationY="-40dp" app:constraint_referenced_ids="t1,tf1,t2,tf2,t3,tf3,t4,tf4,t5,tf5" /> diff --git a/projects/MotionLayoutExperiments/app/src/main/res/xml/demo_110_menu_scene.xml b/projects/MotionLayoutExperiments/app/src/main/res/xml/demo_110_menu_scene.xml index 6129d7cd6..b35d61f2a 100644 --- a/projects/MotionLayoutExperiments/app/src/main/res/xml/demo_110_menu_scene.xml +++ b/projects/MotionLayoutExperiments/app/src/main/res/xml/demo_110_menu_scene.xml @@ -40,15 +40,22 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/projects/MotionLayoutExperiments/app/src/test/java/android/support/constraint/core/test/MotionLayoutUtilsUnitTest.java b/projects/MotionLayoutExperiments/app/src/test/java/android/support/constraint/core/test/MotionLayoutUtilsUnitTest.java new file mode 100644 index 000000000..2ac6861ef --- /dev/null +++ b/projects/MotionLayoutExperiments/app/src/test/java/android/support/constraint/core/test/MotionLayoutUtilsUnitTest.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.support.constraint.core.test; + + +import androidx.constraintlayout.core.motion.utils.CurveFit; +import androidx.constraintlayout.core.motion.utils.Easing; +import androidx.constraintlayout.core.motion.utils.HyperSpline; +import androidx.constraintlayout.core.motion.utils.LinearCurveFit; +import androidx.constraintlayout.core.motion.utils.Oscillator; +import androidx.constraintlayout.motion.utils.StopLogic; + +import org.junit.Test; + +import java.text.DecimalFormat; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class MotionLayoutUtilsUnitTest { + @Test + public void unit_test_framework_working() throws Exception { + assertEquals(4, 2 + 2); + } + + @Test + public void testHyperSpline01() throws Exception { + double[][] points = { + {0, 0}, {1, 1}, {2, 2} + }; + HyperSpline spline = new HyperSpline(points); + double value = spline.getPos(0.5, 1); + assertEquals(1, value, 0.001); + } + + @Test + public void testCurveFit01() throws Exception { + double[][] points = { + {0, 0}, {1, 1}, {2, 0} + }; + double[] time = { + 0, 5, 10 + }; + CurveFit spline = CurveFit.get(CurveFit.SPLINE, time, points); + double value = spline.getPos(5, 0); + assertEquals(1, value, 0.001); + value = spline.getPos(7, 0); + assertEquals(1.4, value, 0.001); + value = spline.getPos(7, 1); + assertEquals(0.744, value, 0.001); + } + + @Test + public void testCurveFit02() throws Exception { + double[][] points = { + {0, 0}, {1, 1}, {2, 0} + }; + double[] time = { + 0, 5, 10 + }; + CurveFit spline = CurveFit.get(CurveFit.LINEAR, time, points); + double value = spline.getPos(5, 0); + assertEquals(1, value, 0.001); + value = spline.getPos(7, 0); + assertEquals(1.4, value, 0.001); + value = spline.getPos(7, 1); + assertEquals(0.6, value, 0.001); + } + + @Test + public void testEasing01() throws Exception { + double value, diffValue; + Easing easing; + easing = Easing.getInterpolator("cubic=(1,1,0,0)"); + value = easing.get(0.5); + assertEquals(0.5, value, 0.001); + diffValue = easing.getDiff(0.5); + assertEquals(1, diffValue, 0.001); + diffValue = easing.getDiff(0.1); + assertEquals(1, diffValue, 0.001); + diffValue = easing.getDiff(0.9); + assertEquals(1, diffValue, 0.001); + + easing = Easing.getInterpolator("cubic=(1,0,0,1)"); + value = easing.get(0.5); + assertEquals(0.5, value, 0.001); + + diffValue = easing.getDiff(0.001); + assertEquals(0, diffValue, 0.001); + diffValue = easing.getDiff(0.9999); + assertEquals(0, diffValue, 0.001); + + easing = Easing.getInterpolator("cubic=(0.5,1,0.5,0)"); + value = easing.get(0.5); + assertEquals(0.5, value, 0.001); + diffValue = easing.getDiff(0.5); + assertEquals(0, diffValue, 0.001); + diffValue = easing.getDiff(0.00001); + assertEquals(2, diffValue, 0.001); + diffValue = easing.getDiff(0.99999); + assertEquals(2, diffValue, 0.001); + + } + + @Test + public void testLinearCurveFit01() throws Exception { + double value, diffValue; + double[][] points = { + {0, 0}, {1, 1}, {2, 0} + }; + double[] time = { + 0, 5, 10 + }; + LinearCurveFit lcurve = new LinearCurveFit(time, points); + value = lcurve.getPos(5, 0); + assertEquals(1, value, 0.001); + value = lcurve.getPos(7, 0); + assertEquals(1.4, value, 0.001); + value = lcurve.getPos(7, 1); + assertEquals(0.6, value, 0.001); + } + + @Test + public void testOscillator01() throws Exception { + Oscillator o = new Oscillator(); + o.setType(Oscillator.SQUARE_WAVE, null); + o.addPoint(0, 0); + o.addPoint(0.5, 10); + o.addPoint(1, 0); + o.normalize(); + assertEquals(19, countZeroCrossings(o, Oscillator.SIN_WAVE)); + assertEquals(19, countZeroCrossings(o, Oscillator.SQUARE_WAVE)); + assertEquals(19, countZeroCrossings(o, Oscillator.TRIANGLE_WAVE)); + assertEquals(19, countZeroCrossings(o, Oscillator.SAW_WAVE)); + assertEquals(19, countZeroCrossings(o, Oscillator.REVERSE_SAW_WAVE)); + assertEquals(20, countZeroCrossings(o, Oscillator.COS_WAVE)); + } + + @Test + public void testStopLogic01() throws Exception { + String []results = { + "[0.4, 0.36, 0.42, 0.578, 0.778, 0.938, 0.999, 1, 1, 1]", + "[0.4, 0.383, 0.464, 0.64, 0.838, 0.967, 1, 1, 1, 1]", + "[0.4, 0.405, 0.509, 0.697, 0.885, 0.986, 1, 1, 1, 1]", + "[0.4, 0.427, 0.553, 0.75, 0.921, 0.997, 1, 1, 1, 1]", + "[0.4, 0.449, 0.598, 0.798, 0.948, 1, 1, 1, 1, 1]", + "[0.4, 0.472, 0.64, 0.838, 0.967, 1, 1, 1, 1, 1]", + "[0.4, 0.494, 0.678, 0.87, 0.981, 1, 1, 1, 1, 1]", + "[0.4, 0.516, 0.71, 0.894, 0.989, 1, 1, 1, 1, 1]", + "[0.4, 0.538, 0.737, 0.913, 0.995, 1, 1, 1, 1, 1]", + "[0.4, 0.56, 0.76, 0.927, 0.998, 1, 1, 1, 1, 1]" + + }; + + for (int i = 0; i < 10; i++) { + float[] f = stopGraph((i - 4) * .1f ); + assertEquals(" test "+i,results[i],arrayToString(f)); + } + } + + private int countZeroCrossings(Oscillator o, int type) { + int n = 1000; + double last = o.getValue(0,0); + int count = 0; + o.setType(type, null); + for (int i = 0; i < n; i++) { + + double v = o.getValue(0.0001 + i / (double) n,0); + if (v * last < 0) { + count++; + } + last = v; + } + return count; + } + String arrayToString(float []f) { + String ret = "["; + DecimalFormat df = new DecimalFormat("###.###"); + for (int i = 0; i < f.length; i++) { + Float aFloat = f[i]; + if (i>0) { + ret += ", "; + } + ret += df.format(f[i]); + } + return ret+"]"; + } + + private static float[] stopGraph(float vel ) { + StopLogic breakLogic = new StopLogic(); + breakLogic.config(.4f, 1f, vel, 1, 2f, 0.9f); + float[] ret = new float[10]; + + for (int i = 0; i < ret.length; i++) { + float time = 2*i / (float) (ret.length - 1); + float pos = breakLogic.getInterpolation(time); + ret[i] = pos; + } + return ret; + } + +} \ No newline at end of file diff --git a/projects/MotionLayoutVerification/app/src/main/java/android/support/constraint/app/CheckSetStates.java b/projects/MotionLayoutVerification/app/src/main/java/android/support/constraint/app/CheckSetStates.java index 6de2f959f..417bc9ebc 100644 --- a/projects/MotionLayoutVerification/app/src/main/java/android/support/constraint/app/CheckSetStates.java +++ b/projects/MotionLayoutVerification/app/src/main/java/android/support/constraint/app/CheckSetStates.java @@ -32,10 +32,10 @@ public class CheckSetStates extends AppCompatActivity { private static final String TAG = "CustomSwipeClick"; String layout_name; MotionLayout mMotionLayout; - int []states = {R.id.s1,R.id.s2,R.id.s3,R.id.s4,R.id.s5,R.id.s6,R.id.s7,R.id.s8,R.id.s9}; + int[] states = {R.id.s1, R.id.s2, R.id.s3, R.id.s4, R.id.s5, R.id.s6, R.id.s7, R.id.s8, R.id.s9}; @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { + protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle extra = getIntent().getExtras(); String prelayout = extra.getString(Utils.KEY); @@ -48,25 +48,40 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { TextView text = findViewById(R.id.text); } - - public void jump1(View view) { + int getNextState() { + int state = 0; + int cstate = mMotionLayout.getCurrentState(); + Log.v(TAG, Debug.getLoc() + " current " + Debug.getName(getApplicationContext(), cstate)); for (int i = 0; i < states.length; i++) { - if (mMotionLayout.getCurrentState() == states[i]) { -// mMotionLayout.setState(states[(i+1)%states.length],-1,-1); - mMotionLayout.transitionToState(states[(i+1)%states.length]); - Log.v(TAG, Debug.getLoc()+" "+i ); - return; + if (cstate == states[i]) { + state = i + 1; + return state % states.length; + } } + return state; + } + + public void jump1(View view) { + int state = getNextState(); + mMotionLayout.transitionToState(states[state]); + Log.v(TAG, Debug.getLoc() + " " + state); + } + public void jump2(View view) { - for (int i = 0; i < states.length; i++) { - if (mMotionLayout.getCurrentState() == states[i]) { - mMotionLayout.setState(states[(i+1)%states.length],-1,-1); - mMotionLayout.setProgress(0); - Log.v(TAG, Debug.getLoc()+" "+i ); - return; - } - } + int state = getNextState(); + int cstate = mMotionLayout.getCurrentState(); + int end = mMotionLayout.getEndState(); + int start = mMotionLayout.getStartState(); + Log.v(TAG, Debug.getLoc() + "set " + Debug.getName(getApplicationContext(), start) + + " -> " + Debug.getName(getApplicationContext(), end) + + " now " + Debug.getName(getApplicationContext(), cstate)); + //mMotionLayout.transitionToState(states[state], 1); + mMotionLayout.jumpToState(states[state]); + cstate = mMotionLayout.getCurrentState(); + Log.v(TAG, Debug.getLoc() + "set " + Debug.getName(getApplicationContext(), states[state]) + + " = " + Debug.getName(getApplicationContext(), cstate)); + } } diff --git a/projects/MotionLayoutVerification/app/src/main/java/android/support/constraint/app/VerificationActivity.java b/projects/MotionLayoutVerification/app/src/main/java/android/support/constraint/app/VerificationActivity.java index d1b194baa..6e5d3c23c 100644 --- a/projects/MotionLayoutVerification/app/src/main/java/android/support/constraint/app/VerificationActivity.java +++ b/projects/MotionLayoutVerification/app/src/main/java/android/support/constraint/app/VerificationActivity.java @@ -97,8 +97,8 @@ public class VerificationActivity extends AppCompatActivity implements View.OnCl private static boolean REVERSE = false; - private final String RUN_FIRST = "verification_450"; - private final String LAYOUTS_MATCHES = "verification_\\d+"; + private final String RUN_FIRST = "verification_501"; + private final String LAYOUTS_MATCHES = "v.*_.*"; private static String SHOW_FIRST = ""; MotionLayout mMotionLayout; diff --git a/projects/MotionLayoutVerification/app/src/main/res/drawable/ic_baseline_redo_24.xml b/projects/MotionLayoutVerification/app/src/main/res/drawable/ic_baseline_redo_24.xml new file mode 100644 index 000000000..37e9b2a72 --- /dev/null +++ b/projects/MotionLayoutVerification/app/src/main/res/drawable/ic_baseline_redo_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/projects/MotionLayoutVerification/app/src/main/res/drawable/ic_baseline_undo_24.xml b/projects/MotionLayoutVerification/app/src/main/res/drawable/ic_baseline_undo_24.xml new file mode 100644 index 000000000..bb1b6293c --- /dev/null +++ b/projects/MotionLayoutVerification/app/src/main/res/drawable/ic_baseline_undo_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/projects/MotionLayoutVerification/app/src/main/res/layout/verification_041.xml b/projects/MotionLayoutVerification/app/src/main/res/layout/verification_041.xml index 1b6288f1f..26933ddd3 100644 --- a/projects/MotionLayoutVerification/app/src/main/res/layout/verification_041.xml +++ b/projects/MotionLayoutVerification/app/src/main/res/layout/verification_041.xml @@ -61,6 +61,7 @@ android:layout_marginTop="64dp" android:background="#A66" android:onClick="click" + app:overlay="false" android:src="@drawable/hoford" app:altSrc="@drawable/avatar_5_raster" app:layout_constraintEnd_toEndOf="parent" diff --git a/projects/MotionLayoutVerification/app/src/main/res/layout/verification_057.xml b/projects/MotionLayoutVerification/app/src/main/res/layout/verification_057.xml index 37fb6286b..ae79d7cf4 100644 --- a/projects/MotionLayoutVerification/app/src/main/res/layout/verification_057.xml +++ b/projects/MotionLayoutVerification/app/src/main/res/layout/verification_057.xml @@ -14,58 +14,58 @@