Android 知识要点整理(11)----Scenes and Transitions(场景和变换)

除了常规的动画(帧动画、补间动画、属性动画)等作用于单个控件的动画,Android 还提供了一种类似的动画的功能,用于在两个不同的布局切换时提供过渡动画效果—-Transitions Framework。

Transitions Framework 简介

该框架帮助在布局改变的时候增加动画效果,它会在布局发生改变的时候应用一个或多个动画效果于布局中的每个控件。框架具有以下特点:

  • 分组动画

    • 一个或多个动画会作用于每个控件
  • 基于变化的动画
    • 动画是基于初始状态和最终状态的控件的属性值执行的
  • 内置动画
    • 框架提供了一些预定义的动画,如淡入淡出动画
  • 支持资源文件
    • 可以从资源文件加载布局和动画
  • 生命周期回调
    • 定义了回调,可以更好地控制变换过程

框架由ScenesTransitionsTransitionManager构成。关系图如下:

Scene(场景)

Scene保存了布局的状态,包括所有的控件和控件的属性。布局可以是一个简单的视图控件或者复杂的视图树和子布局。保存了这个布局状态到Scene后,我们就可以从另一个场景变化到该场景。Android 提供了一个类Scene来代表场景。

Transitions(变换)

从一个场景到另一个场景的变换中会有动画效果,这些动画信息就保存在Transition对象中。要运行动画,我们要使用TransitionManager实例来应用Transition

Transitions 生命周期

Transitions的生命周期和Activity的生命周期类似,它代表了变化过程的状态。在一些重要的状态,框架会触发回调方法,以便我们实现一些操作逻辑。

限制

该框架存在一定的限制。如下:

  • 不能作用于SurfaceView,因为SurfaceView的绘制在非主线程中执行,会导致同步问题。
  • 一些特殊的变化不能应用到TextureView
  • 不能作用于继承自AdapterView的控件,比如ListView。因为它们是用一种不兼容的方式来管理他们的item的。

实例

上面简单了介绍了整个框架的构成,下面就实战一下

基本变换

我们先定义3个布局:

R.layout.scene1

<RelativeLayout
    android:id="@+id/container"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:id="@+id/transition_square"
        android:layout_width="@dimen/square_size_normal"
        android:layout_height="@dimen/square_size_normal"
        android:background="#990000"
        android:gravity="center"/>

    <ImageView
        android:id="@+id/transition_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/transition_square"
        android:src="@drawable/ic_launcher"/>

    <ImageView
        android:id="@+id/transition_oval"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:layout_below="@id/transition_image"
        android:src="@drawable/oval"/>

</RelativeLayout>

R.layout.scene2

<RelativeLayout
    android:id="@+id/container"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:id="@+id/transition_square"
        android:layout_width="@dimen/square_size_normal"
        android:layout_height="@dimen/square_size_normal"
        android:layout_alignParentBottom="true"
        android:background="#990000"
        android:gravity="center"/>

    <ImageView
        android:id="@+id/transition_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:src="@drawable/ic_launcher"/>

    <ImageView
        android:id="@+id/transition_oval"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:layout_centerHorizontal="true"
        android:src="@drawable/oval"/>

</RelativeLayout>

R.layout.scene3

<RelativeLayout
    android:id="@+id/container"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:id="@+id/transition_square"
        android:layout_width="@dimen/square_size_normal"
        android:layout_height="@dimen/square_size_normal"
        android:layout_centerHorizontal="true"
        android:background="#990000"
        android:gravity="center"/>

    <ImageView
        android:id="@+id/transition_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:src="@drawable/ic_launcher"/>

    <ImageView
        android:id="@+id/transition_oval"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:src="@drawable/oval"/>

    <TextView
        android:id="@+id/transition_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="@string/additional_message"
        android:textAppearance="?android:attr/textAppearanceLarge"/>

</RelativeLayout>

仔细观察可知,这三个布局只是图标摆放的位置不同,还有就是布局3添加了一个TextView控件。

接下来我们为每个布局创建场景:

mSceneRoot = (ViewGroup) view.findViewById(R.id.scene_root);

//用构造方法创建场景
mScene1 = new Scene(mSceneRoot, (ViewGroup) mSceneRoot.findViewById(R.id.container));

//从布局资源文件创建场景
mScene2 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene2, getActivity());
mScene3 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene3, getActivity());

//针对布局3,我们要自定义一个Transition,使得布局3中的TextView能够单独地淡入淡出
mTransitionManagerForScene3 = TransitionInflater.from(getActivity())
                .inflateTransitionManager(R.transition.scene3_transition_manager, mSceneRoot);

看看自定义Transition的资源文件:

R.transition.scene3_transition_manager

<transitionManager xmlns:android="http://schemas.android.com/apk/res/android">
    <transition
        android:toScene="@layout/scene3"
        android:transition="@transition/changebounds_fadein_together"/>
</transitionManager>

R.transition.changebounds_fadein_together

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <changeBounds/>
    <fade android:fadingMode="fade_in">
        <targets>
            <target android:targetId="@id/transition_title" />
        </targets>
    </fade>
</transitionSet>

<transitionManager>标签中可以针对不同的场景定义一系列<transition>,transition可以是各种transition的集合。

最后就是执行场景变换了,具体看代码:

@Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        switch (checkedId) {
            case R.id.select_scene_1: {
                //切换到场景1,使用默认的transition
                TransitionManager.go(mScene1);
                break;
            }
            case R.id.select_scene_2: {
                //切换到场景2
                TransitionManager.go(mScene2);
                break;
            }
            case R.id.select_scene_3: {
                //切换到场景3,使用自定义transition
                mTransitionManagerForScene3.transitionTo(mScene3);
                break;
            }
            case R.id.select_scene_4: {
                //场景4,可以不定义具体的Scene,
                //在delayed transition后直接修改控件的属性
                //这样也可以形成动画效果
                TransitionManager.beginDelayedTransition(mSceneRoot);

                View square = mSceneRoot.findViewById(R.id.transition_square);
                ViewGroup.LayoutParams params = square.getLayoutParams();
                int newSize = getResources().getDimensionPixelSize(R.dimen.square_size_expanded);
                params.width = newSize;
                params.height = newSize;
                square.setLayoutParams(params);
                break;
            }
        }
    }

以上就是基本的场景变换的流程。结合效果图,我们发现这种方式可以非常简单地实现布局切换的动画效果,如果用Animation来实现该效果可能会非常复杂。

自定义变换

系统默认提供的Transition有FadeChangeBoundsSlide等等。除了这些,我们还可以自己实现一些Transition,达到自己想要的效果。下面我们就来实现ChangeColor的变换,先看效果图:

这里也定义了3种场景布局:

R.layout.scene1

<LinearLayout
    android:id="@+id/container"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context="com.example.android.customtransition.CustomTransitionFragment">

    <View
        android:id="@+id/view_1"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_margin="8dp"
        android:background="#f00"/>

    <View
        android:id="@+id/view_2"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_margin="8dp"
        android:background="#0f0"/>

    <View
        android:id="@+id/view_3"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_margin="8dp"
        android:background="#00f"/>

</LinearLayout>

其他两个布局只是颜色不同而已,就不贴代码了。

重点看看自定义Transition的代码:

ChangeColor.java

public class ChangeColor extends Transition {

    /** 用于存储颜色值的KEY*/
    private static final String PROPNAME_BACKGROUND = "customtransition:change_color:background";

    // BEGIN_INCLUDE (capture_values)
    /**
     * 获取场景的控件的背景色
     */
    private void captureValues(TransitionValues values) {
        // 获取控件的属性,备用
        values.values.put(PROPNAME_BACKGROUND, values.view.getBackground());
    }

    @Override
    public void captureStartValues(TransitionValues transitionValues) {
        //后去开始的背景色
        captureValues(transitionValues);
    }

    //获取结束的场景的对应的背景色
    @Override
    public void captureEndValues(TransitionValues transitionValues) {
        captureValues(transitionValues);
    }
    //创建动画器用于应用变换
    @Override
    public Animator createAnimator(ViewGroup sceneRoot,
                                   TransitionValues startValues, TransitionValues endValues) {
        if (null == startValues || null == endValues) {
            return null;
        }

        final View view = endValues.view;

        Drawable startBackground = (Drawable) startValues.values.get(PROPNAME_BACKGROUND);
        Drawable endBackground = (Drawable) endValues.values.get(PROPNAME_BACKGROUND);

        if (startBackground instanceof ColorDrawable && endBackground instanceof ColorDrawable) {
            ColorDrawable startColor = (ColorDrawable) startBackground;
            ColorDrawable endColor = (ColorDrawable) endBackground;

            if (startColor.getColor() != endColor.getColor()) {

                ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(),
                        startColor.getColor(), endColor.getColor());
                animator.setDuration(3000);
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        Object value = animation.getAnimatedValue();

                        if (null != value) {
                            view.setBackgroundColor((Integer) value);
                        }
                    }
                });

                return animator;
            }
        }

        return null;
    }

}

首先我们要创建一个继承Transition的抽象类,然后要实现3个方法:

public void captureStartValues(TransitionValues transitionValues):获取初始场景的属性,在这个方法中我们可以取得我们关注的控件的属性值,用于以后的变化。

public void captureEndValues(TransitionValues transitionValues):获取结束场景的属性,这个是我们最终要显示的值。

public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues):创建动画器,这个方法的最关键的,在这个方法里面我们可以定义任何我们想要的动画生成器,然后返回给TransitionFramework使用,从而达到我们想要的动画效果。

最后就是将该Transition应用到变换中,直接调用如下代码即可:

mTransition = new ChangeColor();
TransitionManager.go(mScenes[mCurrentScene], mTransition);

Activity转场变换

将Transition应用的Activity切换中可以实现酷炫的效果,但是这个要在api>=21的系统上才可以看到效果。同样,我们先看看效果:

从效果图上看,从列表到详情的切换非常自然优雅,好像是在同一Activity当中,但其实这是两个不同的Activity。看一下具体的实现过程:

两个Activity的布局,一个是GridView列表,一个是ImageView加TextView,没啥特别的。我们主要关注Activity的切换过程。

列表Activity的操作

在点击列表中的一项时触发`onItemClick()方法,该方法的实现代码如下:

 @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
        Item item = (Item) adapterView.getItemAtPosition(position);

        // 正常创建Intent
        Intent intent = new Intent(this, DetailActivity.class);
        intent.putExtra(DetailActivity.EXTRA_PARAM_ID, item.getId());

       //关键代码,创建场景变化动画,ActivityOptionsCompat可以兼容api > 4的系统
       //但是在api<21的系统中和正常发起Activity一样,没有特殊的效果
        ActivityOptionsCompat activityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(
                this,

                //创建要变换的初始控件和目标名称的映射
                //该映射会在被调起的Activity中用到
                new Pair<View, String>(view.findViewById(R.id.imageview_item),
                        DetailActivity.VIEW_NAME_HEADER_IMAGE),
                new Pair<View, String>(view.findViewById(R.id.textview_name),
                        DetailActivity.VIEW_NAME_HEADER_TITLE));
        ActivityCompat.startActivity(this, intent, activityOptions.toBundle());
    }          

详情Activity的操作

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.details);
        mItem = Item.getItem(getIntent().getIntExtra(EXTRA_PARAM_ID, 0));

        mHeaderImageView = (ImageView) findViewById(R.id.imageview_header);
        mHeaderTitle = (TextView) findViewById(R.id.textview_title);

        //设置要变换到的控件和名称的映射
        //通过名称,我们可以和列表Activity的要变换的空间对应起来
        //从而对其使用动画效果
        ViewCompat.setTransitionName(mHeaderImageView, VIEW_NAME_HEADER_IMAGE);
        ViewCompat.setTransitionName(mHeaderTitle, VIEW_NAME_HEADER_TITLE);
        loadItem();
    }

这样,我们就完成了Activity的切换的场景变换。除此之外,我们还可以监听这种变化的开始和结束,从而执行一些额外的操作。

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private boolean addTransitionListener() {
        //获取变换对象
        final Transition transition = getWindow().getSharedElementEnterTransition();

        if (transition != null) {
            // 添加监听器
            transition.addListener(new Transition.TransitionListener() {
                @Override
                public void onTransitionEnd(Transition transition) {
                    // 下载高清图片
                    loadFullSizeImage();

                    // 取消监听
                    transition.removeListener(this);
                }

                @Override
                public void onTransitionStart(Transition transition) {
                    // No-op
                }

                @Override
                public void onTransitionCancel(Transition transition) {

                    transition.removeListener(this);
                }

                @Override
                public void onTransitionPause(Transition transition) {
                    // No-op
                }

                @Override
                public void onTransitionResume(Transition transition) {
                    // No-op
                }
            });
            return true;
        }

        return false;
    }


到此为止,场景变换的基本知识就讲完了,有了这些基本知识,我们可以举一反三,设计出非常好的场景变换效果,提升APP使用体验。

时间: 2024-08-26 05:32:38

Android 知识要点整理(11)----Scenes and Transitions(场景和变换)的相关文章

Android 知识要点整理(12)----Animation(动画)

动画分类 Android动画有3类:帧动画.视图动画.属性动画.帧动画和视图动画又统称为补间动画.Android 3.0(API LEVEL 11)开始支持属性动画. 帧动画 帧动画是针对Drawable资源的动画.其本质是一系列Drawable资源的连续变化,其本质是AnimationDrawable对象.其使用方法如下: 定义AnimationDrawable 资源中用到了表示天气的三张图片 <animation-list xmlns:android="http://schemas.a

知识要点整理

extjs 以及 serverless api突破 extjs是基础 java python可以写serverless api java可以理解webbuilder 解析器,李金良要推进xwl2html抽离, 等李金良完成java 版本xwl2html,郭廷涛完成python xwl2html 李金良 郭丰 王占兴 郭庭涛 都要掌握extjs 李金良 郭丰 王占兴 郭庭涛都要掌握编写serverless api,语言java python php ruby go 没有任何限制,自己选择 java

android开发要点

[1]Activity的生命周期 Activity作为android系统的表现层组件有着至关重要的地位,因为他直接和用户接触.一个Activity在被创建和销毁的过程中经历了7个过程,他们分别是oncreat(activity创建后第一个被调用的函数),onstart(当activity显示在界面上的时候被调用),onrestart(从停止到活动调用),onresume(能够和用户进行交互的时候调用),onpause(进入暂停态的时候被调用),onstop(进入停止态的时候被调用),ondest

《Fast Traking via Spatio-Temporal Context Learning》要点整理与代码实现之二

上一篇主要讲解了全文的主要思想,整理了一些可能会被忽略的重点,并画了程序的主流程图,但这个流程图只是一个战略性的总图,较为宏观,而程序在实现时还有一些细节上的预处理也很重要,本篇将总结这些小细节. 视频信号是一组随时间变化的动态信号(二维),引述<图像处理.分析与机器视觉>一书第3.2.3节中的一段话:持续时间短的或变化快的时间信号具有宽的频谱.如果我们要处理非静态信号(non-stationary signal),一种选择是将其分解为小片段(常称作窗口),并假定这些窗口外信号是周期性的.这种

struts2知识系统整理

1.MVC 和 JSP Model 2 **   a.:M-Model 模型 包含两部分:业务数据和业务处理逻辑  b.V-View 视图:视图(View)的职责是负责显示界面和用户交互(收集用户信息).  c.C-Controller 控制器 项目中写的ActionServlet.--------------------------------------------------2.我们写的模式被我们称为JSP Model1,在其中我们有模型层(M),但是视图层(V)的  JSP中包含了业务逻

Android Intent 使用整理

在一个Android应用中,主要是由一些组件组成,(Activity,Service,ContentProvider,etc.)在这些组件之间的通讯中,由Intent协助完成. 正如网上一些人解析所说,Intent负责对应用中一次操作的动作.动作涉及数据.附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 Intent传递给调用的组件,并完成组件的调用.Intent在这里起着实现调用者与被调用者之间的解耦作用.Intent传递过程中,要找到目标消费者(另一个Act

SQL基础知识回顾整理

20150929~20151016所学SQL基础知识回顾整理,后续完善补充 服务器名称:是指你要连接的安装的数据库服务器所在的那台电脑的ip地址,如果是本机的话,就是  . mdf 结尾:数据库数据文件,一个数据库有且只有一个 ldf:数据库日志文件,一个数据库有且至少有一个 数据库中存放数据的结构,是通过表的形式来存储的,一个数据库中有很多个表 基础知识(创建.使用数据库及创建表.添加数据.删除表) 约束 查询 子查询 表连接 视图 各类函数 存储过程 触发器 分页语句 事务 20150929

【转】MongoDB 知识要点一览

原文链接 http://www.cnblogs.com/zhangzili/p/4975080.html MongoDB 知识要点一览 1.启动mongoDb数据库: 进入mongoDB的安装目录,执行如下命令 C:\Program Files\MongoDB\Server\3.0\bin>mongod.exe --dbpath "C:\Program Files\MongoDB\Server\3.0\db" 启动成功后在打开一个cmd窗口,进入mongoDB的安装目录,执行mo

Linux Basics 知识框架整理

本博文目录索引 [TOC] 第01章 在VMware Fusion虚拟机中安装CentOS实验环境 1.1 实验:手动安装CentOS 1.2 reset.sh 脚本 本章总结 第02章 Bash Shell特性 2.1 命令提示符 2.2 实验:定制命令提示符格式 2.3 执行命令 2.4 命令别名 2.5 tab键补全 2.6 命令行历史 2.7 Bash快捷键 本章小结 第03章 获取Linux帮助 3.1 whatis 3.2 help(内部命令) 3.3 帮助选项 3.4 man手册(