android5.0之後,系統多了許多由手指拖拉同時產生的動畫,而咱們一般所使用的Animator播放出來都是一個完整的流程,很難拆分出來以適應手指的滑動.雖然能夠使用View#ALPHA,View#java
TRANSLATION_X等動畫的屬性值直接設置,可是一旦想讓多個動畫同時進行,好比平移+旋轉+透明,代碼邏輯會顯得很是複雜.我在源碼中找到了系統中使用的封裝好的一個類android
TouchAnimator.因爲這個類是隱藏的,因此個人作法是將其拷貝出來,這樣也比較好修改(若是有需求的話)api
import android.os.Build; import android.support.annotation.RequiresApi; import android.util.FloatProperty; import android.util.Log; import android.util.Property; import android.view.View; import android.view.animation.Interpolator; import java.util.ArrayList; import java.util.List; public class TouchAnimator { private final Object[] mTargets; private final KeyframeSet[] mKeyframeSets; private final float mStartDelay; private final float mEndDelay; private final float mSpan; private final Interpolator mInterpolator; private final Listener mListener; private float mLastT = -1; private TouchAnimator(Object[] targets, KeyframeSet[] keyframeSets, float startDelay, float endDelay, Interpolator interpolator, Listener listener) { mTargets = targets; mKeyframeSets = keyframeSets; mStartDelay = startDelay; mEndDelay = endDelay; mSpan = (1 - mEndDelay - mStartDelay); mInterpolator = interpolator; mListener = listener; } public void setPosition(float fraction) { float t = MathUtils.constrain((fraction - mStartDelay) / mSpan, 0, 1); if (mInterpolator != null) { t = mInterpolator.getInterpolation(t); } if (t == mLastT) { return; } if (mListener != null) { if (t == 1) { mListener.onAnimationAtEnd(); } else if (t == 0) { mListener.onAnimationAtStart(); } else if (mLastT <= 0 || mLastT == 1) { mListener.onAnimationStarted(); } mLastT = t; } for (int i = 0; i < mTargets.length; i++) { mKeyframeSets[i].setValue(t, mTargets[i]); } } @RequiresApi(api = Build.VERSION_CODES.N) private static final FloatProperty<TouchAnimator> POSITION = new FloatProperty<TouchAnimator>("position") { @Override public void setValue(TouchAnimator touchAnimator, float value) { touchAnimator.setPosition(value); } @Override public Float get(TouchAnimator touchAnimator) { return touchAnimator.mLastT; } }; public static class ListenerAdapter implements Listener { @Override public void onAnimationAtStart() { } @Override public void onAnimationAtEnd() { } @Override public void onAnimationStarted() { } } public interface Listener { /** * Called when the animator moves into a position of "0". Start and end delays are * taken into account, so this position may cover a range of fractional inputs. */ void onAnimationAtStart(); /** * Called when the animator moves into a position of "0". Start and end delays are * taken into account, so this position may cover a range of fractional inputs. */ void onAnimationAtEnd(); /** * Called when the animator moves out of the start or end position and is in a transient * state. */ void onAnimationStarted(); } public static class Builder { private List<Object> mTargets = new ArrayList<>(); private List<KeyframeSet> mValues = new ArrayList<>(); private float mStartDelay; private float mEndDelay; private Interpolator mInterpolator; private Listener mListener; public Builder addFloat(Object target, String property, float... values) { add(target, KeyframeSet.ofFloat(getProperty(target, property, float.class), values)); return this; } public Builder addInt(Object target, String property, int... values) { add(target, KeyframeSet.ofInt(getProperty(target, property, int.class), values)); return this; } private void add(Object target, KeyframeSet keyframeSet) { mTargets.add(target); mValues.add(keyframeSet); } private static Property getProperty(Object target, String property, Class<?> cls) { if (target instanceof View) { switch (property) { case "translationX": return View.TRANSLATION_X; case "translationY": return View.TRANSLATION_Y; case "translationZ": if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return View.TRANSLATION_Z; } return null; case "alpha": return View.ALPHA; case "rotation": return View.ROTATION; case "x": return View.X; case "y": return View.Y; case "scaleX": return View.SCALE_X; case "scaleY": return View.SCALE_Y; } } if (target instanceof TouchAnimator && "position".equals(property)) { return POSITION; } return Property.of(target.getClass(), cls, property); } public Builder setStartDelay(float startDelay) { mStartDelay = startDelay; return this; } public Builder setEndDelay(float endDelay) { mEndDelay = endDelay; return this; } public Builder setInterpolator(Interpolator intepolator) { mInterpolator = intepolator; return this; } public Builder setListener(Listener listener) { mListener = listener; return this; } public TouchAnimator build() { return new TouchAnimator(mTargets.toArray(new Object[mTargets.size()]), mValues.toArray(new KeyframeSet[mValues.size()]), mStartDelay, mEndDelay, mInterpolator, mListener); } } private static abstract class KeyframeSet { private final float mFrameWidth; private final int mSize; public KeyframeSet(int size) { mSize = size; mFrameWidth = 1 / (float) (size - 1); } void setValue(float fraction, Object target) { int i; for (i = 1; i < mSize - 1 && fraction > mFrameWidth; i++) ; float amount = fraction / mFrameWidth; interpolate(i, amount, target); } protected abstract void interpolate(int index, float amount, Object target); public static KeyframeSet ofInt(Property property, int... values) { return new IntKeyframeSet((Property<?, Integer>) property, values); } public static KeyframeSet ofFloat(Property property, float... values) { return new FloatKeyframeSet((Property<?, Float>) property, values); } } private static class FloatKeyframeSet<T> extends KeyframeSet { private final float[] mValues; private final Property<T, Float> mProperty; public FloatKeyframeSet(Property<T, Float> property, float[] values) { super(values.length); mProperty = property; mValues = values; } @Override protected void interpolate(int index, float amount, Object target) { float firstFloat = mValues[index - 1]; float secondFloat = mValues[index]; Log.i("Main12Activity", "index=" + index + " amount=" + amount + " target=" + target); mProperty.set((T) target, firstFloat + (secondFloat - firstFloat) * amount); } } private static class IntKeyframeSet<T> extends KeyframeSet { private final int[] mValues; private final Property<T, Integer> mProperty; public IntKeyframeSet(Property<T, Integer> property, int[] values) { super(values.length); mProperty = property; mValues = values; } @Override protected void interpolate(int index, float amount, Object target) { int firstFloat = mValues[index - 1]; int secondFloat = mValues[index]; mProperty.set((T) target, (int) (firstFloat + (secondFloat - firstFloat) * amount)); } } }
import java.util.Random; public class MathUtils { private static final Random sRandom = new Random(); private static final float DEG_TO_RAD = 3.1415926f / 180.0f; private static final float RAD_TO_DEG = 180.0f / 3.1415926f; private MathUtils() { } public static float abs(float v) { return v > 0 ? v : -v; } public static int constrain(int amount, int low, int high) { return amount < low ? low : (amount > high ? high : amount); } public static long constrain(long amount, long low, long high) { return amount < low ? low : (amount > high ? high : amount); } public static float constrain(float amount, float low, float high) { return amount < low ? low : (amount > high ? high : amount); } public static float log(float a) { return (float) Math.log(a); } public static float exp(float a) { return (float) Math.exp(a); } public static float pow(float a, float b) { return (float) Math.pow(a, b); } public static float max(float a, float b) { return a > b ? a : b; } public static float max(int a, int b) { return a > b ? a : b; } public static float max(float a, float b, float c) { return a > b ? (a > c ? a : c) : (b > c ? b : c); } public static float max(int a, int b, int c) { return a > b ? (a > c ? a : c) : (b > c ? b : c); } public static float min(float a, float b) { return a < b ? a : b; } public static float min(int a, int b) { return a < b ? a : b; } public static float min(float a, float b, float c) { return a < b ? (a < c ? a : c) : (b < c ? b : c); } public static float min(int a, int b, int c) { return a < b ? (a < c ? a : c) : (b < c ? b : c); } public static float dist(float x1, float y1, float x2, float y2) { final float x = (x2 - x1); final float y = (y2 - y1); return (float) Math.hypot(x, y); } public static float dist(float x1, float y1, float z1, float x2, float y2, float z2) { final float x = (x2 - x1); final float y = (y2 - y1); final float z = (z2 - z1); return (float) Math.sqrt(x * x + y * y + z * z); } public static float mag(float a, float b) { return (float) Math.hypot(a, b); } public static float mag(float a, float b, float c) { return (float) Math.sqrt(a * a + b * b + c * c); } public static float sq(float v) { return v * v; } public static float dot(float v1x, float v1y, float v2x, float v2y) { return v1x * v2x + v1y * v2y; } public static float cross(float v1x, float v1y, float v2x, float v2y) { return v1x * v2y - v1y * v2x; } public static float radians(float degrees) { return degrees * DEG_TO_RAD; } public static float degrees(float radians) { return radians * RAD_TO_DEG; } public static float acos(float value) { return (float) Math.acos(value); } public static float asin(float value) { return (float) Math.asin(value); } public static float atan(float value) { return (float) Math.atan(value); } public static float atan2(float a, float b) { return (float) Math.atan2(a, b); } public static float tan(float angle) { return (float) Math.tan(angle); } public static float lerp(float start, float stop, float amount) { return start + (stop - start) * amount; } /** * Returns an interpolated angle in degrees between a set of start and end * angles. * <p> * Unlike {@link #lerp(float, float, float)}, the direction and distance of * travel is determined by the shortest angle between the start and end * angles. For example, if the starting angle is 0 and the ending angle is * 350, then the interpolated angle will be in the range [0,-10] rather * than [0,350]. * * @param start the starting angle in degrees * @param end the ending angle in degrees * @param amount the position between start and end in the range [0,1] * where 0 is the starting angle and 1 is the ending angle * @return the interpolated angle in degrees */ public static float lerpDeg(float start, float end, float amount) { final float minAngle = (((end - start) + 180) % 360) - 180; return minAngle * amount + start; } public static float norm(float start, float stop, float value) { return (value - start) / (stop - start); } public static float map(float minStart, float minStop, float maxStart, float maxStop, float value) { return maxStart + (maxStart - maxStop) * ((value - minStart) / (minStop - minStart)); } public static int random(int howbig) { return (int) (sRandom.nextFloat() * howbig); } public static int random(int howsmall, int howbig) { if (howsmall >= howbig) return howsmall; return (int) (sRandom.nextFloat() * (howbig - howsmall) + howsmall); } public static float random(float howbig) { return sRandom.nextFloat() * howbig; } public static float random(float howsmall, float howbig) { if (howsmall >= howbig) return howsmall; return sRandom.nextFloat() * (howbig - howsmall) + howsmall; } public static void randomSeed(long seed) { sRandom.setSeed(seed); } /** * Returns the sum of the two parameters, or throws an exception if the resulting sum would * cause an overflow or underflow. * @throws IllegalArgumentException when overflow or underflow would occur. */ public static int addOrThrow(int a, int b) throws IllegalArgumentException { if (b == 0) { return a; } if (b > 0 && a <= (Integer.MAX_VALUE - b)) { return a + b; } if (b < 0 && a >= (Integer.MIN_VALUE - b)) { return a + b; } throw new IllegalArgumentException("Addition overflow: " + a + " + " + b); } }
下面是使用demodom
public class Main12Activity extends AppCompatActivity { private static final String TAG = "Main12Activity"; ImageButton btn; TouchAnimator slodDownAnim; private float mLastY; private float position; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main12); btn = (ImageButton) findViewById(R.id.btn); btn.ALPHA.set(btn, 0f); TouchAnimator.Builder builder = new TouchAnimator.Builder(); builder.addFloat(btn, "translationY", 0, 1000); builder.addFloat(btn, "rotation", 0, 360); builder.addFloat(btn, "alpha", 0, 1); builder.setListener(new TouchAnimator.Listener() { @Override public void onAnimationAtStart() { Log.i(TAG, "onAnimationAtStart"); } @Override public void onAnimationAtEnd() { Log.i(TAG, "onAnimationAtEnd"); } @Override public void onAnimationStarted() { Log.i(TAG, "onAnimationStarted"); } }); slodDownAnim = builder.build(); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastY = event.getY(); break; case MotionEvent.ACTION_MOVE: float y = event.getY(); float v = y - mLastY; position += v; if (position > 1000) { position = 1000; } else if (position < 0) { position = 0; } slodDownAnim.setPosition(position / 1000); mLastY = y; break; case MotionEvent.ACTION_UP: break; } return true; } }
這裏的佈局只是一個ImageButtonide
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main12" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageButton android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:src="@mipmap/ic_launcher"/> </RelativeLayout>
附一張效果圖 工具