安卓Slide to Answer如同ImageView动画

5
我需要在ImageView上实现一个动画,类似于许多Android设备中存在的滑动以回答动画。要求如下:
  1. 支持API级别>=8(如果不可能则为9),因此方便的拖动监听器drag listener不能使用
  2. 只有水平拖动时才将ImageView向右或向左移动。一开始图像在水平方向上居中。
  3. 拖动时缩放ImageView-越接近屏幕末端,图像就会变得越小。
  4. 释放拖动时,图像需要动画返回到屏幕中心,并缩放到其原始大小(也是动画)
我找到了许多代码示例并尝试自己实现,但所有要求的组合使它非常困难,我无法得到一个体面的结果,所以请不要提供来自Google搜索第一页的内容链接,因为我已经花了很多天时间尝试实现这些示例,如果需要,请提供一个可行的代码示例+ xml布局。
2个回答

6
您可以通过结合以下方式轻松实现此操作:
  • 使用View.OnTouchListener(用于检测拖动序列,基本上是ACTION_DOWNACTION_MOVE,...,ACTION_UP)。
  • Jake Wharton出色的NineOldAndroids库,支持API级别11之前的视图属性动画。
首先获取ImageView对象并将View.OnTouchListener附加到它。
@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    ...
    mImage = findViewById(...);
    mImage.setOnTouchListener(mTouchListener);
}

其次,编写 OnTouchListener 来捕获 ACTION_DOWN 事件并存储初始触摸位置的 X 坐标。然后,对于每个 ACTION_MOVE,计算出增量(用于平移和缩放),对于 ACTION_UP,将 ImageView 返回到其初始状态。
private View.OnTouchListener mTouchListener = new View.OnTouchListener()
{
    private float mStartX;

    @Override
    public boolean onTouch(View v, MotionEvent event)
    {
        switch (event.getActionMasked())
        {
            case MotionEvent.ACTION_DOWN :
                mStartX = event.getRawX();
                return true;

            case MotionEvent.ACTION_MOVE :
                float currentX = event.getRawX();
                animateTo(currentX - mStartX, true); // Snap to drag
                return true;

            case MotionEvent.ACTION_UP :
            case MotionEvent.ACTION_CANCEL :
                animateTo(0, false); // Ease back
                return true;
        }

        return false;
    }
};
animateTo()将被实现如下。当响应每个移动事件时,immediate标志表示是否应该立即进行平移和缩放(这是真的),而在缓慢返回其初始位置和比例时则为false
private void animateTo(float displacement, boolean immediate)
{
    final int EASE_BACK_DURATION = 300; // ms
    int duration = (immediate ? 0 : EASE_BACK_DURATION);

    Display display = getWindowManager().getDefaultDisplay();
    int totalWidth = display.getWidth();
    float scale = 1.0f - Math.abs(displacement / totalWidth);

    ViewPropertyAnimator.animate(mImage)
        .translationX(displacement)
        .scaleX(scale)
        .scaleY(scale)
        .setDuration(duration)
        .start();
}

您可能想要调整缩放比例。按照当前的设置,当将图像拖到屏幕边缘时,它将以原始大小的50%显示。

这个解决方案应该可以在API级别8中无问题地工作(尽管我没有测试过)。如果需要,完整的代码可以在此处找到。


4
第一时间想到的是通过Handler来逐帧动画LayoutParams。我不确定它是否符合您的要求,这可能需要进行更多的测试。
无论如何,回想起数学还是挺有趣的^^ 因此,以下代码仅使用原生Android工具:
代码:
package com.example.simon.draggableimageview;

import android.os.Handler;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;

/**
 * Created by Simon on 2014 Nov 18.
 */

public class MainActivity extends ActionBarActivity {

    private static final String TAG = "MainActivity";

    // Avoid small values for the following two or setSize will start lagging behind
    // The maximum time, animation (from smallest) to default size will take
    private static final int MAX_DURATION = 500;
    // Minimum delay (ms) for each loop
    private static final int MIN_DELAY = 20;

    // By how many px (at least) each (animation back to default state) loop will shift the image
    private static final float MIN_X_SHIFT = 3;

    private ImageView mImage;
    private int mInitialW, mInitialH, mCenterX;
    private int mMaxMargin;
    private AnimateBack mAnimateBack;
    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new Handler();
        mImage = (ImageView) findViewById(R.id.imageView);
        final RelativeLayout imageHolder = (RelativeLayout) findViewById(R.id.imageHolder);

        mImage.post(new Runnable() {
            @Override
            public void run() {
                // Image ready, measure it
                mInitialH = mImage.getHeight();
                mInitialW = mImage.getWidth();

                imageHolder.post(new Runnable() {
                    @Override
                    public void run() {
                        // Calc other measurements
                        int containerWidth = imageHolder.getWidth();
                        mCenterX = containerWidth / 2;
                        mMaxMargin = containerWidth - mInitialW;
                    }
                });
            }
        });

        imageHolder.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                switch (motionEvent.getAction()) {
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL:
                        mAnimateBack = new AnimateBack();
                        mAnimateBack.run();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        mHandler.removeCallbacks(mAnimateBack);
                        if (motionEvent.getX() > mMaxMargin + mInitialW || motionEvent.getX() < 0) {
                            // Fake Action_Up if out of container bounds
                            motionEvent.setAction(MotionEvent.ACTION_UP);
                            onTouch(view, motionEvent);
                            return true;
                        }
                        setSize(motionEvent.getX() - mCenterX);
                        break;
                }
                return true;
            }
        });
    }

    private void setSize(float offsetFromCenter) {
        // Calculate new left margin
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mImage.getLayoutParams();
        params.leftMargin = (int) (mMaxMargin * offsetFromCenter / (mCenterX - mInitialW / 2.0));

        // Calculate dimensions
        float ratio = 1 - (Math.abs(offsetFromCenter) / mCenterX);
        params.width = (int) (mInitialW * ratio);
        params.height = (int) (mInitialH * ratio);
        mImage.setLayoutParams(params);

//      Log.e(TAG, String.format("leftMargin: %d, W: %d, H: %d",
//              params.leftMargin, params.width, params.height));
    }

    private class AnimateBack implements Runnable {
        private int loopCount, loopDelay;
        private float loopBy;

        public AnimateBack() {
            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mImage.getLayoutParams();
            float offsetFromCenter = (float) params.leftMargin / mMaxMargin *
                    (mCenterX - mInitialW / 2.0f);
            float totalDuration = (Math.abs(offsetFromCenter) * MAX_DURATION / mCenterX);

            loopBy = MIN_X_SHIFT;
            loopCount = (int) Math.abs(offsetFromCenter / loopBy);
            loopDelay = (int) (totalDuration / loopCount);
            if (loopDelay < MIN_DELAY) {
                // Use the minimum delay
                loopDelay = MIN_DELAY;
                // Minimum loop count is 1
                loopCount = (int) Math.max(totalDuration / loopDelay, 1);
                // Calculate by how much each loop will inc/dec the margin
                loopBy = Math.round(Math.abs(offsetFromCenter / loopCount));
            }
            Log.d(TAG, String.format("Animate back will take: %fms. Will start from offset %d. " +
                            "It will advance by %dpx every %dms",
                    totalDuration, (int) offsetFromCenter, (int) loopBy, loopDelay));
        }

        @Override
        public void run() {
            --loopCount;
            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mImage.getLayoutParams();
            // Calculate offsetFromCenter
            float offsetFromCenter = (float) ((float) params.leftMargin / mMaxMargin *
                    (mCenterX - mInitialW / 2.0));
            // Don't pass 0 when looping
            if (params.leftMargin > 0) {
                offsetFromCenter = Math.max(offsetFromCenter - loopBy, 0);
            } else {
                offsetFromCenter = Math.min(offsetFromCenter + loopBy, 0);
            }
            setSize(offsetFromCenter);

            if (loopCount == 0) {
                mHandler.removeCallbacks(this);
            } else {
                mHandler.postDelayed(this, loopDelay);
            }
        }
    }

}

布局:

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <RelativeLayout
        android:id="@+id/imageHolder"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center">

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:src="@drawable/ic_launcher"/>

    </RelativeLayout>
</RelativeLayout>

预览:

在此输入图片描述

需要翻译的内容已经是中文,无需翻译。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接