Android 属性动画拓展(一)
前言
通过之前Android动画总结一文,我们对Android开发中的动画实现做了两种分类传统动画(包括帧动画和补间动画)和属性动画。但是,从动画实现机理及动画所呈现的效果来说,之前的动画实现其实可以归结为一类动画,这类动画,无论是帧动画还是补间动画,亦或是属性动画,都是严格由代码控制,动画的过程(从开始到结束,或者是循环播放的动画),都不受外部交互动作的控制或干预;然而,日常开发中还有一类动画是伴随着交互效果动态变化的,比如说ViewPager的切换,可以随着我们滑动呈现不同的效果;QQ空间下拉时,顶部背景的放大效果,动画效果和用户的操作相互联动。
下面,就结合属性动画的说一说如何实现这类动画。
属性动画的意义
我们知道,从Android3.0 (API LEVEL 11)开始,Android SDK 新增了属性动画();这四个字虽然简单,但却意义深远;我们知道对象的属性决定着对象会呈现出怎样的状态;想一个我们平时使用的ImageView,Button都有哪些属性呢?当他们的属性值发生变化时这些View又会怎么样呢?我们看一下下面的这些效果图:
效果图
实现代码
public class PlayActivity extends AppCompatActivity {
private static final String TAG = "PlayActivity";
@BindView(R.id.image)
ImageView mImage;
@BindView(R.id.rotateX)
EasySeekBar mRotateX;
@BindView(R.id.width)
TextView mWidth;
@BindView(R.id.height)
TextView mHeight;
@BindView(R.id.translationX)
EasySeekBar mTranslationX;
@BindView(R.id.translationY)
EasySeekBar mTranslationY;
@BindView(R.id.translationZ)
EasySeekBar mTranslationZ;
@BindView(R.id.PivotX)
EasySeekBar mPivotX;
@BindView(R.id.PivotY)
EasySeekBar mPivotY;
@BindView(R.id.negate)
CheckBox mNegate;
@BindView(R.id.rotateY)
EasySeekBar mRotateY;
@BindView(R.id.scaleX)
EasySeekBar mScaleX;
@BindView(R.id.scaleY)
EasySeekBar mScaleY;
@BindView(R.id.scrollX)
EasySeekBar mScrollX;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play);
ButterKnife.bind(this);
playView();
}
private void playView() {
mTranslationX.setOnProgressChangedListener(new EasySeekBar.onProgressChangeListener() {
@Override
public void onChangedValue(int value) {
mTranslationX.setNegate(mNegate.isChecked());
ViewCompat.setTranslationX(mImage, value);
}
});
mTranslationZ.setOnProgressChangedListener(new EasySeekBar.onProgressChangeListener() {
@Override
public void onChangedValue(int value) {
mTranslationZ.setNegate(mNegate.isChecked());
ViewCompat.setTranslationZ(mImage, value);
}
});
mRotateX.setOnProgressChangedListener(new EasySeekBar.onProgressChangeListener() {
@Override
public void onChangedValue(int value) {
mRotateX.setNegate(mNegate.isChecked());
ViewCompat.setRotationX(mImage, value);
}
});
mScaleX.setOnProgressChangedListener(new EasySeekBar.onProgressChangeListener() {
@Override
public void onChangedValue(int value) {
float scale = value / 100.0f;
ViewCompat.setScaleX(mImage, scale);
}
});
mScrollX.setOnProgressChangedListener(new EasySeekBar.onProgressChangeListener() {
@Override
public void onChangedValue(int value) {
mScrollX.setNegate(mNegate.isChecked());
mImage.scrollTo(value, 0);
}
});
.......
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
mWidth.setText("ImageWidth= " + mImage.getMeasuredWidth());
mHeight.setText("ImageHeight= " + mImage.getMeasuredHeight());
mScrollX.setMax(mImage.getMeasuredWidth());
mScrollY.setMax(mImage.getMeasuredHeight());
mTranslationX.setMax(mImage.getMeasuredWidth());
mTranslationY.setMax(mImage.getMeasuredHeight());
mPivotX.setMax(mImage.getMeasuredWidth());
mPivotY.setMax(mImage.getMeasuredHeight());
mRotateX.setMax(360);
mRotateY.setMax(360);
}
}
这里解释一下:
- EasySeekBar 是结合SeekBar简单实现的一个自定义控件,主要是为了方便在右侧显示滑动进度和负数值的传递,具体可参考源码。
- ViewCompat 是View实现兼容性的辅助类,比如上像上面
ViewCompat.setTranslationX(mImage, value);
ViewCompat.setTranslationZ(mImage, value);
我们可以替换为
mImage.setTranslationX(value);
mImage.setTranslationZ(value);
setTranslationX是没问题,但是setTranslationZ是Android5.0(API LEVEL 21)才支持的方法,如果我们的minSDK 小于21时,在开发环境中会报出红色的提示,这个时候我们要么提交if判断,要么就得修改minSDK的版本,但是使用ViewCompat就不用考虑这个事情了,因为他会帮我们主动实现兼容,这点从他的源码也可以看出来。
- 在Activity启动后,我们在onWindowFocusChanged方法里获取到了顶部ImageView的宽高,并用他作为SeekBar滑动的终点值。同时也初始化了其他的一些属性的最大值。
以上代码只是截取了部分实现,完整内容可查看文末源码
这里可以看到,当我们通过不同到的SeekBar修改ImageView不同的属性值时,ImageView的样式和他在整个视图中的位置都发生着变化,这就是属性动画的意义。
scrollTo VS setTranslationX(Y)
在布局文件中,我们为ImageView设置了背景
通过图我们可以明显体会到scrollTo和setTranslationX的区别,scrollTo并没有让ImageView发生位置的变动,只是其内容(显示的图片)发生了相应的位移;而setTranslationX则实现了ImageView在整个视图中位置的变化。
可以看到,当scrollTo滑动的值>0时,其整个滑动内容是往左移动的,反之则会往右移动;这点和setTranslationX刚好是相反;需要注意一下,当然这并不是bug,从scrollTo的源码可以发现,这是必然现象。因为scrollTo移动并不是View本身,而是他的内容。
好了,了解上面的这些内容,让我们赶快来实现一些好玩的动画效果吧。
属性动画实践之ViewPager切换动画。
我们知道,通过设置ViewPager的setPageTransformer方法,就可以实现ViewPager切换时的动画效果,关键是如何实现PageTransformer接口中的方法。下面就简单示例几个效果。
** 3D 方块效果**
private class MyCubeTransformer implements ViewPager.PageTransformer {
private float baseRotate = 90.0f;
@Override
public void transformPage(View view, float position) {
if (position < -1) {
ViewCompat.setPivotX(view, view.getMeasuredWidth());
ViewCompat.setPivotY(view, view.getMeasuredHeight() * 0.5f);
ViewCompat.setRotationY(view, 0);
} else if (position <= 0) {
ViewCompat.setPivotX(view, view.getMeasuredWidth());
ViewCompat.setPivotY(view, view.getMeasuredHeight() * 0.5f);
ViewCompat.setRotationY(view, baseRotate * position);
} else if (position <= 1) {
ViewCompat.setPivotX(view, 0);
ViewCompat.setPivotY(view, view.getMeasuredHeight() * 0.5f);
ViewCompat.setRotationY(view, baseRotate * position);
} else {
ViewCompat.setPivotX(view, view.getMeasuredWidth());
ViewCompat.setPivotY(view, view.getMeasuredHeight() * 0.5f);
ViewCompat.setRotationY(view, 0);
}
}
}
mViewPager.setPageTransformer(false, new MyCubeTransformer());
这里主要是使用setPivot和setRotation方法通过ViewPager位置变动,不断改变view的属性值实现。关于pivot的意义,在前文Android动画总结中已经说得很清楚了。
** 风车翻页效果**
private class MyFlipTransformer implements ViewPager.PageTransformer {
private static final float BASE_ROTATION = 180.0f;
@Override
public void transformPage(View page, float position) {
if (position < -1) {
} else if (position <= 0) {
ViewCompat.setTranslationX(page, -page.getWidth() * position);
float rotation = (BASE_ROTATION * position);
ViewCompat.setRotationY(page, rotation);
if (position > -0.5) {
page.setVisibility(View.VISIBLE);
} else {
page.setVisibility(View.INVISIBLE);
}
} else if (position <= 1) {
ViewCompat.setTranslationX(page, -page.getWidth() * position);
float rotation = (BASE_ROTATION * position);
ViewCompat.setRotationY(page, rotation);
if (position < 0.5) {
page.setVisibility(View.VISIBLE);
} else {
page.setVisibility(View.INVISIBLE);
}
}
}
}
mViewPager.setPageTransformer(false, new MyFlipTransformer());
这里同样是不断修改view的RotateY 属性值,实现类似风车的翻转效果。
** 中心放大效果**
private class MyZoomCenterTransformer implements ViewPager.PageTransformer{
@Override
public void transformPage(View view, float position) {
if (position < -1) {
} else if (position <= 0) {
ViewCompat.setTranslationX(view, -view.getWidth() * position);
ViewCompat.setPivotX(view, view.getWidth() * 0.5f);
ViewCompat.setPivotY(view, view.getHeight() * 0.5f);
ViewCompat.setScaleX(view, 1 + position);
ViewCompat.setScaleY(view, 1 + position);
if (position < -0.95f) {
ViewCompat.setAlpha(view, 0);
} else {
ViewCompat.setAlpha(view, 1);
}
} else if (position <= 1) {
ViewCompat.setTranslationX(view, -view.getWidth() * position);
ViewCompat.setPivotX(view, view.getWidth() * 0.5f);
ViewCompat.setPivotY(view, view.getHeight() * 0.5f);
ViewCompat.setScaleX(view, 1 - position);
ViewCompat.setScaleY(view, 1 - position);
if (position > 0.95f) {
ViewCompat.setAlpha(view, 0);
} else {
ViewCompat.setAlpha(view, 1);
}
}
}
}
mViewPager.setPageTransformer(false, new MyZoomCenterTransformer());
这里也很好理解,结合ViewPager中view的position值,不断修改view的scale属性值即可。
可以发挥自己的想象,试试还可以实现怎样的效果
好了,属性动画拓展就先到这里,下篇继续。
文中所有源码地址AndroidAnimationExercise。有兴趣的同学欢迎star & fork。