Android卡牌翻转动画效果实现
来源:互联网 发布:日本人皮肤知乎 编辑:程序博客网 时间:2024/05/16 15:21
Android卡牌翻转动画效果实现
简述
之前项目有个需求,需要实现卡牌翻转效果,自己试着写过,效果不是很好,后来找到了一个Rotatable-master的项目,使用里面提供的类实现了卡牌翻转的效果。默认情况下,翻转动画卡牌在动画进行中的拉伸变形幅度会很大,可以通过setCameraDistance方法来改变控件的应该说是观察点距离吧,让观察点远一点,这样反转动画看起来会舒服一点。
效果图
代码
.java 旋转工具类
import android.animation.Animator;import android.animation.AnimatorListenerAdapter;import android.animation.AnimatorSet;import android.animation.ObjectAnimator;import android.animation.ValueAnimator;import android.content.Context;import android.content.res.Configuration;import android.support.annotation.IntDef;import android.support.v4.view.animation.FastOutSlowInInterpolator;import android.util.DisplayMetrics;import android.util.Property;import android.view.Display;import android.view.MotionEvent;import android.view.View;import android.view.WindowManager;import android.view.animation.CycleInterpolator;import java.util.ArrayList;/** * 旋转工具类 * Created by Yahya Bayramoglu on 01/12/15. */public class Rotatable implements View.OnTouchListener { private static final int NULL_INT = -1; private final int FIT_ANIM_TIME = 300; public static final int DEFAULT_ROTATE_ANIM_TIME = 500; public static final int ROTATE_BOTH = 0; public static final int ROTATE_X = 1; public static final int ROTATE_Y = 2; @IntDef({ROTATE_X, ROTATE_Y, ROTATE_BOTH}) public @interface Direction { } public static final int FRONT_VIEW = 3; public static final int BACK_VIEW = 4; @IntDef({FRONT_VIEW, BACK_VIEW}) public @interface Side { } private RotationListener rotationListener; private View rootView, frontView, backView; private boolean touchEnable = true; private boolean shouldSwapViews = false; private int rotation; private int screenWidth = NULL_INT, screenHeight = NULL_INT; private int currentVisibleView = FRONT_VIEW; private float rotationCount; private float rotationDistance; private float oldX, oldY, currentX, currentY; private float currentXRotation = 0, currentYRotation = 0; private float maxDistanceX = NULL_INT, maxDistanceY = NULL_INT; private float defaultPivotX = NULL_INT, defaultPivotY = NULL_INT; private Rotatable(Builder builder) { this.rootView = builder.root; this.defaultPivotX = rootView.getPivotX(); this.defaultPivotY = rootView.getPivotY(); this.rotationListener = builder.listener; if (builder.pivotX != NULL_INT) { this.rootView.setPivotX(builder.pivotX); } if (builder.pivotY != NULL_INT) { this.rootView.setPivotY(builder.pivotY); } if (builder.frontId != NULL_INT) { this.frontView = rootView.findViewById(builder.frontId); } if (builder.backId != NULL_INT) { this.backView = rootView.findViewById(builder.backId); } this.rotation = builder.rotation; this.rotationCount = builder.rotationCount; this.rotationDistance = builder.rotationDistance; this.shouldSwapViews = frontView != null && backView != null; rootView.setOnTouchListener(this); } /** * This method needs to be call, if only you need to reset and * rebuild a view as rotatable with different configurations */ public void drop() { rootView.setPivotX(defaultPivotX); rootView.setPivotY(defaultPivotY); rootView.setOnTouchListener(null); rootView = null; frontView = null; backView = null; } /** * You can specify rotation direction as axis X, Y or BOTH */ public void setDirection(@Direction int direction) { if (!isRotationValid(direction)) { throw new IllegalArgumentException("Cannot specify given value as rotation direction!"); } this.rotation = direction; } /** * You may need to enable / disable touch interaction at some point, * so it is possible to do it so anytime by rotatable object */ public void setTouchEnable(boolean enable) { this.touchEnable = enable; } /** * To determine rotatable object is currently touchable or not */ public boolean isTouchEnable() { return touchEnable; } /** * If your application can be used multi orientated, then you have to declare * orientation changes to rotatable object, so it can recalculate its maxDistances. * <p> * You only need to inform rotatable object about orientation changes, when you specified * {@link Builder#rotationCount(float)} or {@link Builder#rotationDistance(float)} */ public void orientationChanged(int newOrientation) { if (screenWidth == NULL_INT) { calculateScreenDimensions(); } measureScreenUpToOrientation(newOrientation); // reset maxDistances values to recalculate them maxDistanceX = NULL_INT; maxDistanceY = NULL_INT; } /** * Call this method to reveal rotatable view's existence */ public void takeAttention() { ObjectAnimator animatorX = ObjectAnimator.ofFloat(rootView, View.ROTATION_X, 10); ObjectAnimator animatorY = ObjectAnimator.ofFloat(rootView, View.ROTATION_Y, -10); AnimatorSet set = new AnimatorSet(); set.setDuration(DEFAULT_ROTATE_ANIM_TIME); set.setInterpolator(new CycleInterpolator(0.8f)); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); rootView.animate().rotationX(0).rotationY(0).setDuration(FIT_ANIM_TIME) .setInterpolator(new FastOutSlowInInterpolator()).start(); } }); set.playTogether(animatorX, animatorY); set.start(); } /** * Animate rotatable object with given direction and degree also possible * to set duration and a listener with other derivation of this method */ public void rotate(int direction, float degree) { rotate(direction, degree, DEFAULT_ROTATE_ANIM_TIME); } public void rotate(int direction, float degree, int duration) { rotate(direction, degree, duration, null); } public void rotate(final int direction, float degree, int duration, Animator.AnimatorListener listener) { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.setDuration(duration); animatorSet.setInterpolator(new FastOutSlowInInterpolator()); ArrayList<Animator> animators = new ArrayList<>(); if (direction == ROTATE_X || direction == ROTATE_BOTH) { animators.add(getAnimatorForProperty(View.ROTATION_X, direction, degree)); } if (direction == ROTATE_Y || direction == ROTATE_BOTH) { animators.add(getAnimatorForProperty(View.ROTATION_Y, direction, degree)); } if (listener != null) { animatorSet.addListener(listener); } animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); updateRotationValues(true); } }); animatorSet.playTogether(animators); animatorSet.start(); } /** * Rotates once around in given direction */ public void rotateOnce() { float toDegree; if (rotation == ROTATE_X) { toDegree = rootView.getRotationX(); } else if (rotation == ROTATE_Y) { toDegree = rootView.getRotationY(); } else { toDegree = rootView.getRotation(); } toDegree += 180; rotate(rotation, toDegree); } /** * Returns true if currently frontView is visible, false otherwise */ public boolean isFront() { return getCurrentVisibleView() == FRONT_VIEW; } /** * Returns currentVisibleView value as {@link Side} */ public @Side int getCurrentVisibleView() { return currentVisibleView; } public float getCurrentXRotation() { return currentXRotation; } public float getCurrentYRotation() { return currentYRotation; } private Animator getAnimatorForProperty(Property property, final int direction, float degree) { ObjectAnimator animator = ObjectAnimator.ofFloat(rootView, property, degree); if (shouldSwapViews) { animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { updateRotationValues(false); swapViews(direction); } }); } return animator; } private void updateRotationValues(boolean notifyListener) { currentXRotation = rootView.getRotationX(); currentYRotation = rootView.getRotationY(); if (notifyListener) { notifyListenerRotationChanged(); } } @Override public boolean onTouch(View v, MotionEvent event) { if (touchEnable) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { restoreOldPositions(event); break; } case MotionEvent.ACTION_MOVE: { restoreNewPositions(event); handleRotation(); if (shouldSwapViews) { swapViews(rotation); } notifyListenerRotationChanged(); break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: { fitRotation(); break; } } return true; } else { return false; } } private void restoreOldPositions(MotionEvent event) { if (shouldRotateX()) { oldY = getYValue(event.getRawY()); } if (shouldRotateY()) { oldX = getXValue(event.getRawX()); } } private float getXValue(float rawX) { if (rotationCount != NULL_INT && maxDistanceX != NULL_INT) { return rawX * rotationCount * 180 / maxDistanceX; } if (rotationDistance != NULL_INT) { return rawX * 180 / rotationDistance; } return rawX; } private float getYValue(float rawY) { if (rotationCount != NULL_INT && maxDistanceY != NULL_INT) { return rawY * rotationCount * 180 / maxDistanceY; } if (rotationDistance != NULL_INT) { return rawY * 180 / rotationDistance; } return rawY; } private void restoreNewPositions(MotionEvent event) { if (shouldRotateX()) { if (rotationCount != NULL_INT && maxDistanceY == NULL_INT) { maxDistanceY = (event.getRawY() - oldY) > 0 ? (getScreenHeight() - oldY) : oldY; oldY = getYValue(oldY); } currentY = getYValue(event.getRawY()); } if (shouldRotateY()) { if (rotationCount != NULL_INT && maxDistanceX == NULL_INT) { maxDistanceX = (event.getRawX() - oldX) > 0 ? (getScreenWidth() - oldX) : oldX; oldX = getXValue(oldX); } currentX = getXValue(event.getRawX()); } } private int getScreenWidth() { if (screenWidth == NULL_INT) { calculateScreenDimensions(); } return screenWidth; } private int getScreenHeight() { if (screenHeight == NULL_INT) { calculateScreenDimensions(); } return screenHeight; } private void calculateScreenDimensions() { Display display = ((WindowManager) rootView.getContext() .getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); DisplayMetrics metrics = new DisplayMetrics(); display.getMetrics(metrics); screenWidth = metrics.widthPixels; screenHeight = metrics.heightPixels; } private void measureScreenUpToOrientation(int screenOrientation) { int tempWidth = screenWidth, tempHeight = screenHeight; if (screenOrientation == Configuration.ORIENTATION_LANDSCAPE) { /** * If screenOrientation is landscape, then width will have a larger value than height */ screenWidth = Math.max(tempWidth, tempHeight); screenHeight = Math.min(tempWidth, tempHeight); } else { /** * If screenOrientation: * is portrait, then width will have a smaller value than height * is square, then width and height will be same * is unknown, then unknown to rotatable as well * so either way... */ screenWidth = Math.min(tempWidth, tempHeight); screenHeight = Math.max(tempWidth, tempHeight); } } private boolean shouldRotateX() { return rotation == ROTATE_X || rotation == ROTATE_BOTH; } private boolean shouldRotateY() { return rotation == ROTATE_Y || rotation == ROTATE_BOTH; } private void handleRotation() { if (shouldRotateX()) { float newXRotation = (rootView.getRotationX() + (oldY - currentY)) % 360; rootView.setRotationX(newXRotation); currentXRotation = newXRotation; oldY = currentY; } if (shouldRotateY()) { float newYRotation; if (isInFrontArea(currentXRotation)) { newYRotation = (rootView.getRotationY() + (currentX - oldX)) % 360; } else { newYRotation = (rootView.getRotationY() - (currentX - oldX)) % 360; } rootView.setRotationY(newYRotation); currentYRotation = newYRotation; oldX = currentX; } } private boolean isInFrontArea(float value) { return (-270 >= value && value >= -360) || (-90 <= value && value <= 90) || (270 <= value && value <= 360); } private void swapViews(int rotation) { boolean isFront = false; if (rotation == ROTATE_Y) { isFront = isInFrontArea(currentYRotation); if (!isInFrontArea(currentXRotation)) { isFront = !isFront; } } if (rotation == ROTATE_X) { isFront = isInFrontArea(currentXRotation); if (!isInFrontArea(currentYRotation)) { isFront = !isFront; } } if (rotation == ROTATE_BOTH) { isFront = (currentXRotation > -90 && currentXRotation < 90) && (currentYRotation > -90 && currentYRotation < 90) || (currentXRotation > -90 && currentXRotation < 90) && (currentYRotation > -360 && currentYRotation < -270) || (currentXRotation > -360 && currentXRotation < -270) && (currentYRotation > -90 && currentYRotation < 90) || (currentXRotation > -90 && currentXRotation < 90) && (currentYRotation > 270 && currentYRotation < 360) || (currentXRotation > 270 && currentXRotation < 360) && (currentYRotation > -90 && currentYRotation < 90) || (currentXRotation > 90 && currentXRotation < 270) && (currentYRotation > -270 && currentYRotation < -90) || (currentXRotation > -270 && currentXRotation < -90) && (currentYRotation > 90 && currentYRotation < 270) || (currentXRotation > 90 && currentXRotation < 270) && (currentYRotation > 90 && currentYRotation < 270) || (currentXRotation > -270 && currentXRotation < -90) && (currentYRotation > -270 && currentYRotation < -90); } boolean shouldSwap = (isFront && currentVisibleView == BACK_VIEW) || (!isFront && currentVisibleView == FRONT_VIEW); if (shouldSwap) { frontView.setVisibility(isFront ? View.VISIBLE : View.GONE); backView.setVisibility(isFront ? View.GONE : View.VISIBLE); currentVisibleView = isFront ? FRONT_VIEW : BACK_VIEW; } } private void notifyListenerRotationChanged() { if (rotationListener != null) { rotationListener.onRotationChanged(currentXRotation, currentYRotation); } } private void fitRotation() { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.setDuration(FIT_ANIM_TIME); animatorSet.setInterpolator(new FastOutSlowInInterpolator()); ArrayList<Animator> animators = new ArrayList<>(); if (shouldRotateY()) { animators.add(ObjectAnimator.ofFloat(rootView, View.ROTATION_Y, getRequiredRotation(rootView.getRotationY()))); } if (shouldRotateX()) { animators.add(ObjectAnimator.ofFloat(rootView, View.ROTATION_X, getRequiredRotation(rootView.getRotationX()))); } animatorSet.playTogether(animators); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); updateRotationValues(true); } }); animatorSet.start(); // Reset max values to calculate again on touch down maxDistanceX = NULL_INT; maxDistanceY = NULL_INT; } private float getRequiredRotation(float currentRotation) { float requiredRotation; if (currentRotation < -270) { requiredRotation = -360; } else if (currentRotation < -90 && currentRotation > -270) { requiredRotation = -180; } else if (currentRotation > -90 && currentRotation < 90) { requiredRotation = 0; } else if (currentRotation > 90 && currentRotation < 270) { requiredRotation = 180; } else { requiredRotation = 360; } return requiredRotation; } /** * Listener to get notified whenever view's rotation is changed */ public interface RotationListener { void onRotationChanged(float newRotationX, float newRotationY); } public static class Builder { private View root; private RotationListener listener; private int rotation = NULL_INT; private int frontId = NULL_INT; private int backId = NULL_INT; private int pivotX = NULL_INT; private int pivotY = NULL_INT; private float rotationCount = NULL_INT; private float rotationDistance = NULL_INT; public Builder(View viewToRotate) { this.root = viewToRotate; } /** * This listener will receive current rotation values of given view */ public Builder listener(RotationListener listener) { this.listener = listener; return this; } /** * Declaring sides will provide swapping between them when necessary, * if not declared, then rootView will be rotating by itself without any other effect */ public Builder sides(int frontViewId, int backViewId) { this.frontId = frontViewId; this.backId = backViewId; return this; } /** * Specify an axis or both axises to rotate around */ public Builder direction(@Direction int rotation) { this.rotation = rotation; return this; } /** * This method provides view to rotate only as given rotation count, * irrelevant to its position or touch distance */ public Builder rotationCount(float count) { if (rotationDistance != NULL_INT) { throw new IllegalArgumentException("You cannot specify both distance and count for rotation limitation."); } this.rotationCount = count; return this; } /** * This method provides view to rotate once in given distance, * note that it won't rotate full if touch distance is not enough * but it may still fit the rotation. If you want to ensure rotation gets completed * see {@link #rotationCount(float} */ public Builder rotationDistance(float distance) { if (rotationCount != NULL_INT) { throw new IllegalArgumentException("You cannot specify both distance and count for rotation limitation."); } this.rotationDistance = distance; return this; } /** * Consider not to change pivot values because view may out of its bounders and get invisible. */ public Builder pivot(int pivotX, int pivotY) { this.pivotX = pivotX; this.pivotY = pivotY; return this; } /** * Consider not to change pivot values because view may out of its bounders and get invisible. */ public Builder pivotX(int pivotX) { this.pivotX = pivotX; return this; } /** * Consider not to change pivot values because view may out of its bounders and get invisible. */ public Builder pivotY(int pivotY) { this.pivotY = pivotY; return this; } public Rotatable build() { if (rotation == NULL_INT || !isRotationValid(rotation)) { throw new IllegalArgumentException("You must specify a direction!"); } return new Rotatable(this); } } private static boolean isRotationValid(int value) { return value == ROTATE_X || value == ROTATE_Y || value == ROTATE_BOTH; }}
.java 用到了nineoldandroids辅助做了一些动画
import android.app.Activity;import android.os.Bundle;import android.view.View;import android.widget.ImageView;import android.widget.RelativeLayout;import com.example.wood.haha.DisplayImageOptionsUtil;import com.example.wood.haha.R;import com.example.wood.haha.Rotatable;import com.nineoldandroids.view.ViewHelper;import com.nostra13.universalimageloader.core.ImageLoader;/** * Created by Wood on 2016/8/12. */public class RotateCardActivity extends Activity implements View.OnClickListener { private static final String LOG_TAG = "RotateCardActivity"; private RelativeLayout rlCardRoot; private ImageView imageViewBack; private ImageView imageViewFront; private void initView() { rlCardRoot = (RelativeLayout) findViewById(R.id.rl_card_root); imageViewBack = (ImageView) findViewById(R.id.imageView_back); imageViewFront = (ImageView) findViewById(R.id.imageView_front); imageViewBack.setOnClickListener(this); imageViewFront.setOnClickListener(this); setCameraDistance(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_rotate_card); initView(); initData(); } /** * 设置数据 */ public void initData() { String imageUri = "drawable://" + R.drawable.blue_back; ImageLoader.getInstance().displayImage(imageUri, imageViewBack, DisplayImageOptionsUtil.getDisplayImageOptionsNoCache(R.drawable.card_bg_gray)); imageUri = "drawable://" + R.drawable.blue_front; ImageLoader.getInstance().displayImage(imageUri, imageViewFront, DisplayImageOptionsUtil.getDisplayImageOptionsNoCache(R.drawable.card_bg_gray)); imageViewBack.setVisibility(View.VISIBLE); imageViewFront.setVisibility(View.INVISIBLE); } /** * 翻牌 */ public void cardTurnover() { if (View.VISIBLE == imageViewBack.getVisibility()) { ViewHelper.setRotationY(imageViewFront, 180f);//先翻转180,转回来时就不是反转的了 Rotatable rotatable = new Rotatable.Builder(rlCardRoot) .sides(R.id.imageView_back, R.id.imageView_front) .direction(Rotatable.ROTATE_Y) .rotationCount(1) .build(); rotatable.setTouchEnable(false); rotatable.rotate(Rotatable.ROTATE_Y, -180, 1500); } else if (View.VISIBLE == imageViewFront.getVisibility()) { Rotatable rotatable = new Rotatable.Builder(rlCardRoot) .sides(R.id.imageView_back, R.id.imageView_front) .direction(Rotatable.ROTATE_Y) .rotationCount(1) .build(); rotatable.setTouchEnable(false); rotatable.rotate(Rotatable.ROTATE_Y, 0, 1500); } } /** * 改变视角距离, 贴近屏幕 */ private void setCameraDistance() { int distance = 10000; float scale = getResources().getDisplayMetrics().density * distance; rlCardRoot.setCameraDistance(scale); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.imageView_back: case R.id.imageView_front: cardTurnover(); break; } }}
.xml 卡牌的正反面,布局需要一个viewgroup包着两个控件,这两个控件就是正反面
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#62ADC6"> <RelativeLayout android:id="@+id/rl_card_root" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/transparent"> <ImageView android:id="@+id/imageView_back" android:layout_width="258dp" android:layout_height="350dp" android:layout_centerInParent="true" /> <ImageView android:id="@+id/imageView_front" android:layout_width="258dp" android:layout_height="350dp" android:layout_centerInParent="true" /> </RelativeLayout></FrameLayout>
素材
资源链接
Rotatable-master项目代码
0 0
- Android卡牌翻转动画效果实现
- 简单实现Android图片翻转动画效果
- Android图片翻转动画效果
- android 翻转效果动画源码
- Android 翻转动画 Rotate3dAnimation 效果
- Android 卡片翻转动画效果
- Android动画之3D翻转效果实现函数分析
- Android 2D翻转动画效果的实现
- 实现卡片翻转的动画效果
- 实现翻转卡片的动画效果
- android实现图片翻转动画
- Android实现卡片翻转动画
- Android/OPhone动画分析之翻转效果
- Android\OPhone动画分析之翻转效果
- Android 动画分析之翻转效果
- Android---显示卡片翻转的动画效果
- 实现Gmail邮箱翻转效果之翻转动画
- cocos2dx实现简单卡牌翻转效果
- iOS开发的诡异技巧
- 随机梯度下降和批量梯度下降
- IT-linux--ssh-bashrc bash_profile解释
- 事务处理
- 文件元数据
- Android卡牌翻转动画效果实现
- Oracle.ManagedDataAccess.dll 连接Oracle数据库不需要安装客户端
- Linux线程相关指令
- 2016年11月 系统集成项目管理工程师 中级 培训视频 全套视屏 需要联系
- 浅谈快速seo排名软件及用后体验,seo思维决定最终成败
- event.preventDefault()方法
- java 对数组进行插入删除修改
- 浏览器调起app应用方法
- 关于editText和scrollView起冲突,editText内容超过编剧不能滑动的问题