Android 属性动画拓展(一)

Android 属性动画拓展(一)

前言

通过之前Android动画总结一文,我们对Android开发中的动画实现做了两种分类传统动画(包括帧动画和补间动画)属性动画。但是,从动画实现机理及动画所呈现的效果来说,之前的动画实现其实可以归结为一类动画,这类动画,无论是帧动画还是补间动画,亦或是属性动画,都是严格由代码控制,动画的过程(从开始到结束,或者是循环播放的动画),都不受外部交互动作的控制或干预;然而,日常开发中还有一类动画是伴随着交互效果动态变化的,比如说ViewPager的切换,可以随着我们滑动呈现不同的效果;QQ空间下拉时,顶部背景的放大效果,动画效果和用户的操作相互联动

下面,就结合属性动画的说一说如何实现这类动画。

属性动画的意义

我们知道,从Android3.0 (API LEVEL 11)开始,Android SDK 新增了属性动画();这四个字虽然简单,但却意义深远;我们知道对象的属性决定着对象会呈现出怎样的状态;想一个我们平时使用的ImageView,Button都有哪些属性呢?当他们的属性值发生变化时这些View又会怎么样呢?我们看一下下面的这些效果图:

效果图

move.gif
scale2.gif

实现代码

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在整个视图中位置的变化。

-move.gif

可以看到,当scrollTo滑动的值>0时,其整个滑动内容是往左移动的,反之则会往右移动;这点和setTranslationX刚好是相反;需要注意一下,当然这并不是bug,从scrollTo的源码可以发现,这是必然现象。因为scrollTo移动并不是View本身,而是他的内容。

好了,了解上面的这些内容,让我们赶快来实现一些好玩的动画效果吧。

属性动画实践之ViewPager切换动画。

我们知道,通过设置ViewPager的setPageTransformer方法,就可以实现ViewPager切换时的动画效果,关键是如何实现PageTransformer接口中的方法。下面就简单示例几个效果。

cube.gif

** 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动画总结中已经说得很清楚了。

clip.gif

** 风车翻页效果**

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 属性值,实现类似风车的翻转效果。

scale1.gif

** 中心放大效果**

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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,472评论 25 707
  • 什么是View View 是 Android 中所有控件的基类。 View的位置参数 View 的位置由它的四个顶...
    acc8226阅读 1,074评论 0 7
  • 考试,这个词对于我们来说再熟悉不过了。时间过得可真快,一转眼就到了期中考试的日子。此时 四年级的孩子们正在底下认真...
    听风的故事阅读 715评论 0 0
  • 从小学习的物理学告诉我,事物是不断永恒发展与变化的,地球从宇宙大爆炸中渐渐孕育而成,经历了冰川时代、恐龙时代等许多...
    尝试写点想法阅读 634评论 0 0
  • 我写过薛宝钗,就有很多朋友留言说,“我喜欢宝钗,不喜欢黛玉。” 为什么很多人不喜欢黛玉呢? 我想这首先是因为,曹公...
    陶薰读书阅读 1,047评论 14 27