Android 对一个View进行缩放处理(放大或缩小View)案例

源码

1 activity_zoom.xml



    

        

        

        

            

            

            
        
    

    

    

2 strings.xml




    ZoomAnimation
    Hello world!
    Settings
    Touch a photo to expand it.
    Image 1
    Image 2
    Expanded image (touch to close)


3 TouchHighlightImageButton.java

package com.example.zoomanimation;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageButton;

public class TouchHighlightImageButton extends ImageButton {

    private Drawable mForegroundDrawable;
    private Rect mCachedBounds = new Rect();

    public TouchHighlightImageButton(Context context) {
        super(context);
        init();
    }

    public TouchHighlightImageButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public TouchHighlightImageButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

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


4 ZoomActivity.java

package com.example.zoomanimation;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;

public class ZoomActivity extends Activity {

    /**
     * Hold a reference to the current animator, so that it can be canceled
     * mid-way.
     */
    private Animator mCurrentAnimator;

    /**
     * The system "short" animation time duration, in milliseconds. This
     * duration is ideal for subtle animations or animations that occur very
     * frequently.
     */
    private int mShortAnimationDuration;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_zoom);

        // Hook up clicks on the thumbnail views.

        final View thumb1View = findViewById(R.id.thumb_button_1);
        thumb1View.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                zoomImageFromThumb(thumb1View, R.drawable.image1);
            }
        });

        final View thumb2View = findViewById(R.id.thumb_button_2);
        thumb2View.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                zoomImageFromThumb(thumb2View, R.drawable.image2);
            }
        });

        // Retrieve and cache the system's default "short" animation time.
        mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);
    }

    /**
     * "Zooms" in a thumbnail view by assigning the high resolution image to a
     * hidden "zoomed-in" image view and animating its bounds to fit the entire
     * activity content area. More specifically:
     *
     * 
    *
  1. Assign the high-res image to the hidden "zoomed-in" (expanded) image * view.
  2. *
  3. Calculate the starting and ending bounds for the expanded view.
  4. *
  5. Animate each of four positioning/sizing properties (X, Y, SCALE_X, * SCALE_Y) simultaneously, from the starting bounds to the ending bounds.
  6. *
  7. Zoom back out by running the reverse animation on click.
  8. *
* * @param thumbView * The thumbnail view to zoom in. * @param imageResId * The high-resolution version of the image represented by the * thumbnail. */ private void zoomImageFromThumb(final View thumbView, int imageResId) { // If there's an animation in progress, cancel it immediately and // proceed with this one. if (mCurrentAnimator != null) { mCurrentAnimator.cancel(); } // Load the high-resolution "zoomed-in" image. final ImageView expandedImageView = (ImageView) findViewById(R.id.expanded_image); expandedImageView.setImageResource(imageResId); // Calculate the starting and ending bounds for the zoomed-in image. // This step // involves lots of math. Yay, math. final Rect startBounds = new Rect(); final Rect finalBounds = new Rect(); final Point globalOffset = new Point(); // The start bounds are the global visible rectangle of the thumbnail, // and the // final bounds are the global visible rectangle of the container view. // Also // set the container view's offset as the origin for the bounds, since // that's // the origin for the positioning animation properties (X, Y). thumbView.getGlobalVisibleRect(startBounds); findViewById(R.id.container).getGlobalVisibleRect(finalBounds, globalOffset); startBounds.offset(-globalOffset.x, -globalOffset.y); finalBounds.offset(-globalOffset.x, -globalOffset.y); // Adjust the start bounds to be the same aspect ratio as the final // bounds using the // "center crop" technique. This prevents undesirable stretching during // the animation. // Also calculate the start scaling factor (the end scaling factor is // always 1.0). float startScale; 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(); 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; } // Hide the thumbnail and show the zoomed-in view. When the animation // begins, // it will position the zoomed-in view in the place of the thumbnail. thumbView.setAlpha(0f); expandedImageView.setVisibility(View.VISIBLE); // Set the pivot point for SCALE_X and SCALE_Y transformations to the // top-left corner of // the zoomed-in view (the default is the center of the view). expandedImageView.setPivotX(0f); expandedImageView.setPivotY(0f); // Construct and run the parallel animation of the four translation and // scale properties // (X, Y, SCALE_X, and SCALE_Y). AnimatorSet set = new AnimatorSet(); 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(); mCurrentAnimator = set; // Upon clicking the zoomed-in image, it should zoom back down to the // original bounds // and show the thumbnail instead of the expanded image. final float startScaleFinal = startScale; expandedImageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mCurrentAnimator != null) { mCurrentAnimator.cancel(); } // Animate the four positioning/sizing properties in parallel, // back to their // original values. AnimatorSet set = new AnimatorSet(); set.play(ObjectAnimator.ofFloat(expandedImageView, View.X, startBounds.left)) .with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top)) .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScaleFinal)) .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScaleFinal)); set.setDuration(mShortAnimationDuration); set.setInterpolator(new DecelerateInterpolator()); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { thumbView.setAlpha(1f); expandedImageView.setVisibility(View.GONE); mCurrentAnimator = null; } @Override public void onAnimationCancel(Animator animation) { thumbView.setAlpha(1f); expandedImageView.setVisibility(View.GONE); mCurrentAnimator = null; } }); set.start(); mCurrentAnimator = set; } }); } }

分析

对zoomImageFromThumb方法添加Log,打印数据理解下:

private void zoomImageFromThumb(final View thumbView, int imageResId) {
        // If there's an animation in progress, cancel it immediately and
        // proceed with this one.
        if (mCurrentAnimator != null) {
            mCurrentAnimator.cancel();
        }

        // Load the high-resolution "zoomed-in" image.
        final ImageView expandedImageView = (ImageView) findViewById(R.id.expanded_image);
        expandedImageView.setImageResource(imageResId);

        // Calculate the starting and ending bounds for the zoomed-in image.
        // This step
        // involves lots of math. Yay, math.
        final Rect startBounds = new Rect();
        final Rect finalBounds = new Rect();
        final Point globalOffset = new Point();

        // The start bounds are the global visible rectangle of the thumbnail,
        // and the
        // final bounds are the global visible rectangle of the container view.
        // Also
        // set the container view's offset as the origin for the bounds, since
        // that's
        // the origin for the positioning animation properties (X, Y).
        thumbView.getGlobalVisibleRect(startBounds);
        findViewById(R.id.container).getGlobalVisibleRect(finalBounds, globalOffset);

        Log.d("ZoomActivity", "before startBounds =" + startBounds);
        Log.d("ZoomActivity", "before finalBounds =" + finalBounds);

        int[] thumbViewLocation = new int[2];
        thumbView.getLocationOnScreen(thumbViewLocation);
        int thumbViewX = thumbViewLocation[0];
        int thumbViewY = thumbViewLocation[1];

        int[] containerLocation = new int[2];
        findViewById(R.id.container).getLocationOnScreen(containerLocation);
        int containerX = containerLocation[0];
        int containerY = containerLocation[1];
        Log.d("ZoomActivity", "getLocationOnScreen thumbViewX =" + thumbViewX + "   thumbViewY=" + thumbViewY);
        Log.d("ZoomActivity", "getLocationOnScreen containerX =" + containerX + "   containerY=" + containerY);

        startBounds.offset(-globalOffset.x, -globalOffset.y);
        finalBounds.offset(-globalOffset.x, -globalOffset.y);

        Log.d("ZoomActivity", "after startBounds =" + startBounds);
        Log.d("ZoomActivity", "after finalBounds =" + finalBounds);

        // Adjust the start bounds to be the same aspect ratio as the final
        // bounds using the
        // "center crop" technique. This prevents undesirable stretching during
        // the animation.
        // Also calculate the start scaling factor (the end scaling factor is
        // always 1.0).
        float startScale;
        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();
            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;
        }

        // Hide the thumbnail and show the zoomed-in view. When the animation
        // begins,
        // it will position the zoomed-in view in the place of the thumbnail.
        thumbView.setAlpha(0f);
        expandedImageView.setVisibility(View.VISIBLE);

        // Set the pivot point for SCALE_X and SCALE_Y transformations to the
        // top-left corner of
        // the zoomed-in view (the default is the center of the view).
        expandedImageView.setPivotX(0f);
        expandedImageView.setPivotY(0f);

        // Construct and run the parallel animation of the four translation and
        // scale properties
        // (X, Y, SCALE_X, and SCALE_Y).
        AnimatorSet set = new AnimatorSet();
        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();
        mCurrentAnimator = set;

        // Upon clicking the zoomed-in image, it should zoom back down to the
        // original bounds
        // and show the thumbnail instead of the expanded image.
        final float startScaleFinal = startScale;
        expandedImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mCurrentAnimator != null) {
                    mCurrentAnimator.cancel();
                }

                // Animate the four positioning/sizing properties in parallel,
                // back to their
                // original values.
                AnimatorSet set = new AnimatorSet();
                set.play(ObjectAnimator.ofFloat(expandedImageView, View.X, startBounds.left))
                        .with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top))
                        .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScaleFinal))
                        .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScaleFinal));
                set.setDuration(mShortAnimationDuration);
                set.setInterpolator(new DecelerateInterpolator());
                set.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        thumbView.setAlpha(1f);
                        expandedImageView.setVisibility(View.GONE);
                        mCurrentAnimator = null;
                    }

                    @Override
                    public void onAnimationCancel(Animator animation) {
                        thumbView.setAlpha(1f);
                        expandedImageView.setVisibility(View.GONE);
                        mCurrentAnimator = null;
                    }
                });
                set.start();
                mCurrentAnimator = set;
            }
        });
    }

Log:

01-05 00:05:33.585 16826 16826 D ZoomActivity: before startBounds =Rect(32, 246 - 232, 396)
01-05 00:05:33.585 16826 16826 D ZoomActivity: before finalBounds =Rect(0, 144 - 720, 1184)
01-05 00:05:33.586 16826 16826 D ZoomActivity: getLocationOnScreen thumbViewX =32   thumbViewY=246
01-05 00:05:33.586 16826 16826 D ZoomActivity: getLocationOnScreen containerX =0   containerY=144
01-05 00:05:33.586 16826 16826 D ZoomActivity: after startBounds =Rect(32, 102 - 232, 252)
01-05 00:05:33.586 16826 16826 D ZoomActivity: after finalBounds =Rect(0, 0 - 720, 1040)

A,

从Log知:getGlobalVisibleRect方法返回的Rect对象的左上角坐标与getLocationOnScreen方法是一致的,也就理解了:

getGlobalVisibleRect获取全局坐标系的一个视图区域, 返回一个填充的Rect对象,该Rect是基于总整个屏幕的。

对view进行offset处理是为了去除相对屏幕的偏移量,使”子View“的Rect的左上角坐标是相对"父View",这样利于计算”子View“相对”父View“的最终显示位置

B,

  float startScale;
        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();
            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;
        }
   为了使用相同的宽高比例,避免出现较大的不正常拉伸 ,需要进行计算:
         

   1.满足if判断说明:“屏幕宽高比例” 大于 “缩略图宽高比例”,说明最终显示的“宽度”较大,为了较少变形拉伸,因此需要在“宽度”上进行调整,此时就应该拿相对比较接近的"原始高度"因素进行计算(为了较少偏差),首先通过"高度"计算出“缩放因子”(startScale),使用同样的缩放因子(startScale)计算出“应该有的原始宽度”,然后用“应该有的原始宽度”减去“真实的原始宽度“就是总的宽度偏差,然后除以2算出单侧的偏差(deltaHeight),然后对”真实的原始宽度“,用”单侧的偏差(deltaHeight)“进行微调 

   2.满足else判断说明:“屏幕宽高比例” 小于 “缩略图宽高比例”,说明最终显示的“高度”较大,为了较少变形拉伸,因此需要在“高度”上进行调整,类似上面分析...

上面使用了:”相同的宽高比例“及"中心剪切技术"等概念。

你可能感兴趣的:(android)