Material Design之定制动画--触摸反馈,循环揭露,转场动画,共享元素和曲线运动


先贴下官网的API

触摸反馈:

在按钮属性中添加
android:background="?android:attr/selectableItemBackground"
则点击按钮的时候会有水波纹(有边界),波纹不超过设置的控件大小,如果控件为矩形则波纹不会超过矩形大小
如果添加:
android:background="?android:attr/selectableItemBackgroundBorderless"
则点击效果为无界水波纹,范围是一个圆形
循环揭露:

同样我们可以用 ViewAnimationUtils.createCircularReveal()方式来实现这种水波纹渐变显示效果,
这种渐变式扩散的动画很有用,我们会以一种Activity转场动画的形式将其添加,一会演示给大家。
private void doCircle(View myView)
{
    // get the center for the clipping circle
    int cx = (myView.getLeft() + myView.getRight()) / 2;
    int cy = (myView.getTop() + myView.getBottom()) / 2;

    int finalRadius = Math.max(myView.getWidth(), myView.getHeight());

    Animator anim =
            ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
    anim.setDuration(3000);
    myView.setVisibility(View.VISIBLE);
    anim.start();
}
同样点击控件水波纹收缩隐藏的效果就不说了,跟以上方法的实现一样,只不过是将半径逐渐缩小

private void hideCircle(final View myView)
{
    int cx = (myView.getLeft() + myView.getRight()) / 2;
    int cy = (myView.getTop() + myView.getBottom()) / 2;

    int finalRadius = myView.getWidth();

    Animator anim =
            ViewAnimationUtils.createCircularReveal(myView, cx, cy, finalRadius, 0);
    anim.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation)
        {
            super.onAnimationEnd(animation);
            myView.setVisibility(View.INVISIBLE);
        }
    });
    anim.setDuration(3000);
    anim.start();
}
需要注意的是,当按钮显示的时候会响应用户点击事件,当随渐变动画消失之后,再次点击会失去响应,此外如果我们想要改变波纹的颜色
可以在xml中修改 android:colorControlHighlight 的属性

转场动画:

使用转场动画需要在主题中使用 android:windowContentTransitions  属性启用窗口内容转换
或者在代码中设置,将其置于setContentView()方法之前。
getWindow (). requestFeature ( Window . FEATURE_CONTENT_TRANSITIONS );
比如我们现在要做一个效果是从FirstActivity跳转到SecondActivity,如果要实现转场动画需要怎么处理呢

首先在FirstActivity中Button的点击事件中设置跳转
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(FirstActivity.this).toBundle());
然后在SecondActivity中的OnCreate()方法中设置进入此Activity的动画和返回上一个Activity的动画样式,如淡入淡出效果
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    //TODO 淡入淡出
    getWindow().setEnterTransition(new Fade().setDuration(2000));
    getWindow().setExitTransition(new Fade().setDuration(2000));
    setContentView(R.layout.activity_second);
    linearLayout = (LinearLayout) this.findViewById(R.id.secondLayout);
}

Ok,就是这么简单,一个淡入淡出的转场动画就完成了

其次google还提供了其他两种动画方式
explode:从场景的中心移入或移出
slide:从场景的边缘移入或移出

如果想要加快进入转换的动作,需要调用
getWindow().setAllowEnterTransitionOverlap(true);
他会让动画变得更加生动。

我们关键看ActivityOptions.makeSceneTransitionAnimation(FirstActivity.this)这个方法,它具体是干什么的呢?

它是在创建onCreate()方法的时候ActivityOptions活动之间过渡使用的场景动画,主要有这几类:

我们上面提到过这种循环揭露的动画方式,同样可以用来做转场动画使用,楼主亲测,效果不错

MakeClipRevealAnimation:(圆形循环揭露)
staticActivityOptions makeClipRevealAnimation(View source, int startX, int startY, int width, int height)
Create an ActivityOptions specifying an animation where the new activity is revealed from a small originating area of the screen to its final full representation.
他是从一个点以圆形渐变到满屏,参数类型,依次为:操作view,SecondActivity开始渐变点横坐标, SecondActivity 开始渐变点纵坐标, SecondActivity 要扩展的初始圆半径, SecondActivity 要扩展的最终圆半径。
我们来设置下看看效果

tiaozhuan.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v)
    {
        Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
        startActivity(intent, ActivityOptions.makeClipRevealAnimation(v,0,0,v.getRight(),v.getBottom()).toBundle());

    }
});
给个效果:



这个实例不难,最主要的是要设置第二个Activity的主题背景为透明
MakeCustomAnimation:(自定义转场动画)
staticActivityOptions makeCustomAnimation(Context context, int enterResId, int exitResId)
Create an ActivityOptions specifying a custom animation to run when the activity is displayed.
这个方法是需要传入传统的自定义动画,以xml动画的形式来实现跳转效果,enterResId指的是要跳转的Activity以怎么样的动画形式出现,exitResId这个xml动画指的是,当前Activity以哪种动画方式消失
tiaozhuan.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v)
    {
        Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
        startActivity(intent, ActivityOptions.makeCustomAnimation(FirstActivity.this,R.anim.bottom_to_center,R.anim.center_to_left).toBundle());

    }
});

xml动画是我事先定义好的,不出意外当前Activity会由中心到屏幕左边缘消失,要跳转的Activity则会从屏幕底部弹出

来看下效果:

MakeScaleUpAnimation:(渐变缩放转场动画)
staticActivityOptions makeScaleUpAnimation(View source, int startX, int startY, int width, int height)

Create an ActivityOptions specifying an animation where the new activity is scaled from a small originating area of the screen to its final full representation.


渐变缩放的转场动画与上面提到的圆形循环揭露效果 makeClipRevealAnimation在参数上没有变化,效果上也很相似
tiaozhuan.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v)
    {
        Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
        startActivity(intent, ActivityOptions.makeScaleUpAnimation(v,v.getLeft(),v.getTop(),v.getRight(),v.getBottom()).toBundle());
    }
});
看下效果:

MakeThumbnailScaleUpAnimation:(缩略图形缩放转场动画)
staticActivityOptions makeThumbnailScaleUpAnimation(View source, Bitmap thumbnail, int startX, int startY)
Create an ActivityOptions specifying an animation where a thumbnail is scaled from a given position to the new activity window that is being started.
指定缩略图从给定位置缩放到正在启动的新活动窗口的动画,这种使用方法很简单也没啥子特别的就不扯了,下面是使用方法。

tiaozhuan.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v)
    {
        v.setDrawingCacheEnabled(true);
        bitmap=v.getDrawingCache();
        Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
        startActivity(intent, ActivityOptions.makeThumbnailScaleUpAnimation(v,bitmap,10,10).toBundle());
    }
});
共享元素动画:

在解析共享元素之前,我们来承接一下上文的内容,做个归类:

Activity Transition提供了两种Transition类型:
Enter(进入):进入一个Activity的效果
Exit(退出):退出一个Activity的效果

而这每种类型又分为普通Transition和共享元素Transition:

普通Transition:(上面提到过)

explode:从场景的中心移入或移出
slide:从场景的边缘移入或移出
fade:淡入淡出的效果

除了上面三种系统提供的转场动画之外,google还为我们提供了可简单自定义的多种动画形式,如:

圆形循环揭露
自定义转场动画(跟我们传统的设置方式overridePendingTransition(int enterAnim, int exitAnim))近似
渐变缩放转场动画
缩略图形缩放动画

Shared Elements Transition 共享元素转换:

它的作用就是共享两个acitivity种共同的元素,支持如下效果,我们会在后面解析
changeBounds -  改变目标视图的布局边界
changeClipBounds - 裁剪目标视图边界
changeTransform - 改变目标视图的缩放比例和旋转角度
changeImageTransform - 改变目标图片的大小和缩放比例
changeScroll 滚动变化


如果要在代码中指定转换,以  Transition 对象调用这些方法:

staticActivityOptions makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName)

Create an ActivityOptions to transition between Activities using cross-Activity scene animations.

staticActivityOptions makeSceneTransitionAnimation(Activity activity, Pair...<ViewString> sharedElements)

Create an ActivityOptions to transition between Activities using cross-Activity scene animations.


这两个方法是实现共享元素的关键,第一个是设置单个共享元素,第二个方法是设置多个共享元素

共享元素的元件是窗口绘制在整个视图层次顶部的ViewOverlay上的,他相当于一个图层,在View中所有其他的内容绘制完成之后绘制,以下是它所提供的公有方法

Public methods

void add(Drawable drawable)

Adds a Drawable to the overlay.

void clear()

Removes all content from the overlay.

void remove(Drawable drawable)

Removes the specified Drawable from the overlay.

从上面可以看出,他同时也支持添加和移除drawable

单个共享元素:

首先我们来看单个共享元素的实现:
既然元素共享,说明我们当前Activity中的 view和要跳转的Activity中的 view中有共享的标签,所以我们需要在FirstActivity中的ImageView的xml属性中设置
android:transitionName="button"
同样需要在SecondActivity中的ImageView的xml属性中设置android:transitionName="button",这样就实现了元素共享
接下来在FirstActivity中启用就ok了
tiaozhuan.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v)
    {
        Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
        startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(FirstActivity.this, v, "button").toBundle());
    }
});

来看下效果:

仔细观察左上角,两个界面的切换就在一瞬间,是不是很流畅,就是这么简单,如果想要在第二个元件执行效果结束后反转,可以调用 Activity.finishAfterTransition()  这个方法。

多个共享元素:

多个元素共享顾名思义就是多个控件联动:
        img.setOnClickListener(new View.OnClickListener() {
            @TargetApi(Build.VERSION_CODES.M)
            @Override
            public void onClick(View v)
            {
                Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
                Pair<View, String> pairOne = new Pair<View, String>(img, "img");
                Pair<View, String> pairTwo = new Pair<View, String>(button1, "button1");
                Pair<View, String> pairThreee = new Pair<View, String>(button2, "button2");
                Pair<View, String> pairFour = new Pair<View, String>(button3, "button3");
                Pair<View, String> pairFive = new Pair<View, String>(button4, "button4");
                ActivityOptions activityOptions = ActivityOptions.makeSceneTransitionAnimation(FirstActivity.this, pairOne, pairTwo, pairThreee, pairFour, pairFive);
                startActivity(intent, activityOptions.toBundle());
            }

多个共享元素的设置和单个共享元素的设置一样,有必要可以在SecondActivity中也进行元素绑定,不过不绑定也可以实现效果,在这里我采用了弹性动画
  getWindow().setEnterTransition(new Explode().setDuration(2000).setInterpolator(new BounceInterpolator()));
  getWindow().setExitTransition(new Explode().setDuration(2000));
ViewCompat.setTransitionName(img,"img");
ViewCompat.setTransitionName(button1,"button1");
……
来看下效果:

我们也可以通过定义transitionSet的方式来融合更多的共享元素动画,来使效果更加绚丽,个人也喜欢这种比较灵活的方式:
//TODO 共享元素实现方式
private void ShareElements_Two(Intent intent)
{
    TransitionSet transitionSet=new TransitionSet();
    transitionSet.addTransition(new ChangeImageTransform());
 
    getWindow().setSharedElementEnterTransition(transitionSet);
    getWindow().setSharedElementExitTransition(transitionSet);

    Pair<View,String> pairOne=new Pair<View, String>(meinv,"meinv");
    Pair<View,String> pairTwo=new Pair<View, String>(tiaozhuan,"button");
    ActivityOptions activityOptions=ActivityOptions.makeSceneTransitionAnimation(FirstActivity.this,pairOne,pairTwo);
    startActivity(intent,activityOptions.toBundle());
}
上文我们提到了为共享元素添加剪裁方式的动画:
ChangeBounds This transition captures the layout bounds of target views before and after the scene change and animates those changes during the transition. 
ChangeClipBounds ChangeClipBounds captures the getClipBounds() before and after the scene change and animates those changes during the transition. 
ChangeImageTransform This Transition captures an ImageView's matrix before and after the scene change and animates it during the transition. 
ChangeScroll This transition captures the scroll properties of targets before and after the scene change and animates any changes. 
ChangeTransform This Transition captures scale and rotation for Views before and after the scene change and animates those changes during the transition. 
CircularPropagation A propagation that varies with the distance to the epicenter of the Transition or center of the scene if no epicenter exists. 
首先我们来看下ChangeClipBounds,刚开始接触还摸不着头脑,不知道怎么用啊,后来看了源码发现其实很简单:
最主要是下面三个方法
void captureEndValues(TransitionValues transitionValues)

Captures the values in the end scene for the properties that this transition monitors.

void captureStartValues(TransitionValues transitionValues)

Captures the values in the start scene for the properties that this transition monitors.

Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues)
This method creates an animation that will be run for this transition given the information in the startValues and endValues structures captured earlier for the start and end scenes.
我们使用剪裁的时候,需要注意这种剪裁动画是伴随着共享元素的扩展而发生的,所以必须结合共享元素的使用
通过设置TransitionValues来定义我们的初始场景值,包括view,和剪裁区域Rect,他是通过map集合去设置值的,另外captureEndValues()设置动画值之前必须调用setClipBounds()方法,否则会报空指针
看下源码就一目了然了
//TODO 这两个方法都调用了captureValues()方法,不同的是一个传的是初始值,一个是结束值
public void captureStartValues(TransitionValues transitionValues) {
    captureValues(transitionValues);
}

@Override
public void captureEndValues(TransitionValues transitionValues) {
    captureValues(transitionValues);
}
//具体这个方法是做什么的呢?因为transitionValues封装了我们要操作的view,所以我们要将他剪裁的区域也封装起来,看标红部分,所以在
这个方法之前我们必须剪裁视图的可视区域,即setClipBounds(),否则将不起作用
private void captureValues(TransitionValues values) {
    View view = values.view;
    if (view.getVisibility() == View.GONE) {
        return;
    }

    Rect clip = view.getClipBounds();
    values.values.put(PROPNAME_CLIP, clip);
    if (clip == null) {
        Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
        values.values.put(PROPNAME_BOUNDS, bounds);
    }
}

再来看我们剪裁动画执行的方法:
public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,
        TransitionValues endValues) {
    //可以看出在这之前一定要先设置captureStartValues和captureEndValues方法,否则就会返回null
    if (startValues == null || endValues == null
            || !startValues.values.containsKey(PROPNAME_CLIP)
            || !endValues.values.containsKey(PROPNAME_CLIP)) {
        return null;
    }
   //获取开始和剪裁区域
    Rect start = (Rect) startValues.values.get(PROPNAME_CLIP);
    Rect end = (Rect) endValues.values.get(PROPNAME_CLIP);
    if (start == null && end == null) {
        return null; // No animation required since there is no clip.
    }

    if (start == null) {
        start = (Rect) startValues.values.get(PROPNAME_BOUNDS);
    } else if (end == null) {
        end = (Rect) endValues.values.get(PROPNAME_BOUNDS);
    }
    if (start.equals(end)) {
        return null;
    }
    //这句话很关键,因为我们这个方法最主要操作的对象是endValues中封装的view,也就是下面我们属性动画真正要作用的对象
    endValues.view.setClipBounds(start);
    //我们属性动画是按照矩形拓展的方式演变
    RectEvaluator evaluator = new RectEvaluator(new Rect());
    return ObjectAnimator.ofObject(endValues.view, "clipBounds", evaluator, start, end);
}
熟悉了源码的流程我们来使用下看看效果:
    private void ShareElements_Two(Intent intent)
    {
        ChangeClipBounds changeClipBounds=new ChangeClipBounds();
        TransitionValues values_start = new TransitionValues();
        TransitionValues values_end = new TransitionValues();

        values_start.view=tiaozhuan;
        values_end.view=meinv;
        //TODO setClipBounds(Rect rect),直接指定当前view的可视区域,当前的Rect使用的view的自身的坐标系。
        //TODO startView原始大小200*200,endView原始大小600*600
        values_start.view.setClipBounds(new Rect(0, 0, 0, 0));
        //TODO 通过分析源码我们知道这个方法虽然可省,但是之建立在要扩展的视图显示区域是本身大小的基础上
        values_end.view.setClipBounds(new Rect(0, 0, 600, 600));
        changeClipBounds.captureStartValues(values_start);
        changeClipBounds.captureEndValues(values_end);
        changeClipBounds.createAnimator(layout, values_start, values_end).setDuration(3000).start();
        TransitionSet set=new TransitionSet();
        set.addTransition(changeClipBounds);

        set.addTransition(new Slide());
        set.addTransition(new Fade());
        set.setDuration(3000);
        //TODO 顺序播放时1,同时播放是0
        set.setOrdering(0);
//        //TODO 普通转场动画进入效果
//        getWindow().setEnterTransition(set);
//        //TODO 普通转场动画退出效果
//        getWindow().setExitTransition(set);
        //TODO 共享元素进入效果
//        getWindow().setSharedElementEnterTransition(set);
        //TODO 共享元素退出效果
//        getWindow().setSharedElementExitTransition(set);
        //TODO 我们的动画是建立在硬件加速的基础上的,如果关闭你会看到惨不忍睹的画面
        Pair<View,String> pairOne=new Pair<View, String>(meinv,"meinv");
        Pair<View,String> pairTwo=new Pair<View, String>(tiaozhuan,"button");
        ActivityOptions activityOptions=ActivityOptions.makeSceneTransitionAnimation(FirstActivity.this,pairOne,pairTwo);
        startActivity(intent,activityOptions.toBundle());
    }

直接看下效果:代码都有注释不做过多讲解



曲线运动:

曲线运动是动画利用曲线实现时间内插与空间移动模式, PathInterpolator是一个基于贝塞尔曲线或path对象的全新插入器,这个插入器会在1*1的正方形内指定一个
运动曲线,定位点位于(0,0)以及(1,1),而控制点由构造函数参数指定。
属性动画类别给出了新的构造函数,可以使用path对象为视图的X,Y属性添加动画,系统提供了三种基本曲线:

  • @interpolator/fast_out_linear_in.xml
  • @interpolator/fast_out_slow_in.xml
  • @interpolator/linear_out_slow_in.xml
除此之外我们可以自己定义:根据你需要的曲线去绘制,其实跟我们使用的贝塞尔曲线去不断刷新视图的运动轨迹是一样的。

//TODO 使用曲线运动
private void doCurdAnimation(View view)
{
    ObjectAnimator objectAnimator;
    PathInterpolator pathInterpolator=new PathInterpolator(0.5f,0.9f);
    Path path=new Path();
    path.arcTo(1f,1f,400f,800f,0,120,false);
    objectAnimator=ObjectAnimator.ofFloat(view,View.X,View.Y,path);
    objectAnimator.setInterpolator(pathInterpolator);
    objectAnimator.setDuration(3000);
    objectAnimator.start();
}


视图状态动画:

  StateListAnimator  这个类能够在视图状态改变的同时执行动画,举个例子,比如我们点击button,按下的时候让button拉长,松开的时候恢复原位
通常我们的做法是自定义ValueAnimator动画不断改变刷新button的长度,有了StateListAnimator就很简单了,它可以通过定义xml文件的形式,给button设置属性,文件可以放在drawable下,也可以放到anim文件夹下。
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <set>
            <objectAnimator android:duration="500" android:propertyName="scaleX" android:valueTo="1.5" android:valueType="floatType" />
        </set>
    </item>

    <item android:state_pressed="false">
        <set>
            <objectAnimator android:duration="500" android:propertyName="scaleX" android:valueTo="1" android:valueType="floatType" />
        </set>
    </item>
</selector>

然后在xml中给button引用
<Button
    android:id="@+id/state_animte"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="跳转"
    android:stateListAnimator="@drawable/click_selector"
    />

或者在Activity中用代码定义添加:
StateListAnimator stateListAnimator= AnimatorInflater.loadStateListAnimator(this,R.drawable.click_selector);
state_animte.setStateListAnimator(stateListAnimator);
这样一个简单的视图状态动画就完成了,看下效果


















真正需要自定义的一些效果来满足需求的,都需要花时间去研究,欢迎大家到交流群里面分享一些惊天地泣鬼神的代码,或者编程思想 ,贴下,Android 代码艺术:128672634, 如果你不仅仅是把安卓当作一份工作,而是饱含坚持和热爱!


  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值