缩放动画源码分析

来源:互联网 发布:淘宝宝贝发布物流 编辑:程序博客网 时间:2024/05/29 10:31

缩放动画源码分析

背景

这个动画例子来自于Android官方文档Trainning中Addding Animations一章中。
Demo下载地址

动画效果

源码分析

只需要看放大的过程,缩小的过程是放大的逆过程。

布局分析

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/container"    android:layout_width="match_parent"    android:layout_height="match_parent">    <LinearLayout android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="vertical"        android:padding="16dp">        <TextView style="?android:textAppearanceSmall"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="@string/message_zoom_touch_expand" />        <!-- This is an example layout containing thumbnail image buttons that, when pressed,             zoom in to show more detail. All of the zooming and animation logic is in             the ZoomActivity class. -->        <LinearLayout            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_marginTop="16dp"            android:orientation="horizontal">            <!-- These buttons don't have any decorations (3D bevel, etc.), but it's still                 important to show feedback on touch or focus. The custom                 "ToughHighlightImageButton" ImageButton subclass helps achieve this by drawing                 the standard system "pressed" and "focused" overlay upon user interaction. -->            <com.example.android.animationsdemo.TouchHighlightImageButton                android:id="@+id/thumb_button_1"                android:layout_width="100dp"                android:layout_height="75dp"                android:layout_marginRight="1dp"                android:src="@drawable/thumb1"                android:scaleType="centerCrop"                android:contentDescription="@string/description_image_1" />            <com.example.android.animationsdemo.TouchHighlightImageButton                android:id="@+id/thumb_button_2"                android:layout_width="100dp"                android:layout_height="75dp"                android:src="@drawable/thumb2"                android:scaleType="centerCrop"                android:contentDescription="@string/description_image_2" />        </LinearLayout>    </LinearLayout>    <!-- This initially-hidden ImageView will hold the expanded/zoomed version of the         images above. Without transformations applied, it takes up the entire screen.         To achieve the "zoom" animation, this view's bounds are animated from the         bounds of the thumbnail buttons above, to its final laid-out bounds. The implementation         of this animation is in the ZoomActivity class. -->    <ImageView        android:id="@+id/expanded_image"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:visibility="invisible"        android:contentDescription="@string/description_zoom_touch_close" /></FrameLayout>

从上面看到,实现的整体思路是,小图用一个小的ImageView显示,放大过程图片显示以及动画作用的对象为另一个ImageView,可称之为目标ImageVBiew。

动画实现代码分析

1、同一坐标系
代码如下:

//小图Boundsfinal Rect startBounds = new Rect();//大图Boundsfinal Rect finalBounds = new Rect();final Point globalOffset = new Point();thumbView.getGlobalVisibleRect(startBounds);findViewById(R.id.container).getGlobalVisibleRect(finalBounds, globalOffset);startBounds.offset(-globalOffset.x, -globalOffset.y);finalBounds.offset(-globalOffset.x, -globalOffset.y);

getGlobalVisibleRect()获取的是绝对坐标(即在屏幕上的坐标);
id为R.id.containner的布局为小图ImageView和大图ImageView的parent;
startBounds.offset(-globalOffset.x,-globalOffset.y)和finalBounds.offset(-globalOffset.x,-globalOffset)将startBounds和finalBounds同处于大图ImageView所处parent的坐标系,这样动画才能统一度量。

2、startBounds的宽高比调整为和finalBounds一样的宽高比

代码如下:

//startBounds的宽或高只能扩大,不能缩小//finalBounds宽高比大于startBounds宽高比,需要扩大startBounds的宽度if ((float) finalBounds.width() / finalBounds.height()> (float) startBounds.width() / startBounds.height()) {    // Extend start bounds horizontally    //以高为基准计算出比率    startScale = (float) startBounds.height() /finalBounds.height();    //计算出期望到初始宽度    float startWidth = startScale * finalBounds.width();    //通过改变startBounds的left和right来改变大小    //同时由于大图ImageView是居中显示,变化后图片仍然是与小图重合    float deltaWidth = (startWidth - startBounds.width()) / 2;    startBounds.left -= deltaWidth;    startBounds.right += deltaWidth;} else {    // Extend start bounds vertically    startScale = (float) startBounds.width() / finalBounds.width();    float startHeight = startScale * finalBounds.height();    float deltaHeight = (startHeight - startBounds.height()) / 2;    startBounds.top -= deltaHeight;    startBounds.bottom += deltaHeight;}final float startScaleFinal = startScale;

为什么要这么做?
因为如果同一时间内,假如宽度从比率startBounds.width()/finalBounds.width()扩大到1,高度从比率startBounds.height()/finalBounds.heigh()扩大到1,在动画过程会发现很不自然,以为在变化过程中大图IamgeView的宽高比没有始终保持同一比例,导致图片可能被拉伸或压缩。

不自然到效果:

怎么做调整startBounds使之和finalBounds有相同到宽高比?
请看上面代码注释

3、Show Time

//小图不可见,大图可见thumbView.setAlpha(0f);expandedImageView.setVisibility(View.VISIBLE);//以大图的左上角为缩放中点expandedImageView.setPivotX(0f);expandedImageView.setPivotY(0f);AnimatorSet set = new AnimatorSet();//扩大通过View.SCALE_X,View.SCALE_Y属性来实现//View.X,View.Y属性来实现大图从小图左上角开始扩大set    .play(ObjectAnimator.ofFloat(expandedImageView, View.X, startBounds.left,finalBounds.left))    .with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top,finalBounds.top))    .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScale, 1f))    .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScale, 1f));set.setDuration(mShortAnimationDuration);set.setInterpolator(new DecelerateInterpolator());set.addListener(new AnimatorListenerAdapter() {    @Override    public void onAnimationEnd(Animator animation) {        mCurrentAnimator = null;    }    @Override    public void onAnimationCancel(Animator animation) {        mCurrentAnimator = null;    }});set.start();

要点总结

  1. 动画作用的目标对象是大图ImageView
  2. 初始状态和最终状态要处于目标对象所在的坐标系
  3. 初始状态和最终状态有相同到宽高比

PS 给ImageView加上前景图片

其他View设置StateListDrawable,可以在点击时更改背景,对于ImageView如何实现?

由于ImageView设置到图片会覆盖背景,所以一般到方法并不能实现。

实现思路:

  1. 继承ImageView或ImageButton
  2. 新增Drawable类型成员变量mForegroundDrawable引用StatListDrawable对象
  3. 重写View的drawableStateChanged()方法,设置mForegroundDrawable为当前状态

部分实现:

public class TouchHighlightImageButton extends ImageButton {    /**     * The highlight drawable. This generally a {@link android.graphics.drawable.StateListDrawable}     * that's transparent in the default state, and contains a semi-transparent overlay     * for the focused and pressed states.     */    private Drawable mForegroundDrawable;    /**     * The cached bounds of the view.     */    private Rect mCachedBounds = new Rect();    public TouchHighlightImageButton(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    /**     * General view initialization used common to all constructors of the view.     */    private void init() {        // Reset default ImageButton background and padding.        setBackgroundColor(0);        setPadding(0, 0, 0, 0);        // Retrieve the drawable resource assigned to the android.R.attr.selectableItemBackground        // theme attribute from the current theme.        TypedArray a = getContext()                .obtainStyledAttributes(new int[]{android.R.attr.selectableItemBackground});        mForegroundDrawable = a.getDrawable(0);        mForegroundDrawable.setCallback(this);        a.recycle();    }    @Override    protected void drawableStateChanged() {        super.drawableStateChanged();        // Update the state of the highlight drawable to match        // the state of the button.        if (mForegroundDrawable.isStateful()) {            mForegroundDrawable.setState(getDrawableState());        }        // Trigger a redraw.        invalidate();    }    @Override    protected void onDraw(Canvas canvas) {        // First draw the image.        super.onDraw(canvas);        // Then draw the highlight on top of it. If the button is neither focused        // nor pressed, the drawable will be transparent, so just the image        // will be drawn.        mForegroundDrawable.setBounds(mCachedBounds);        mForegroundDrawable.draw(canvas);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        // Cache the view bounds.        mCachedBounds.set(0, 0, w, h);    }}
原创粉丝点击