這個 Android 官方源碼的注解:
A scene represents the collection of values that various properties in the View hierarchy will have when the scene is applied. A Scene can be configured to automatically run a Transition when it is applied, which will animate the various property changes that take place during the scene change.
大致的中文意思是:
場景表示應用場景時視圖層次結構中的各種屬性,將具有的值的集合。 可以將場景配置為在應用自動運行過渡時,這將為場景更改期間發生的各種屬性、更改設置動畫。
Scene 是Android 19 引入的轉換框架中一個場景 api ,幫我們友好的創建開始布局 Scene 和結束布局 Scene,有了開始 Scene 和結束 Scene,運用 Transition 框架來實現帶有動畫的場景切換。舉個例子,從 A 布局切換到 B 布局,一般情況下處理是 View.GONE 或者 View.VISIBLE,但是這樣太生硬了,沒有一點過度效果。那么 Android 的 Transition 框架就可以完美的解決切換場景帶來的生硬視覺感受。
其中 Scene是一個容器,就是放置你定義的布局,而真正去做場景之間切換這個動作是 Transition 框架中 TransitionManager 調用其中 go 方法或者 transitionTo 方法完成場景之間切換,而真正創建具體動畫交由Transition 子類來完成,開始動畫交給 Transition 來執行。
借助 Android 的過渡框架,您只需提供起始布局和結束布局,即可為界面中的各種運動添加動畫效果。您可以選擇所需的動畫類型(例如,淡入/淡出視圖或更改視圖尺寸),而過渡框架會確定如何為從起始布局到結束布局的運動添加動畫效果。
過渡框架包含以下功能:
- 群組級動畫:將一個或多個動畫效果應用于視圖層次結構中的所有視圖。
- 內置動畫:對淡出或移動等常見效果使用預定義動畫。
- 資源文件支持:從布局資源文件加載視圖層次結構和內置動畫。
- 生命周期回調:接收可控制動畫和層次結構更改流程的回調。
注意:如何在同一 Activity 的各個布局之間打造過渡效果。如果用戶在多個 Activity 之間移動,您應改為參閱啟動使用動畫的 Activity。
在兩種布局之間添加動畫效果的基本流程如下所示:
- 為起始布局和結束布局創建一個
[Scene](https://developer.android.google.cn/reference/android/transition/Scene)
對象。然而,起始布局的場景通常是根據當前布局自動確定的。 - 創建一個
[Transition](https://developer.android.google.cn/reference/android/transition/Transition)
對象以定義所需的動畫類型。 - 調用
[TransitionManager.go()](https://developer.android.google.cn/reference/android/transition/TransitionManager#go(android.transition.Scene))
,然后系統會運行動畫以交換布局。
圖 1 中的示意圖說明了布局、場景、過渡和最終動畫之間的關系。
具體場景動畫 Scene 的創建場景、應用過渡等具體介紹,可以觀看 Android 官方文檔:https://developer.android.google.cn/training/transitions/
public class Scene {
private Context mContext;
private int mLayoutId = -1;
private ViewGroup mSceneRoot;
private View mLayout; // alternative to layoutId
private Runnable mEnterAction, mExitAction;
/**
* 返回由與給定 layoutId 參數關聯的資源文件描述的場景。 如果已經為給定的 sceneRoot 創建了這樣的場
* 景,則將返回相同的場景。 這種基于 layoutId 的場景的緩存允許在代碼中創建的場景和由
* TransitionManager XML 資源文件引用的場景之間共享常見場景。
*
*
* @param sceneRoot The root of the hierarchy in which scene changes
* and transitions will take place.
* @param layoutId The id of a standard layout resource file.
* @param context The context used in the process of inflating
* the layout resource.
* @return The scene for the given root and layout id
*/
public static Scene getSceneForLayout(@NonNull ViewGroup sceneRoot, @LayoutRes int layoutId,
@NonNull Context context) {
SparseArray<Scene> scenes =
(SparseArray<Scene>) sceneRoot.getTag(R.id.transition_scene_layoutid_cache);
if (scenes == null) {
scenes = new SparseArray<>();
sceneRoot.setTag(R.id.transition_scene_layoutid_cache, scenes);
}
Scene scene = scenes.get(layoutId);
if (scene != null) {
return scene;
} else {
scene = new Scene(sceneRoot, layoutId, context);
scenes.put(layoutId, scene);
return scene;
}
}
/**
* 構造一個場景,但沒有關于應用此場景時值將如何變化的信息。 此構造函數可能在創建場景時使用,目
* 的是通過設置 setEnterAction(Runnable) 和可能的 setExitAction(Runnable) 進行動態配置。
* {@link #setExitAction(Runnable)}.
*
* @param sceneRoot The root of the hierarchy in which scene changes
* and transitions will take place.
*/
public Scene(@NonNull ViewGroup sceneRoot) {
mSceneRoot = sceneRoot;
}
/**
* 構造一個場景,當進入該場景時,它將從 sceneRoot 容器中移除所有子節點,并將膨脹并添加 layoutId
* 資源文件指定的層次結構。這個方法是隱藏的,因為基于 layoutId 的場景應該由緩存工廠方法
* Scene.getCurrentScene(View) 創建。
*
* @param sceneRoot The root of the hierarchy in which scene changes
* and transitions will take place.
* @param layoutId The id of a resource file that defines the view
* hierarchy of this scene.
* @param context The context used in the process of inflating
* the layout resource.
*/
private Scene(ViewGroup sceneRoot, int layoutId, Context context) {
mContext = context;
mSceneRoot = sceneRoot;
mLayoutId = layoutId;
}
/**
* 構造一個 Scene ,當進入該場景時,將從 sceneRoot 容器中移除所有子級,并將布局對象添加為該容器
* 的新子級。
*
* @param sceneRoot The root of the hierarchy in which scene changes
* and transitions will take place.
* @param layout The view hierarchy of this scene, added as a child
* of sceneRoot when this scene is entered.
*/
public Scene(@NonNull ViewGroup sceneRoot, @NonNull View layout) {
mSceneRoot = sceneRoot;
mLayout = layout;
}
/**
* 獲取場景的根,它是由于該場景而受到更改影響的視圖層次結構的根,并且在進入該場景時將被動畫
* 化。
*
* @return The root of the view hierarchy affected by this scene.
*/
@NonNull
public ViewGroup getSceneRoot() {
return mSceneRoot;
}
/**
* 如果它是場景的場景根上的當前場景,則退出該場景。 進入場景時設置當前場景。 如果有一個場景,則
* 退出場景會運行退出動作。
*/
public void exit() {
if (getCurrentScene(mSceneRoot) == this) {
if (mExitAction != null) {
mExitAction.run();
}
}
}
/**
* 進入此場景,這需要更改此場景指定的所有值。 這些可能是與現在將添加到場景根的布局視圖組或布局
* 資源文件相關聯的值,或者可能是由 setEnterAction(Runnable) enter action} 更改的值,或這些的組
* 合。 進入場景時不會運行任何過渡。 要在場景更改中獲得過渡行為,請改用 TransitionManager 中的方法之一。
*/
public void enter() {
// Apply layout change, if any
if (mLayoutId > 0 || mLayout != null) {
// empty out parent container before adding to it
getSceneRoot().removeAllViews();
if (mLayoutId > 0) {
LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
} else {
mSceneRoot.addView(mLayout);
}
}
// Notify next scene that it is entering. Subclasses may override to configure scene.
if (mEnterAction != null) {
mEnterAction.run();
}
setCurrentScene(mSceneRoot, this);
}
/**
* 設置給定 ViewGroup 所在的場景。當前場景僅設置在場景的根 ViewGroup 上,而不是針對該層次結構
* 中的每個視圖。 Scene 使用此信息來確定是否存在應在進入新場景之前退出的先前場景。
*
* @param sceneRoot The ViewGroup on which the current scene is being set
*/
static void setCurrentScene(@NonNull ViewGroup sceneRoot, @Nullable Scene scene) {
sceneRoot.setTag(R.id.transition_current_scene, scene);
}
/**
* 獲取給定 ViewGroup 上的當前場景集。 只有當 ViewGroup 是場景根時,才會在 ViewGroup 上設置場
* 景。
*
* @param sceneRoot The ViewGroup on which the current scene will be returned
* @return The current Scene set on this ViewGroup. A value of null indicates that
* no Scene is currently set.
*/
@Nullable
public static Scene getCurrentScene(@NonNull ViewGroup sceneRoot) {
return (Scene) sceneRoot.getTag(R.id.transition_current_scene);
}
/**
* 未使用布局資源或層次結構定義的場景,或者在這些層次結構更改為之后需要執行額外步驟的場景,應
* 設置進入動作,也可能設置退出動作。 輸入動作將導致場景回調到應用程序代碼中,以便在轉換捕獲預
* 更改值之后以及在應用任何其他場景更改之后執行應用程序所需的任何其他操作,例如將布局(如果
* 有) 添加到視圖中 等級制度。 調用此方法后,將播放過渡。
*
* @param action The runnable whose {@link Runnable#run() run()} method will
* be called when this scene is entered
* @see #setExitAction(Runnable)
* @see Scene#Scene(ViewGroup)
*/
public void setEnterAction(@Nullable Runnable action) {
mEnterAction = action;
}
/**
* 未使用布局資源或層次結構定義的場景,或者在這些層次結構更改為之后需要執行額外步驟的場景,應
* 設置進入動作,也可能設置退出動作。 退出操作將導致場景回調到應用程序代碼中,以在適用的轉換已
* 捕獲更改前值之后但在應用任何其他場景更改之前執行應用程序需要執行的任何操作,例如新布局(如
* 果有) 添加到視圖層次結構中。 調用此方法后,將進入下一個場景,如果設置了進入操作,則調用
* setEnterAction(Runnable)。
*
* @see #setEnterAction(Runnable)
* @see Scene#Scene(ViewGroup)
*/
public void setExitAction(@Nullable Runnable action) {
mExitAction = action;
}
/**
* 返回此場景是否由布局資源文件創建,由傳遞給 getSceneForLayout(ViewGroup, int, Context) 的
* layoutId 確定。
*/
boolean isCreatedFromLayoutResource() {
return (mLayoutId > 0);
}
}
對場景動畫 Scene 的具體代碼實踐,可以參考 Android 官方開發團隊的 Demo:
https://github.com/android/animation-samples/tree/main/BasicTransition
場景是視圖層次結構狀態的封裝,包括該層次結構中的視圖以及這些視圖具有的各種值(與布局相關的和其他的)。 場景可以直接通過布局層次結構定義,也可以通過在輸入場景時動態設置場景的代碼來定義。
Transition 是一種自動動畫化進入新場景時發生的變化的機制。 一些轉換功能是自動的。 也就是說,進入場景可能會導致動畫運行,淡出消失的視圖,更改邊界并調整已更改的現有視圖的大小,以及淡入可見的視圖。 還有一些額外的過渡可以為其他屬性設置動畫,例如顏色變化,并且可以選擇性地指定在特定場景變化期間發生。 最后,開發人員可以定義他們自己的 Transition 子類,這些子類監視特定的屬性更改并在這些屬性更改值時運行自定義動畫。
TransitionManager 用于為特定場景更改指定自定義轉換,并導致具有特定轉換的場景更改發生。
具體使用,可看如下代碼示例:
public class BasicTransitionFragment extends Fragment
implements RadioGroup.OnCheckedChangeListener {
// We transition between these Scenes
private Scene mScene1;
private Scene mScene2;
private Scene mScene3;
/** A custom TransitionManager */
private TransitionManager mTransitionManagerForScene3;
/** Transitions take place in this ViewGroup. We retain this for the dynamic transition on scene 4. */
private ViewGroup mSceneRoot;
public static BasicTransitionFragment newInstance() {
return new BasicTransitionFragment();
}
public BasicTransitionFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_basic_transition, container, false);
assert view != null;
RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.select_scene);
radioGroup.setOnCheckedChangeListener(this);
mSceneRoot = (ViewGroup) view.findViewById(R.id.scene_root);
// BEGIN_INCLUDE(instantiation_from_view)
// A Scene can be instantiated from a live view hierarchy.
mScene1 = new Scene(mSceneRoot, (ViewGroup) mSceneRoot.findViewById(R.id.container));
// END_INCLUDE(instantiation_from_view)
// BEGIN_INCLUDE(instantiation_from_resource)
// You can also inflate a generate a Scene from a layout resource file.
mScene2 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene2, getActivity());
// END_INCLUDE(instantiation_from_resource)
// Another scene from a layout resource file.
mScene3 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene3, getActivity());
// BEGIN_INCLUDE(custom_transition_manager)
// We create a custom TransitionManager for Scene 3, in which ChangeBounds and Fade
// take place at the same time.
mTransitionManagerForScene3 = TransitionInflater.from(getActivity())
.inflateTransitionManager(R.transition.scene3_transition_manager, mSceneRoot);
// END_INCLUDE(custom_transition_manager)
return view;
}
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.select_scene_1: {
// BEGIN_INCLUDE(transition_simple)
// You can start an automatic transition with TransitionManager.go().
TransitionManager.go(mScene1);
// END_INCLUDE(transition_simple)
break;
}
case R.id.select_scene_2: {
TransitionManager.go(mScene2);
break;
}
case R.id.select_scene_3: {
// BEGIN_INCLUDE(transition_custom)
// You can also start a transition with a custom TransitionManager.
mTransitionManagerForScene3.transitionTo(mScene3);
// END_INCLUDE(transition_custom)
break;
}
case R.id.select_scene_4: {
// BEGIN_INCLUDE(transition_dynamic)
// Alternatively, transition can be invoked dynamically without a Scene.
// For this, we first call TransitionManager.beginDelayedTransition().
TransitionManager.beginDelayedTransition(mSceneRoot);
// Then, we can just change view properties as usual.
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);
// END_INCLUDE(transition_dynamic)
break;
}
}
}
}