57 | *
The callback runs once then is automatically removed.
58 | */ 59 | public abstract void postFrameCallback(FrameCallback callback); 60 | 61 | /** 62 | * Post a frame callback to run on the next frame after the specified delay. 63 | *64 | *
The callback runs once then is automatically removed.
65 | */ 66 | public abstract void postFrameCallbackDelayed(FrameCallback callback, long delayMillis); 67 | 68 | /** 69 | * Remove a previously posted frame callback. 70 | */ 71 | public abstract void removeFrameCallback(FrameCallback callback); 72 | 73 | 74 | /** 75 | * A callback that will occur on a future drawing frame. This is a compatible version of {@link 76 | * android.view.Choreographer.FrameCallback}. 77 | */ 78 | public abstract static class FrameCallback { 79 | private Runnable runnable; 80 | private Choreographer.FrameCallback realCallback; 81 | 82 | public abstract void doFrame(long frameTimeNanos); 83 | 84 | @TargetApi(VERSION_CODES.JELLY_BEAN) 85 | Choreographer.FrameCallback getRealCallback() { 86 | if (realCallback == null) { 87 | realCallback = new Choreographer.FrameCallback() { 88 | @Override 89 | public void doFrame(long frameTimeNanos) { 90 | FrameCallback.this.doFrame(frameTimeNanos); 91 | } 92 | }; 93 | } 94 | 95 | return realCallback; 96 | } 97 | 98 | Runnable getRunnable() { 99 | if (runnable == null) { 100 | runnable = new Runnable() { 101 | @Override 102 | public void run() { 103 | doFrame(System.nanoTime()); 104 | } 105 | }; 106 | } 107 | 108 | return runnable; 109 | } 110 | } 111 | 112 | /** 113 | * A {@link ChoreographerCompat} that just wraps a real {@link Choreographer}, for use on API 114 | * versions that support it. 115 | */ 116 | @TargetApi(VERSION_CODES.JELLY_BEAN) 117 | private static class RealChoreographer extends ChoreographerCompat { 118 | private Choreographer choreographer; 119 | 120 | public RealChoreographer() { 121 | choreographer = Choreographer.getInstance(); 122 | } 123 | 124 | @Override 125 | public void postFrameCallback(FrameCallback callback) { 126 | choreographer.postFrameCallback(callback.getRealCallback()); 127 | } 128 | 129 | @Override 130 | public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) { 131 | choreographer.postFrameCallbackDelayed(callback.getRealCallback(), delayMillis); 132 | } 133 | 134 | @Override 135 | public void removeFrameCallback(FrameCallback callback) { 136 | choreographer.removeFrameCallback(callback.getRealCallback()); 137 | } 138 | } 139 | 140 | /** 141 | * A {@link ChoreographerCompat} that wraps a {@link Handler} and emulates (at a basic level, 142 | * anyway) the behavior of a {@link Choreographer}. 143 | */ 144 | private static class LegacyHandlerWrapper extends ChoreographerCompat { 145 | private static final long FRAME_TIME_MS = 17; 146 | private Handler handler; 147 | 148 | public LegacyHandlerWrapper(Looper looper) { 149 | handler = new Handler(looper); 150 | } 151 | 152 | @Override 153 | public void postFrameCallback(FrameCallback callback) { 154 | handler.postDelayed(callback.getRunnable(), 0); 155 | } 156 | 157 | @Override 158 | public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) { 159 | handler.postDelayed(callback.getRunnable(), delayMillis + FRAME_TIME_MS); 160 | } 161 | 162 | @Override 163 | public void removeFrameCallback(FrameCallback callback) { 164 | handler.removeCallbacks(callback.getRunnable()); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/physics/Force.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.physics; 17 | 18 | import com.google.android.material.motion.physics.math.Vector; 19 | 20 | /** 21 | * A dynamic Force that acts on an object. Usually models a force in real life. 22 | */ 23 | public interface Force { 24 | 25 | /** 26 | * Calculates the acceleration this Force applies on an object. 27 | * 28 | * @param x The object's position. 29 | * @param v The object's velocity. 30 | * @param t The time elapsed since this Force was first active. 31 | */ 32 | Vector acceleration(Vector x, Vector v, double t); 33 | 34 | /** 35 | * Calculates the potential energy of this Force on an object. The potential energy of a force 36 | * which is a function of an object's position is the integral of the force function with 37 | * respect to position. 38 | *39 | * Return {@link Integrator#SOME_ENERGY} if the potential energy of this force is difficult or 40 | * unrealistic to calculate, but is a non-trivial amount. 41 | *
42 | * Return {@link Integrator#NO_ENERGY} if this force does not have potential energy or only a 43 | * trivial amount. For example, if the force is not a function of the object's position. 44 | * 45 | * @param x The object's position. 46 | * @param t The time elapsed since this Force was first active. 47 | */ 48 | float potentialEnergy(Vector x, double t); 49 | } 50 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/physics/Integrator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.physics; 17 | 18 | import android.content.Context; 19 | import android.os.Build.VERSION; 20 | import android.os.Build.VERSION_CODES; 21 | import android.provider.Settings.Global; 22 | import android.support.annotation.Nullable; 23 | import android.support.annotation.VisibleForTesting; 24 | import android.support.v4.util.SimpleArrayMap; 25 | 26 | import com.google.android.material.motion.physics.ChoreographerCompat.FrameCallback; 27 | import com.google.android.material.motion.physics.math.Vector; 28 | 29 | import java.util.Locale; 30 | import java.util.concurrent.CopyOnWriteArrayList; 31 | 32 | import static com.google.android.material.motion.physics.math.Vectors.dot; 33 | 34 | /** 35 | * An integrator that runs an ongoing physics simulation of an object with multiple forced being 36 | * applied to it. 37 | */ 38 | public abstract class Integrator { 39 | 40 | /** 41 | * A Listener that receives notifications from a physics simulation, including on every 42 | * animation frame. 43 | */ 44 | public interface Listener { 45 | 46 | /** 47 | * Notifies the start of the physics simulation. 48 | */ 49 | void onStart(); 50 | 51 | /** 52 | * Notifies the occurrence of another frame of the physics simulation. This is called after 53 | * the current frame's values have been calculated. 54 | * 55 | * @param x The position of the object. 56 | * @param v The velocity of the object. 57 | */ 58 | void onUpdate(Vector x, Vector v); 59 | 60 | /** 61 | * Notifies the physics simulation coming to a settled state. This is called after the total 62 | * energy of the system is below the energy threshold. 63 | */ 64 | void onSettle(); 65 | 66 | /** 67 | * Notifies the end of the physics simulation. This is called upon reaching a settled state, 68 | * or if {@link #stop()} is called externally. 69 | */ 70 | void onStop(); 71 | } 72 | 73 | /** 74 | * This class provides empty implementations of the methods from {@link Listener}. Any custom 75 | * listener that cares only about a subset of the methods of this listener can simply subclass 76 | * this adapter class instead of implementing the interface directly. 77 | */ 78 | public abstract static class SimpleListener implements Listener { 79 | 80 | @Override 81 | public void onStart() { 82 | } 83 | 84 | @Override 85 | public void onUpdate(Vector x, Vector v) { 86 | } 87 | 88 | @Override 89 | public void onSettle() { 90 | } 91 | 92 | @Override 93 | public void onStop() { 94 | } 95 | } 96 | 97 | public static final float MASS = 1f; 98 | /** 99 | * The minimum amount of energy required to continue the physics simulation. 100 | *
101 | * This value can be returned from {@link Force#potentialEnergy(Vector, double)} to signify that
102 | * the force acting on the given object constitutes some non-trivial amount of potential
103 | * energy.
104 | */
105 | public static final float SOME_ENERGY = 1f;
106 | /**
107 | * This value can be returned from {@link Force#potentialEnergy(Vector, double)} to signify that
108 | * the force acting on the given object constitutes zero or a trivial amount of potential
109 | * energy.
110 | */
111 | public static final float NO_ENERGY = 0f;
112 |
113 | private static final float FRAME = 1f / 60f;
114 | private static final float MAX_FRAMES_TO_SIMULATE = 4;
115 | private static final float MAX_DELTA = MAX_FRAMES_TO_SIMULATE * FRAME;
116 |
117 | private final ChoreographerCompat choreographer = ChoreographerCompat.getInstance();
118 | private final CopyOnWriteArrayList
49 | * The damping coefficient is chosen so that for all initial states:
191 | * Returns true if {@code a} is less than or equal to {@code b}, allowing for {@code epsilon}
192 | * error due to limitations in floating point accuracy.
193 | *
194 | * Does not handle overflow, underflow, infinity, or NaN.
195 | */
196 | public static boolean leq(float a, float b, float epsilon) {
197 | return a <= b + epsilon;
198 | }
199 |
200 | /**
201 | * Fuzzy greater than or equal to for floats.
202 | *
203 | * Returns true if {@code a} is greater than or equal to {@code b}, allowing for {@code epsilon}
204 | * error due to limitations in floating point accuracy.
205 | *
206 | * Does not handle overflow, underflow, infinity, or NaN.
207 | */
208 | public static boolean geq(float a, float b, float epsilon) {
209 | return a + epsilon >= b;
210 | }
211 |
212 | /**
213 | * Fuzzy equal to for floats.
214 | *
215 | * Returns true if {@code a} is equal to {@code b}, allowing for {@code epsilon} error due to
216 | * limitations in floating point accuracy.
217 | *
218 | * Does not handle overflow, underflow, infinity, or NaN.
219 | */
220 | public static boolean eq(float a, float b, float epsilon) {
221 | return abs(a - b) <= epsilon;
222 | }
223 |
224 | /**
225 | * Fuzzy not equal to for floats.
226 | *
227 | * Returns false if {@code a} is equal to {@code b}, allowing for {@code epsilon} error due to
228 | * limitations in floating point accuracy.
229 | *
230 | * Does not handle overflow, underflow, infinity, or NaN.
231 | */
232 | public static boolean neq(float a, float b, float epsilon) {
233 | return abs(a - b) > epsilon;
234 | }
235 |
236 | /**
237 | * Returns the furthest distance from the point defined by pointX and pointY to the four corners
238 | * of the rectangle defined by rectLeft, rectTop, rectRight, and rectBottom.
239 | *
240 | * The caller should ensure that the point and rectangle share the same coordinate space.
241 | */
242 | public static float distanceToFurthestCorner(
243 | float pointX,
244 | float pointY,
245 | float rectLeft,
246 | float rectTop,
247 | float rectRight,
248 | float rectBottom) {
249 | return max(
250 | dist(pointX, pointY, rectLeft, rectTop),
251 | dist(pointX, pointY, rectRight, rectTop),
252 | dist(pointX, pointY, rectRight, rectBottom),
253 | dist(pointX, pointY, rectLeft, rectBottom));
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/library/src/main/java/com/google/android/material/motion/physics/math/Vector.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-present The Material Motion Authors. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.google.android.material.motion.physics.math;
17 |
18 | import android.support.annotation.NonNull;
19 | import android.support.annotation.Nullable;
20 |
21 | import java.util.Arrays;
22 |
23 | /**
24 | * A general purpose one dimensional vector class. The
29 | * The only exception is the zero-sized vector. It represents an arbitrarily sized vector with all
30 | * zero values.
31 | */
32 | public class Vector {
33 | private int size;
34 | @Nullable
35 | private float[] values;
36 |
37 | public Vector() {
38 | this.size = 0;
39 | this.values = null;
40 | }
41 |
42 | public Vector(Vector vector) {
43 | if (vector.size == 0) {
44 | this.size = 0;
45 | this.values = null;
46 | return;
47 | }
48 | this.size = vector.size;
49 | this.values = new float[size];
50 | System.arraycopy(checkNotNull(vector.values), 0, values, 0, size);
51 | }
52 |
53 | public Vector(float... values) {
54 | this.size = values.length;
55 | this.values = new float[size];
56 | System.arraycopy(values, 0, this.values, 0, size);
57 | }
58 |
59 | public int getSize() {
60 | return size;
61 | }
62 |
63 | public float[] getValues() {
64 | return checkNotNull(values);
65 | }
66 |
67 | public float getValue(int index) {
68 | if (size == 0) {
69 | return 0f;
70 | }
71 | return checkNotNull(values)[index];
72 | }
73 |
74 | /**
75 | * Sets this vector to the given vector's values.
76 | */
77 | public Vector set(Vector other) {
78 | if (other.size == 0) {
79 | size = 0;
80 | values = null;
81 | return this;
82 | }
83 | if (size == 0) {
84 | size = other.size;
85 | values = new float[size];
86 | }
87 | checkArgument(size == other.size);
88 | System.arraycopy(
89 | checkNotNull(other.values), 0, checkNotNull(values), 0, size);
90 | return this;
91 | }
92 |
93 | /**
94 | * Sets this vector to the given values.
95 | */
96 | public Vector set(float... values) {
97 | if (size == 0) {
98 | size = values.length;
99 | this.values = new float[size];
100 | }
101 | checkArgument(size == values.length);
102 | System.arraycopy(values, 0, checkNotNull(this.values), 0, size);
103 | return this;
104 | }
105 |
106 | /**
107 | * Sets this vector to all zero values.
108 | */
109 | public Vector clear() {
110 | if (size > 0) {
111 | Arrays.fill(checkNotNull(values), 0, size, 0f);
112 | }
113 | return this;
114 | }
115 |
116 | /**
117 | * Returns
221 | * If this is a zero vector, the normalization results in a zero vector.
222 | */
223 | public Vector normalize() {
224 | return normalize(this);
225 | }
226 |
227 | /**
228 | * Sets this vector to
230 | * If this is a zero vector, its normalization results in a zero vector.
231 | */
232 | public Vector normalize(Vector v) {
233 | if (v.size == 0) {
234 | size = 0;
235 | values = null;
236 | return this;
237 | }
238 |
239 | if (size == 0) {
240 | size = v.size;
241 | values = new float[size];
242 | }
243 | checkArgument(size == v.size);
244 | float magnitude = v.magnitude();
245 | if (magnitude == 0f) {
246 | clear();
247 | return this;
248 | }
249 |
250 | float[] vValues = checkNotNull(v.values);
251 | for (int i = 0; i < v.size; i++) {
252 | values[i] = vValues[i] / magnitude;
253 | }
254 | return this;
255 | }
256 |
257 | /**
258 | * Returns whether this vector is normalized within the given epsilon error.
259 | *
260 | * A zero vector is considered normalized.
261 | */
262 | public boolean isNormalized(float epsilon) {
263 | float magnitude = magnitude();
264 | return MathUtils.eq(magnitude, 1f, epsilon) || MathUtils.eq(magnitude, 0f, epsilon);
265 | }
266 |
267 | /**
268 | * Returns whether this vector is a zero vector within the given epsilon error.
269 | */
270 | public boolean isZero(float epsilon) {
271 | float magnitude = magnitude();
272 | return MathUtils.eq(magnitude, 0f, epsilon);
273 | }
274 |
275 | /**
276 | * Returns
278 | *
279 | * If this is a zero vector, the projection of the given vector onto this results in itself.
280 | */
281 | public Vector proj(Vector b) {
282 | return proj(this, b);
283 | }
284 |
285 | /**
286 | * Sets this vector to
288 | *
289 | * If a is a zero vector, the projection of b onto a results in itself.
290 | */
291 | public Vector proj(Vector a, Vector b) {
292 | if (a.size == 0) {
293 | set(b);
294 | return this;
295 | }
296 | if (b.size == 0) {
297 | clear();
298 | return this;
299 | }
300 |
301 | if (size == 0) {
302 | size = a.size;
303 | values = new float[size];
304 | }
305 | checkArgument(size == a.size);
306 | checkArgument(size == b.size);
307 | float aa = Vectors.dot(a, a);
308 | if (aa == 0f) {
309 | set(b);
310 | return this;
311 | }
312 |
313 | float ab = Vectors.dot(a, b);
314 | float scalar = ab / aa;
315 | scale(a, scalar);
316 | return this;
317 | }
318 |
319 | /**
320 | * Returns
341 | * Zero vectors are considered to have 0f angle with any other vector.
342 | */
343 | public float angle(Vector other) {
344 | checkState(isNormalized(MathUtils.DEFAULT_EPSILON));
345 | checkState(other.isNormalized(MathUtils.DEFAULT_EPSILON));
346 |
347 | if (isZero(MathUtils.DEFAULT_EPSILON) || other.isZero(MathUtils.DEFAULT_EPSILON)) {
348 | return 0f;
349 | }
350 |
351 | return MathUtils.acos(dot(other));
352 | }
353 |
354 | /**
355 | * Returns
358 | * Zero vectors are considered to have 0f angle with any other vector.
359 | */
360 | public float angleWithNormalization(Vector other) {
361 | normalize();
362 | other.normalize();
363 | return angle(other);
364 | }
365 |
366 | /**
367 | * Returns the unsigned distance between this vector and the given vector.
368 | *
369 | * This is identical to
31 | * This class helps reduce the amount of boilerplate needed to use a ViewDragHelper.
32 | */
33 | public class DragFrameLayout extends FrameLayout {
34 |
35 | private final ViewDragHelper helper;
36 | @Nullable
37 | private Callback callback;
38 |
39 | public DragFrameLayout(Context context) {
40 | this(context, null);
41 | }
42 |
43 | public DragFrameLayout(Context context, AttributeSet attrs) {
44 | super(context, attrs);
45 |
46 | helper =
47 | ViewDragHelper.create(
48 | this,
49 | new Callback() {
50 | @Override
51 | public boolean tryCaptureView(View child, int pointerId) {
52 | if (callback != null) {
53 | return callback.tryCaptureView(child, pointerId);
54 | }
55 | return true;
56 | }
57 |
58 | @Override
59 | public void onViewDragStateChanged(int state) {
60 | if (callback != null) {
61 | callback.onViewDragStateChanged(state);
62 | }
63 | }
64 |
65 | @Override
66 | public void onViewPositionChanged(
67 | View changedView, int left, int top, int dx, int dy) {
68 | if (callback != null) {
69 | callback.onViewPositionChanged(changedView, left, top, dx, dy);
70 | }
71 | }
72 |
73 | @Override
74 | public void onViewCaptured(View capturedChild, int activePointerId) {
75 | if (callback != null) {
76 | callback.onViewCaptured(capturedChild, activePointerId);
77 | }
78 | }
79 |
80 | @Override
81 | public void onViewReleased(View releasedChild, float xvel, float yvel) {
82 | if (callback != null) {
83 | callback.onViewReleased(releasedChild, xvel, yvel);
84 | }
85 | }
86 |
87 | @Override
88 | public void onEdgeTouched(int edgeFlags, int pointerId) {
89 | if (callback != null) {
90 | callback.onEdgeTouched(edgeFlags, pointerId);
91 | }
92 | }
93 |
94 | @Override
95 | public boolean onEdgeLock(int edgeFlags) {
96 | if (callback != null) {
97 | return callback.onEdgeLock(edgeFlags);
98 | }
99 | return super.onEdgeLock(edgeFlags);
100 | }
101 |
102 | @Override
103 | public void onEdgeDragStarted(int edgeFlags, int pointerId) {
104 | if (callback != null) {
105 | callback.onEdgeDragStarted(edgeFlags, pointerId);
106 | }
107 | }
108 |
109 | @Override
110 | public int getOrderedChildIndex(int index) {
111 | if (callback != null) {
112 | return callback.getOrderedChildIndex(index);
113 | }
114 | return super.getOrderedChildIndex(index);
115 | }
116 |
117 | @Override
118 | public int getViewHorizontalDragRange(View child) {
119 | if (callback != null) {
120 | return callback.getViewHorizontalDragRange(child);
121 | }
122 | return super.getViewHorizontalDragRange(child);
123 | }
124 |
125 | @Override
126 | public int getViewVerticalDragRange(View child) {
127 | if (callback != null) {
128 | return callback.getViewVerticalDragRange(child);
129 | }
130 | return super.getViewVerticalDragRange(child);
131 | }
132 |
133 | @Override
134 | public int clampViewPositionHorizontal(View child, int left, int dx) {
135 | if (callback != null) {
136 | return callback.clampViewPositionHorizontal(child, left, dx);
137 | }
138 | return super.clampViewPositionHorizontal(child, left, dx);
139 | }
140 |
141 | @Override
142 | public int clampViewPositionVertical(View child, int top, int dy) {
143 | if (callback != null) {
144 | return callback.clampViewPositionVertical(child, top, dy);
145 | }
146 | return super.clampViewPositionVertical(child, top, dy);
147 | }
148 | });
149 | }
150 |
151 | public void setCallback(@Nullable Callback callback) {
152 | this.callback = callback;
153 | }
154 |
155 | @Override
156 | public boolean onInterceptTouchEvent(MotionEvent ev) {
157 | final int action = MotionEventCompat.getActionMasked(ev);
158 | if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
159 | helper.cancel();
160 | return false;
161 | }
162 | return helper.shouldInterceptTouchEvent(ev);
163 | }
164 |
165 | @Override
166 | public boolean onTouchEvent(MotionEvent ev) {
167 | helper.processTouchEvent(ev);
168 | return true;
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/google/android/material/motion/physics/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.google.android.material.motion.physics.sample;
17 |
18 | import android.os.Bundle;
19 | import android.support.v4.widget.ViewDragHelper.Callback;
20 | import android.support.v7.app.AppCompatActivity;
21 | import android.view.View;
22 | import android.view.View.OnLayoutChangeListener;
23 |
24 | import com.google.android.material.motion.physics.Integrator;
25 | import com.google.android.material.motion.physics.Integrator.SimpleListener;
26 | import com.google.android.material.motion.physics.forces.AnchoredForce;
27 | import com.google.android.material.motion.physics.forces.Spring;
28 | import com.google.android.material.motion.physics.integrators.Rk4Integrator;
29 | import com.google.android.material.motion.physics.math.Vector;
30 | import com.google.android.material.motion.physics.math.Vectors;
31 |
32 | import java.util.ArrayList;
33 | import java.util.List;
34 |
35 | /**
36 | * Activity that displays a basic demo of dynamic motion in Odeon.
37 | */
38 | public class MainActivity extends AppCompatActivity {
39 |
40 | private final List
229 | * Forces without an anchor view should just be directly added to the {@link
230 | * Integrator}.
231 | */
232 | public Demo with(AnchoredForce>... forces) {
233 | this.forces = forces;
234 | return this;
235 | }
236 | }
237 |
238 | /**
239 | * An {@link Integrator.Listener} that syncs the integrator's state with the target view's
240 | * position. When the integrator settles, it activates the next force.
241 | */
242 | private class TrackingListener extends SimpleListener {
243 |
244 | private final Demo demo;
245 |
246 | public TrackingListener(Demo demo) {
247 | this.demo = demo;
248 | }
249 |
250 | @Override
251 | public void onUpdate(Vector x, Vector v) {
252 | setCenter(demo.target, x.getValue(0), x.getValue(1));
253 | }
254 |
255 | @Override
256 | public void onSettle() {
257 | activateNextForce(demo);
258 | }
259 | }
260 |
261 | /**
262 | * A {@link Callback} that allows both the target and anchor views to be captured. It syncs the
263 | * anchor view's position with the corresponding force's anchor point. When the target view is
264 | * flung, it calculates which force to activate.
265 | */
266 | private class TrackingCallback extends Callback {
267 |
268 | private final Demo demo;
269 |
270 | public TrackingCallback(Demo demo) {
271 | this.demo = demo;
272 | }
273 |
274 | @Override
275 | public boolean tryCaptureView(View child, int pointerId) {
276 | if (child == demo.target) {
277 | return true;
278 | }
279 | for (View anchor : demo.anchors) {
280 | if (child == anchor) {
281 | return true;
282 | }
283 | }
284 | return false;
285 | }
286 |
287 | @Override
288 | public void onViewCaptured(View capturedChild, int activePointerId) {
289 | demo.container.getParent().requestDisallowInterceptTouchEvent(true);
290 | if (capturedChild == demo.target) {
291 | demo.integrator.stop();
292 | }
293 | }
294 |
295 | @Override
296 | public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
297 | for (int i = 0; i < demo.anchors.length; i++) {
298 | View anchor = demo.anchors[i];
299 | if (changedView == anchor) {
300 | AnchoredForce> force = demo.forces[i];
301 | force.setAnchorPoint(new Vector(getCenterX(anchor), getCenterY(anchor)));
302 | break;
303 | }
304 | }
305 | }
306 |
307 | @Override
308 | public void onViewReleased(View releasedChild, float xvel, float yvel) {
309 | if (releasedChild == demo.target) {
310 | demo.integrator.setState(
311 | new Vector(getCenterX(releasedChild), getCenterY(releasedChild)),
312 | new Vector(xvel, yvel));
313 | demo.integrator.start();
314 | }
315 | }
316 |
317 | @Override
318 | public int clampViewPositionHorizontal(View child, int left, int dx) {
319 | return left;
320 | }
321 |
322 | @Override
323 | public int clampViewPositionVertical(View child, int top, int dy) {
324 | return top;
325 | }
326 | }
327 | }
328 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/anchor1.xml:
--------------------------------------------------------------------------------
1 |
2 | current
and previous
, then return an interpolated State.
303 | */
304 | protected abstract State onFrame(
305 | double deltaTime, State current, State previous, float animatorDurationScale);
306 |
307 | protected final Vector acceleration(State state, Vector out) {
308 | out.clear();
309 | for (int i = 0, count = forces.size(); i < count; i++) {
310 | Force force = forces.keyAt(i);
311 | double initialTime = forces.valueAt(i);
312 | out.add(force.acceleration(state.x, state.v, state.t - initialTime));
313 | }
314 | return out;
315 | }
316 |
317 | @VisibleForTesting
318 | final boolean hasKineticEnergyGreaterThan(State state, float energy) {
319 | return 0.5f * MASS * dot(state.v, state.v) > energy;
320 | }
321 |
322 | @VisibleForTesting
323 | final boolean hasPotentialEnergyGreaterThan(State state, float energy) {
324 | for (int i = 0, count = forces.size(); i < count; i++) {
325 | Force force = forces.keyAt(i);
326 | double initialTime = forces.valueAt(i);
327 | float potentialEnergy = force.potentialEnergy(state.x, state.t - initialTime);
328 | if (potentialEnergy > energy) {
329 | return true;
330 | }
331 | }
332 | return false;
333 | }
334 |
335 | private final FrameCallback frameCallback =
336 | new FrameCallback() {
337 | private static final double NANOS_PER_SECOND = 1000000000.0;
338 |
339 | @Override
340 | public void doFrame(long frameTimeNanos) {
341 | isScheduled = false;
342 | Integrator.this.doFrame(frameTimeNanos / NANOS_PER_SECOND);
343 | }
344 | };
345 |
346 | /**
347 | * A data holder for a position
and velocity
at time t
.
348 | */
349 | public static class State {
350 | public final Vector x = new Vector();
351 | public final Vector v = new Vector();
352 | public double t;
353 |
354 | public State set(State other) {
355 | this.x.set(other.x);
356 | this.v.set(other.v);
357 | this.t = other.t;
358 | return this;
359 | }
360 |
361 | @Override
362 | public String toString() {
363 | return String.format(Locale.US, "%s\nx=%s\nv=%s", State.class.getSimpleName(), x, v);
364 | }
365 | }
366 |
367 | /**
368 | * A data holder for rate of change. This is used to calculate intermediary {@link State
369 | * States}.
370 | */
371 | public static class Derivative {
372 | public final Vector dx = new Vector();
373 | public final Vector dv = new Vector();
374 |
375 | @Override
376 | public String toString() {
377 | return String.format(Locale.US, "%s\ndx=%s\ndv=%s", State.class.getSimpleName(), dx, dv);
378 | }
379 | }
380 | }
381 |
--------------------------------------------------------------------------------
/library/src/main/java/com/google/android/material/motion/physics/forces/AnchoredForce.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-present The Material Motion Authors. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.google.android.material.motion.physics.forces;
17 |
18 | import com.google.android.material.motion.physics.Force;
19 | import com.google.android.material.motion.physics.math.Vector;
20 |
21 | /**
22 | * A force with an intrinsic anchor point. The direction and magnitude that this force acts upon an
23 | * object is usually a function of the displacement between that object and the anchor point.
24 | *
25 | * @param k
and damping coefficient
25 | * b
. All springs assume zero resting distance from the anchor point.
26 | */
27 | public class Spring extends AnchoredForce
52 | */
53 | public static Spring createCriticallyDamped(float k) {
54 | Spring spring = new Spring();
55 | spring.k = k;
56 | spring.b = (float) Math.sqrt(4 * Integrator.MASS * k);
57 |
58 | return spring;
59 | }
60 |
61 | /**
62 | * Creates a viscous frictional force that opposes the object's velocity.
63 | */
64 | public static Spring createFriction(float mu) {
65 | Spring spring = new Spring();
66 | spring.k = 0f;
67 | spring.b = mu;
68 |
69 | return spring;
70 | }
71 |
72 | @Override
73 | public Vector acceleration(Vector x, Vector v, double t) {
74 | Vector displacement = displacement(x, tmp2);
75 |
76 | // Vector tension = -k * displacement;
77 | Vector tension = tmp1;
78 | tension.scale(displacement, -k);
79 |
80 | // Vector damping = -b * v;
81 | Vector damping = tmp2;
82 | damping.scale(v, -b);
83 |
84 | // Vector force = tension + damping;
85 | Vector force = tension.add(damping);
86 | return force.scale(1f / Integrator.MASS);
87 | }
88 |
89 | @Override
90 | public float potentialEnergy(Vector x, double t) {
91 | Vector displacement = displacement(x, tmp2);
92 | return 0.5f * k * dot(displacement, displacement);
93 | }
94 |
95 | public static float tensionFromOrigamiValue(float value) {
96 | return value == 0f ? 0f : (value - 30f) * 3.62f + 194f;
97 | }
98 |
99 | public static float frictionFromOrigamiValue(float value) {
100 | return value == 0f ? 0f : (value - 8f) * 3f + 25f;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/library/src/main/java/com/google/android/material/motion/physics/integrators/Rk4Integrator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-present The Material Motion Authors. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.google.android.material.motion.physics.integrators;
17 |
18 | import android.content.Context;
19 |
20 | import com.google.android.material.motion.physics.Integrator;
21 | import com.google.android.material.motion.physics.math.Vector;
22 |
23 | /**
24 | * An integrator based on the Runge-Kutta method.
25 | */
26 | public class Rk4Integrator extends Integrator {
27 | private static final float FRAME = 1f / 60f;
28 | /**
29 | * Experimentally determined. Lowest value with acceptable accuracy.
30 | */
31 | private static final float ITERATIONS_PER_FRAME = 2;
32 | private static final float DT = (1 / ITERATIONS_PER_FRAME) * FRAME;
33 |
34 | private final State tmpState = new State();
35 | private final Derivative tmpDerivative1 = new Derivative();
36 | private final Derivative tmpDerivative2 = new Derivative();
37 | private final Derivative tmpDerivative3 = new Derivative();
38 | private final Derivative tmpDerivative4 = new Derivative();
39 | private final Vector tmpVector1 = new Vector();
40 | private final Vector tmpVector2 = new Vector();
41 |
42 | private double accumulator;
43 |
44 | public Rk4Integrator() {
45 | super();
46 | }
47 |
48 | public Rk4Integrator(Context context) {
49 | super(context);
50 | }
51 |
52 | @Override
53 | protected State onFrame(
54 | double deltaTime, State current, State previous, float animatorDurationScale) {
55 | deltaTime = deltaTime / animatorDurationScale;
56 | float dt = DT / animatorDurationScale;
57 |
58 | accumulator += deltaTime;
59 |
60 | while (accumulator >= dt) {
61 | accumulator -= dt;
62 | previous.set(current);
63 | integrate(current, dt);
64 | }
65 |
66 | return interpolate(previous, current, (float) (accumulator / dt), tmpState);
67 | }
68 |
69 | private void integrate(State state, float dt) {
70 | Derivative a = evaluate(state, tmpDerivative1);
71 | Derivative b = evaluate(state, dt * 0.5f, a, tmpDerivative2);
72 | Derivative c = evaluate(state, dt * 0.5f, b, tmpDerivative3);
73 | Derivative d = evaluate(state, dt, c, tmpDerivative4);
74 |
75 | // Vector dxdt = 1.0f / 6.0f * (a.dx + 2.0f * (b.dx + c.dx) + d.dx);
76 | Vector dxdt = tmpVector1;
77 | dxdt.add(b.dx, c.dx);
78 | dxdt.scale(2.0f);
79 | dxdt.add(a.dx);
80 | dxdt.add(d.dx);
81 | dxdt.scale(1.0f / 6.0f);
82 |
83 | // Vector dvdt = 1.0f / 6.0f * (a.dv + 2.0f * (b.dv + c.dv) + d.dv);
84 | Vector dvdt = tmpVector2;
85 | dvdt.add(b.dv, c.dv);
86 | dvdt.scale(2.0f);
87 | dvdt.add(a.dv);
88 | dvdt.add(d.dv);
89 | dvdt.scale(1.0f / 6.0f);
90 |
91 | // state.x = state.x + dxdt * dt;
92 | dxdt.scale(dt);
93 | state.x.add(dxdt);
94 |
95 | // state.v = state.v + dvdt * dt;
96 | dvdt.scale(dt);
97 | state.v.add(dvdt);
98 |
99 | // state.t = state.t + dt;
100 | state.t += dt;
101 | }
102 |
103 | private Derivative evaluate(State initial, Derivative out) {
104 | out.dx.set(initial.v);
105 | out.dv.set(acceleration(initial, tmpVector1));
106 | return out;
107 | }
108 |
109 | private Derivative evaluate(State initial, float dt, Derivative d, Derivative out) {
110 | State state = tmpState;
111 |
112 | // state.x = initial.x + d.dx * dt;
113 | Vector x = state.x;
114 | x.scale(d.dx, dt);
115 | x.add(initial.x);
116 |
117 | // state.v = initial.v + d.dv * dt;
118 | Vector v = state.v;
119 | v.scale(d.dv, dt);
120 | v.add(initial.v);
121 |
122 | state.t = initial.t + dt;
123 |
124 | out.dx.set(state.v);
125 | out.dv.set(acceleration(state, tmpVector1));
126 | return out;
127 | }
128 |
129 | private State interpolate(State previous, State current, float alpha, State out) {
130 | // out.x = current.x * alpha + previous.x * (1 - alpha);
131 | Vector x = out.x;
132 | x.scale(current.x, alpha);
133 | tmpVector1.scale(previous.x, 1 - alpha);
134 | x.add(tmpVector1);
135 |
136 | // out.v = current.v * alpha + previous.v * (1 - alpha);
137 | Vector v = out.v;
138 | v.scale(current.v, alpha);
139 | tmpVector1.scale(previous.v, 1 - alpha);
140 | v.add(tmpVector1);
141 |
142 | out.t = current.t * alpha + previous.t * (1 - alpha);
143 |
144 | return out;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/library/src/main/java/com/google/android/material/motion/physics/math/MathUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-present The Material Motion Authors. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.google.android.material.motion.physics.math;
17 |
18 | /**
19 | * A class that contains utility methods related to numbers.
20 | */
21 | public final class MathUtils {
22 |
23 | /**
24 | * Default epsilon value for fuzzy float comparisons.
25 | */
26 | public static final float DEFAULT_EPSILON = 0.0001f;
27 |
28 | private static final float DEG_TO_RAD = 3.1415926f / 180.0f;
29 | private static final float RAD_TO_DEG = 180.0f / 3.1415926f;
30 |
31 | private MathUtils() {
32 | }
33 |
34 | public static float abs(float v) {
35 | return v > 0 ? v : -v;
36 | }
37 |
38 | public static int constrain(int amount, int low, int high) {
39 | return amount < low ? low : (amount > high ? high : amount);
40 | }
41 |
42 | public static long constrain(long amount, long low, long high) {
43 | return amount < low ? low : (amount > high ? high : amount);
44 | }
45 |
46 | public static float constrain(float amount, float low, float high) {
47 | return amount < low ? low : (amount > high ? high : amount);
48 | }
49 |
50 | public static int nearest(int amount, int low, int high) {
51 | return abs(amount - low) < abs(amount - high) ? low : high;
52 | }
53 |
54 | public static float nearest(float amount, float low, float high) {
55 | return abs(amount - low) < abs(amount - high) ? low : high;
56 | }
57 |
58 | public static float log(float a) {
59 | return (float) Math.log(a);
60 | }
61 |
62 | public static float exp(float a) {
63 | return (float) Math.exp(a);
64 | }
65 |
66 | public static float pow(float a, float b) {
67 | return (float) Math.pow(a, b);
68 | }
69 |
70 | public static float max(float a, float b) {
71 | return a > b ? a : b;
72 | }
73 |
74 | public static float max(int a, int b) {
75 | return a > b ? a : b;
76 | }
77 |
78 | public static float max(float a, float b, float c) {
79 | return a > b ? (a > c ? a : c) : (b > c ? b : c);
80 | }
81 |
82 | public static float max(int a, int b, int c) {
83 | return a > b ? (a > c ? a : c) : (b > c ? b : c);
84 | }
85 |
86 | public static float max(float a, float b, float c, float d) {
87 | return a > b && a > c && a > d ? a : b > c && b > d ? b : c > d ? c : d;
88 | }
89 |
90 | public static float max(int a, int b, int c, int d) {
91 | return a > b && a > c && a > d ? a : b > c && b > d ? b : c > d ? c : d;
92 | }
93 |
94 | public static float min(float a, float b) {
95 | return a < b ? a : b;
96 | }
97 |
98 | public static float min(int a, int b) {
99 | return a < b ? a : b;
100 | }
101 |
102 | public static float min(float a, float b, float c) {
103 | return a < b ? (a < c ? a : c) : (b < c ? b : c);
104 | }
105 |
106 | public static float min(int a, int b, int c) {
107 | return a < b ? (a < c ? a : c) : (b < c ? b : c);
108 | }
109 |
110 | public static float dist(float x1, float y1, float x2, float y2) {
111 | final float x = (x2 - x1);
112 | final float y = (y2 - y1);
113 | return (float) Math.hypot(x, y);
114 | }
115 |
116 | public static float dist(float x1, float y1, float z1, float x2, float y2, float z2) {
117 | final float x = (x2 - x1);
118 | final float y = (y2 - y1);
119 | final float z = (z2 - z1);
120 | return (float) Math.sqrt(x * x + y * y + z * z);
121 | }
122 |
123 | public static float mag(float a, float b) {
124 | return (float) Math.hypot(a, b);
125 | }
126 |
127 | public static float mag(float a, float b, float c) {
128 | return (float) Math.sqrt(a * a + b * b + c * c);
129 | }
130 |
131 | public static float sq(float v) {
132 | return v * v;
133 | }
134 |
135 | public static float sqrt(float value) {
136 | return (float) Math.sqrt(value);
137 | }
138 |
139 | public static float dot(float v1x, float v1y, float v2x, float v2y) {
140 | return v1x * v2x + v1y * v2y;
141 | }
142 |
143 | public static float cross(float v1x, float v1y, float v2x, float v2y) {
144 | return v1x * v2y - v1y * v2x;
145 | }
146 |
147 | public static float radians(float degrees) {
148 | return degrees * DEG_TO_RAD;
149 | }
150 |
151 | public static float degrees(float radians) {
152 | return radians * RAD_TO_DEG;
153 | }
154 |
155 | public static float acos(float value) {
156 | return (float) Math.acos(value);
157 | }
158 |
159 | public static float asin(float value) {
160 | return (float) Math.asin(value);
161 | }
162 |
163 | public static float atan(float value) {
164 | return (float) Math.atan(value);
165 | }
166 |
167 | public static float atan2(float a, float b) {
168 | return (float) Math.atan2(a, b);
169 | }
170 |
171 | public static float tan(float angle) {
172 | return (float) Math.tan(angle);
173 | }
174 |
175 | public static float lerp(float start, float stop, float amount) {
176 | return (1 - amount) * start + amount * stop;
177 | }
178 |
179 | public static float norm(float start, float stop, float value) {
180 | return (value - start) / (stop - start);
181 | }
182 |
183 | public static float map(
184 | float minStart, float minStop, float maxStart, float maxStop, float value) {
185 | return maxStart + (maxStart - maxStop) * ((value - minStart) / (minStop - minStart));
186 | }
187 |
188 | /**
189 | * Fuzzy less than or equal to for floats.
190 | * size
of a vector is effectively
25 | * final. All calculations involving multiple vectors will assume that all vectors involved are the
26 | * same size. All calculations that return a Vector will mutate the vector that the method was
27 | * called on.
28 | * a + b
, the sum of the given vector and this.
118 | */
119 | public Vector add(Vector b) {
120 | return add(this, b);
121 | }
122 |
123 | /**
124 | * Sets this vector to a + b
.
125 | */
126 | public Vector add(Vector a, Vector b) {
127 | if (a.size == 0) {
128 | set(b);
129 | return this;
130 | }
131 | if (b.size == 0) {
132 | set(a);
133 | return this;
134 | }
135 |
136 | if (size == 0) {
137 | size = a.size;
138 | values = new float[size];
139 | }
140 | checkArgument(size == a.size);
141 | checkArgument(size == b.size);
142 | float[] aValues = checkNotNull(a.values);
143 | float[] bValues = checkNotNull(b.values);
144 | for (int i = 0; i < a.size; i++) {
145 | values[i] = aValues[i] + bValues[i];
146 | }
147 | return this;
148 | }
149 |
150 | /**
151 | * Returns a - b
, the subtraction of the given vector from this.
152 | */
153 | public Vector sub(Vector b) {
154 | return sub(this, b);
155 | }
156 |
157 | /**
158 | * Sets this vector to a - b
.
159 | */
160 | public Vector sub(Vector a, Vector b) {
161 | if (a.size == 0) {
162 | scale(b, -1f);
163 | return this;
164 | }
165 | if (b.size == 0) {
166 | set(a);
167 | return this;
168 | }
169 |
170 | if (size == 0) {
171 | size = a.size;
172 | values = new float[size];
173 | }
174 | checkArgument(size == a.size);
175 | checkArgument(size == b.size);
176 | float[] aValues = checkNotNull(a.values);
177 | float[] bValues = checkNotNull(b.values);
178 | for (int i = 0; i < a.size; i++) {
179 | values[i] = aValues[i] - bValues[i];
180 | }
181 | return this;
182 | }
183 |
184 | /**
185 | * Returns k * v
, the scaling of this vector by the given scalar.
186 | */
187 | public Vector scale(float k) {
188 | return scale(this, k);
189 | }
190 |
191 | /**
192 | * Sets this vector to k * v
.
193 | */
194 | public Vector scale(Vector v, float k) {
195 | if (k == 1f) {
196 | set(v);
197 | return this;
198 | }
199 |
200 | if (v.size == 0) {
201 | size = 0;
202 | values = null;
203 | return this;
204 | }
205 |
206 | if (size == 0) {
207 | size = v.size;
208 | values = new float[size];
209 | }
210 | checkArgument(size == v.size);
211 | float[] vValues = checkNotNull(v.values);
212 | for (int i = 0; i < v.size; i++) {
213 | values[i] = vValues[i] * k;
214 | }
215 | return this;
216 | }
217 |
218 | /**
219 | * Returns v / ||v||
, the normalization of this vector.
220 | * v/||v||
, the normalization of this vector.
229 | * projab
, the projection of the given vector onto this.
277 | * projab
, the projection of b onto a.
287 | * v1⋅v2
, the dot product of this vector and the given vector.
321 | */
322 | public float dot(Vector other) {
323 | if (size == 0 || other.size == 0) {
324 | return 0f;
325 | }
326 |
327 | checkArgument(size == other.size);
328 | float[] thisValues = checkNotNull(this.values);
329 | float[] otherValues = checkNotNull(other.values);
330 | float sum = 0f;
331 | for (int i = 0; i < size; i++) {
332 | sum += thisValues[i] * otherValues[i];
333 | }
334 | return sum;
335 | }
336 |
337 | /**
338 | * Returns θ
, the unsigned angle in between this normalized vector and the
339 | * given normalized vector.
340 | * θ
, the unsigned angle in between this vector and the given vector.
356 | * Both vectors will be normalized in place.
357 | * ||v1-v2||
, but without the intermediary vector
370 | * allocation or mutation.
371 | */
372 | public float distance(Vector other) {
373 | if (size == 0) {
374 | return other.magnitude();
375 | }
376 | if (other.size == 0) {
377 | return magnitude();
378 | }
379 |
380 | checkArgument(size == other.size);
381 | float[] thisValues = checkNotNull(this.values);
382 | float[] otherValues = checkNotNull(other.values);
383 | float sum = 0f;
384 | for (int i = 0; i < size; i++) {
385 | float diff = thisValues[i] - otherValues[i];
386 | sum += diff * diff;
387 | }
388 | return MathUtils.sqrt(sum);
389 | }
390 |
391 | /**
392 | * Returns ||v||
, the magnitude of this vector.
393 | */
394 | public float magnitude() {
395 | return MathUtils.sqrt(Vectors.dot(this, this));
396 | }
397 |
398 | @Override
399 | public String toString() {
400 | return String.format("%s %s", Vector.class.getSimpleName(), Arrays.toString(values));
401 | }
402 |
403 | @Override
404 | public boolean equals(Object o) {
405 | if (this == o) {
406 | return true;
407 | }
408 | if (o == null || getClass() != o.getClass()) {
409 | return false;
410 | }
411 |
412 | Vector vector = (Vector) o;
413 |
414 | return size == 0 && vector.size == 0
415 | || size == 0 && vector.magnitude() == 0
416 | || vector.size == 0 && magnitude() == 0
417 | || Arrays.equals(values, vector.values);
418 | }
419 |
420 | @Override
421 | public int hashCode() {
422 | if (size == 0 || magnitude() == 0) {
423 | return 0;
424 | }
425 |
426 | return Arrays.hashCode(values);
427 | }
428 |
429 | /**
430 | * Ensures that an object reference passed as a parameter to the calling method is not null.
431 | *
432 | * @param reference an object reference
433 | * @return the non-null reference that was validated
434 | * @throws NullPointerException if {@code reference} is null
435 | */
436 | @NonNull
437 | private static i
must correspond to the {@link AnchoredForce} in {@link #forces} at index
217 | * i
.
218 | */
219 | public Demo with(View... anchors) {
220 | this.anchors = anchors;
221 | return this;
222 | }
223 |
224 | /**
225 | * Sets the forces on this demo. Each anchor view in {@link #anchors} at index
226 | * i
must correspond to the {@link AnchoredForce} in {@link #forces} at index
227 | * i
.
228 | *