Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
С помощью ViewFlipper создадим 3D-эффект переворачивания картинки. Представим, что у нас есть две картинки: игральная карта, монетка и т.д. Нужно красиво заменить одну картинку на обратную.
Для примера я взял готовые файлы из open-source-проекта android-3d-flip-view-transition - A 3D view flip transition for Android.
Добавим в свой проект новый пакет com.tekle.oss.android.animation и в него добавим два класса:
/**
* Copyright (c) 2012 Ephraim Tekle [email protected]
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @author Ephraim A. Tekle
*
*/
package com.tekle.oss.android.animation;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.TranslateAnimation;
import android.view.animation.Animation.AnimationListener;
import android.widget.ViewAnimator;
/**
* This class contains methods for creating {@link Animation} objects for some of the most common animation, including a 3D flip animation, {@link FlipAnimation}.
* Furthermore, utility methods are provided for initiating fade-in-then-out and flip animations.
*
* @author Ephraim A. Tekle
*
*/
public class AnimationFactory {
/**
* The {@code FlipDirection} enumeration defines the most typical flip view transitions: left-to-right and right-to-left. {@code FlipDirection} is used during the creation of {@link FlipAnimation} animations.
*
* @author Ephraim A. Tekle
*
*/
public static enum FlipDirection {
LEFT_RIGHT,
RIGHT_LEFT;
public float getStartDegreeForFirstView() {
return 0;
}
public float getStartDegreeForSecondView() {
switch(this) {
case LEFT_RIGHT:
return -90;
case RIGHT_LEFT:
return 90;
default:
return 0;
}
}
public float getEndDegreeForFirstView() {
switch(this) {
case LEFT_RIGHT:
return 90;
case RIGHT_LEFT:
return -90;
default:
return 0;
}
}
public float getEndDegreeForSecondView() {
return 0;
}
public FlipDirection theOtherDirection() {
switch(this) {
case LEFT_RIGHT:
return RIGHT_LEFT;
case RIGHT_LEFT:
return LEFT_RIGHT;
default:
return null;
}
}
};
/**
* Create a pair of {@link FlipAnimation} that can be used to flip 3D transition from {@code fromView} to {@code toView}. A typical use case is with {@link ViewAnimator} as an out and in transition.
*
* NOTE: Avoid using this method. Instead, use {@link #flipTransition}.
*
* @param fromView the view transition away from
* @param toView the view transition to
* @param dir the flip direction
* @param duration the transition duration in milliseconds
* @param interpolator the interpolator to use (pass {@code null} to use the {@link AccelerateInterpolator} interpolator)
* @return
*/
public static Animation[] flipAnimation(final View fromView, final View toView, FlipDirection dir, long duration, Interpolator interpolator) {
Animation[] result = new Animation[2];
float centerX;
float centerY;
centerX = fromView.getWidth() / 2.0f;
centerY = fromView.getHeight() / 2.0f;
Animation outFlip= new FlipAnimation(dir.getStartDegreeForFirstView(), dir.getEndDegreeForFirstView(), centerX, centerY, FlipAnimation.SCALE_DEFAULT, FlipAnimation.ScaleUpDownEnum.SCALE_DOWN);
outFlip.setDuration(duration);
outFlip.setFillAfter(true);
outFlip.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator);
AnimationSet outAnimation = new AnimationSet(true);
outAnimation.addAnimation(outFlip);
result[0] = outAnimation;
// Uncomment the following if toView has its layout established (not the case if using ViewFlipper and on first show)
//centerX = toView.getWidth() / 2.0f;
//centerY = toView.getHeight() / 2.0f;
Animation inFlip = new FlipAnimation(dir.getStartDegreeForSecondView(), dir.getEndDegreeForSecondView(), centerX, centerY, FlipAnimation.SCALE_DEFAULT, FlipAnimation.ScaleUpDownEnum.SCALE_UP);
inFlip.setDuration(duration);
inFlip.setFillAfter(true);
inFlip.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator);
inFlip.setStartOffset(duration);
AnimationSet inAnimation = new AnimationSet(true);
inAnimation.addAnimation(inFlip);
result[1] = inAnimation;
return result;
}
/**
* Flip to the next view of the {@code ViewAnimator}'s subviews. A call to this method will initiate a {@link FlipAnimation} to show the next View.
* If the currently visible view is the last view, flip direction will be reversed for this transition.
*
* @param viewAnimator the {@code ViewAnimator}
* @param dir the direction of flip
*/
public static void flipTransition(final ViewAnimator viewAnimator, FlipDirection dir) {
final View fromView = viewAnimator.getCurrentView();
final int currentIndex = viewAnimator.getDisplayedChild();
final int nextIndex = (currentIndex + 1)%viewAnimator.getChildCount();
final View toView = viewAnimator.getChildAt(nextIndex);
Animation[] animc = AnimationFactory.flipAnimation(fromView, toView, (nextIndex < currentIndex?dir.theOtherDirection():dir), 500, null);
viewAnimator.setOutAnimation(animc[0]);
viewAnimator.setInAnimation(animc[1]);
viewAnimator.showNext();
}
//////////////
/**
* Slide animations to enter a view from left.
*
* @param duration the animation duration in milliseconds
* @param interpolator the interpolator to use (pass {@code null} to use the {@link AccelerateInterpolator} interpolator)
* @return a slide transition animation
*/
public static Animation inFromLeftAnimation(long duration, Interpolator interpolator) {
Animation inFromLeft = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0.0f,
Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f
);
inFromLeft.setDuration(duration);
inFromLeft.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); //AccelerateInterpolator
return inFromLeft;
}
/**
* Slide animations to hide a view by sliding it to the right
*
* @param duration the animation duration in milliseconds
* @param interpolator the interpolator to use (pass {@code null} to use the {@link AccelerateInterpolator} interpolator)
* @return a slide transition animation
*/
public static Animation outToRightAnimation(long duration, Interpolator interpolator) {
Animation outtoRight = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, +1.0f,
Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f
);
outtoRight.setDuration(duration);
outtoRight.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator);
return outtoRight;
}
/**
* Slide animations to enter a view from right.
*
* @param duration the animation duration in milliseconds
* @param interpolator the interpolator to use (pass {@code null} to use the {@link AccelerateInterpolator} interpolator)
* @return a slide transition animation
*/
public static Animation inFromRightAnimation(long duration, Interpolator interpolator) {
Animation inFromRight = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, +1.0f, Animation.RELATIVE_TO_PARENT, 0.0f,
Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f
);
inFromRight.setDuration(duration);
inFromRight.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator);
return inFromRight;
}
/**
* Slide animations to hide a view by sliding it to the left.
*
* @param duration the animation duration in milliseconds
* @param interpolator the interpolator to use (pass {@code null} to use the {@link AccelerateInterpolator} interpolator)
* @return a slide transition animation
*/
public static Animation outToLeftAnimation(long duration, Interpolator interpolator) {
Animation outtoLeft = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, -1.0f,
Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f
);
outtoLeft.setDuration(duration);
outtoLeft.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator);
return outtoLeft;
}
/**
* Slide animations to enter a view from top.
*
* @param duration the animation duration in milliseconds
* @param interpolator the interpolator to use (pass {@code null} to use the {@link AccelerateInterpolator} interpolator)
* @return a slide transition animation
*/
public static Animation inFromTopAnimation(long duration, Interpolator interpolator) {
Animation infromtop = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f,
Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0.0f
);
infromtop.setDuration(duration);
infromtop.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator);
return infromtop;
}
/**
* Slide animations to hide a view by sliding it to the top
*
* @param duration the animation duration in milliseconds
* @param interpolator the interpolator to use (pass {@code null} to use the {@link AccelerateInterpolator} interpolator)
* @return a slide transition animation
*/
public static Animation outToTopAnimation(long duration, Interpolator interpolator) {
Animation outtotop = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f,
Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, -1.0f
);
outtotop.setDuration(duration);
outtotop.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator);
return outtotop;
}
/**
* A fade animation that will fade the subject in by changing alpha from 0 to 1.
*
* @param duration the animation duration in milliseconds
* @param delay how long to wait before starting the animation, in milliseconds
* @return a fade animation
* @see #fadeInAnimation(View, long)
*/
public static Animation fadeInAnimation(long duration, long delay) {
Animation fadeIn = new AlphaAnimation(0, 1);
fadeIn.setInterpolator(new DecelerateInterpolator());
fadeIn.setDuration(duration);
fadeIn.setStartOffset(delay);
return fadeIn;
}
/**
* A fade animation that will fade the subject out by changing alpha from 1 to 0.
*
* @param duration the animation duration in milliseconds
* @param delay how long to wait before starting the animation, in milliseconds
* @return a fade animation
* @see #fadeOutAnimation(View, long)
*/
public static Animation fadeOutAnimation(long duration, long delay) {
Animation fadeOut = new AlphaAnimation(1, 0);
fadeOut.setInterpolator(new AccelerateInterpolator());
fadeOut.setStartOffset(delay);
fadeOut.setDuration(duration);
return fadeOut;
}
/**
* A fade animation that will ensure the View starts and ends with the correct visibility
* @param view the View to be faded in
* @param duration the animation duration in milliseconds
* @return a fade animation that will set the visibility of the view at the start and end of animation
*/
public static Animation fadeInAnimation(long duration, final View view) {
Animation animation = fadeInAnimation(500, 0);
animation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
view.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationStart(Animation animation) {
view.setVisibility(View.GONE);
}
});
return animation;
}
/**
* A fade animation that will ensure the View starts and ends with the correct visibility
* @param view the View to be faded out
* @param duration the animation duration in milliseconds
* @return a fade animation that will set the visibility of the view at the start and end of animation
*/
public static Animation fadeOutAnimation(long duration, final View view) {
Animation animation = fadeOutAnimation(500, 0);
animation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
view.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationStart(Animation animation) {
view.setVisibility(View.VISIBLE);
}
});
return animation;
}
/**
* Creates a pair of animation that will fade in, delay, then fade out
* @param duration the animation duration in milliseconds
* @param delay how long to wait after fading in the subject and before starting the fade out
* @return a fade in then out animations
*/
public static Animation[] fadeInThenOutAnimation(long duration, long delay) {
return new Animation[] {fadeInAnimation(duration,0), fadeOutAnimation(duration, duration+delay)};
}
/**
* Fades the view in. Animation starts right away.
* @param v the view to be faded in
*/
public static void fadeOut(View v) {
if (v==null) return;
v.startAnimation(fadeOutAnimation(500, v));
}
/**
* Fades the view out. Animation starts right away.
* @param v the view to be faded out
*/
public static void fadeIn(View v) {
if (v==null) return;
v.startAnimation(fadeInAnimation(500, v));
}
/**
* Fades the view in, delays the specified amount of time, then fades the view out
* @param v the view to be faded in then out
* @param delay how long the view will be visible for
*/
public static void fadeInThenOut(final View v, long delay) {
if (v==null) return;
v.setVisibility(View.VISIBLE);
AnimationSet animation = new AnimationSet(true);
Animation[] fadeInOut = fadeInThenOutAnimation(500,delay);
animation.addAnimation(fadeInOut[0]);
animation.addAnimation(fadeInOut[1]);
animation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
v.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationStart(Animation animation) {
v.setVisibility(View.VISIBLE);
}
});
v.startAnimation(animation);
}
}
/**
* Copyright (c) 2012 Ephraim Tekle [email protected]
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @author Ephraim A. Tekle
*
*/
package com.tekle.oss.android.animation;
import android.view.animation.Animation;
import android.graphics.Camera;
import android.graphics.Matrix;
import android.view.animation.Transformation;
/**
* This class extends Animation to support a 3D flip view transition animation. Two instances of this class is
* required: one for the "from" view and another for the "to" view.
*
* NOTE: use {@link AnimationFactory} to use this class.
*
* @author Ephraim A. Tekle
*
*/
public class FlipAnimation extends Animation {
private final float mFromDegrees;
private final float mToDegrees;
private final float mCenterX;
private final float mCenterY;
private Camera mCamera;
private final ScaleUpDownEnum scaleType;
/**
* How much to scale up/down. The default scale of 75% of full size seems optimal based on testing. Feel free to experiment away, however.
*/
public static final float SCALE_DEFAULT = 0.75f;
private float scale;
/**
* Constructs a new {@code FlipAnimation} object.Two {@code FlipAnimation} objects are needed for a complete transition b/n two views.
*
* @param fromDegrees the start angle in degrees for a rotation along the y-axis, i.e. in-and-out of the screen, i.e. 3D flip. This should really be multiple of 90 degrees.
* @param toDegrees the end angle in degrees for a rotation along the y-axis, i.e. in-and-out of the screen, i.e. 3D flip. This should really be multiple of 90 degrees.
* @param centerX the x-axis value of the center of rotation
* @param centerY the y-axis value of the center of rotation
* @param scale to get a 3D effect, the transition views need to be zoomed (scaled). This value must be b/n (0,1) or else the default scale {@link #SCALE_DEFAULT} is used.
* @param scaleType flip view transition is broken down into two: the zoom-out of the "from" view and the zoom-in of the "to" view. This parameter is used to determine which is being done. See {@link ScaleUpDownEnum}.
*/
public FlipAnimation(float fromDegrees, float toDegrees, float centerX, float centerY, float scale, ScaleUpDownEnum scaleType) {
mFromDegrees = fromDegrees;
mToDegrees = toDegrees;
mCenterX = centerX;
mCenterY = centerY;
this.scale = (scale<=0||scale>=1)?SCALE_DEFAULT:scale;
this.scaleType = scaleType==null?ScaleUpDownEnum.SCALE_CYCLE:scaleType;
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mCamera = new Camera();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float fromDegrees = mFromDegrees;
float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
final float centerX = mCenterX;
final float centerY = mCenterY;
final Camera camera = mCamera;
final Matrix matrix = t.getMatrix();
camera.save();
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
matrix.preScale(scaleType.getScale(scale, interpolatedTime), scaleType.getScale(scale, interpolatedTime), centerX, centerY);
}
/**
* This enumeration is used to determine the zoom (or scale) behavior of a {@link FlipAnimation}.
*
* @author Ephraim A. Tekle
*
*/
public static enum ScaleUpDownEnum {
/**
* The view will be scaled up from the scale value until it's at 100% zoom level (i.e. no zoom).
*/
SCALE_UP,
/**
* The view will be scaled down starting at no zoom (100% zoom level) until it's at a specified zoom level.
*/
SCALE_DOWN,
/**
* The view will cycle through a zoom down and then zoom up.
*/
SCALE_CYCLE,
/**
* No zoom effect is applied.
*/
SCALE_NONE;
/**
* The intermittent zoom level given the current or desired maximum zoom level for the specified iteration
*
* @param max the maximum desired or current zoom level
* @param iter the iteration (from 0..1).
* @return the current zoom level
*/
public float getScale(float max, float iter) {
switch(this) {
case SCALE_UP:
return max + (1-max)*iter;
case SCALE_DOWN:
return 1 - (1-max)*iter;
case SCALE_CYCLE: {
final boolean halfWay = (iter > 0.5);
if (halfWay) {
return max + (1-max)*(iter-0.5f)*2;
} else {
return 1 - (1-max)*(iter*2);
}
}
default:
return 1;
}
}
}
}
Подготовим разметку, добавив в неё ViewFlipper и два ImageView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:gravity="center"
android:scaleType="centerCrop"
android:src="@drawable/exampleimage" />
<ViewFlipper
android:id="@+id/viewFlipper"
android:layout_width="300dp"
android:layout_height="450dp"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:addStatesFromChildren="true"
android:clickable="true"
android:gravity="center" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="300dp"
android:layout_height="450dp"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:clickable="true"
android:gravity="center"
android:scaleType="fitCenter"
android:src="@drawable/ic_launcher" />
<ImageView
android:id="@+id/imageView2"
android:layout_width="300dp"
android:layout_height="450dp"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:clickable="true"
android:gravity="center"
android:scaleType="fitCenter"
android:src="@drawable/ic_cat" />
</ViewFlipper>
</RelativeLayout>
Теперь осталось написать код для обработки нажатия на элементах:
package ru.alexanderklimov.test;
import ...
import com.tekle.oss.android.animation.AnimationFactory;
import com.tekle.oss.android.animation.AnimationFactory.FlipDirection;
public class MainActivity extends Activity {
ImageView imageView1, imageView2;
ViewAnimator viewAnimator;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewAnimator = (ViewAnimator) findViewById(R.id.viewFlipper);
imageView1 = (ImageView) findViewById(R.id.imageView1);
imageView2 = (ImageView) findViewById(R.id.imageView2);
imageView1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 3D flip
AnimationFactory.flipTransition(viewAnimator,
FlipDirection.RIGHT_LEFT);
// AnimationFactory.fadeInThenOut(imageView1, 5000);
// AnimationFactory.fadeIn(imageView1);
}
});
imageView2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 3D flip
AnimationFactory.flipTransition(viewAnimator,
FlipDirection.LEFT_RIGHT);
}
});
}
}
На видео видно, как будет рабоать пример.
В вспомогательных классах есть и другие методы анимации. В моём примере я оставил две закомментированные строчки с использованием анимации исчезновения.