使用android5.0以上系统中隐藏的动画类进行手势识别的属性动画

android5.0以后,系统多了许多由手指拖拉同时产生的动画,而我们通常所使用的Animator播放出来都是一个完整的流程,很难拆分出来以适应手指的滑动.虽然可以使用View#ALPHA,View#

TRANSLATION_X等动画的属性值直接设置,但是一旦想让多个动画同时进行,比如平移+旋转+透明,代码逻辑会显得非常复杂.我在源码中找到了系统中使用的封装好的一个类

TouchAnimator.由于这个类是隐藏的,所以我的做法是将其拷贝出来,这样也比较好修改(如果有需求的话)

    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);
    }
}

注意,由于TouchAnimator使用到了一些新版本的Api,有可能部分属性(如TranslationZ)不能在老版本兼容,因此在生产环境中必须修改该类,提高兼容性

下面是使用demo

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;
    }
}

这里的布局只是一个ImageButton

<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>

附一张效果图

转载于:https://my.oschina.net/zhouzhenBlog/blog/790719

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值