Android Transition转场动效使用全解析
Transition的使用和原理
项目效果
1. 简述
Android 4.4.2 中引入了 Transition 过渡动画,不过功能比较简单。在 Android 5.0 的 Material Design 中引入更完整和强大的 Transition 框架。通过Transition可以实现:
- 同一个页面中的场景过渡动画
- Activity/Fragment 切换时的内容过渡动画(content transition)
- Activity/Fragment 切换时的共享元素过渡动画
核心作用:降低动效的开发成本
2. 关键概念和类
- **Scene:**场景,定义了一个确定的 UI 状态
场景表示视图层级结构中各种属性在应用该场景时所具有的值的集合。一个场景可以被配置为在应用时自动运行一个过渡动画,该过渡动画会对在场景变化过程中发生的各种属性变化进行动画处理。
创建Scene有两种方式:
- Scene.getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)
- new Scene(ViewGroup sceneRoot, View layout)
- **Transition:**转换,定义了两个场景切换时的动画。
( Transition是一个抽象类,系统提供了一些实现类,也可以通过继承Transition来自定义Transition)
当两个场景切换时,Transition 主要有下面两个行为:
- 确定开始场景和结束场景中每个 view 的状态。
- 根据状态差异创建 Animator,用以场景切换时 view 执行的动画。
- **TransitionManager:**用于管理场景之间的过渡。有以下两种方式进行场景切换
- TransitionManager.go(Scene scene, Transition transition)
- TransitionManager.beginDelayedTransition(ViewGroup sceneRoot, Transition transition)
3. 系统提供的Transition
系统提供的Transition通常包括淡入/淡出、滑动、缩放等动画效果。
4. 自定义Transition
自定义Transition步骤和示例如下
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.graphics.drawable.Drawable;
import android.transition.Transition;
import android.transition.TransitionValues;
import android.view.ViewGroup;
import android.widget.ImageView;/*** 自定义Transition,实现两个不相同的图片的转场*/
//1.继承Transition
public class ChangeImageResourceTransition extends Transition {//2.属性定义:官方建议命名规则:package_name:transition_class:property_nameprivate static final String CHANGE_IMAGE = "plato:ChangeImageResourceTransition:src";//3.采集起始帧(属性)@Overridepublic void captureStartValues(TransitionValues transitionValues) {captureValues(transitionValues);}//4.采集终止帧(属性)@Overridepublic void captureEndValues(TransitionValues transitionValues) {captureValues(transitionValues);}private void captureValues(TransitionValues transitionValues) {if (transitionValues == null || !(transitionValues.view instanceof ImageView)) return;ImageView imageView = (ImageView) transitionValues.view;transitionValues.values.put(CHANGE_IMAGE, imageView.getDrawable());}//5.根据起始属性和终止属性创建属性动画@Overridepublic Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {if (startValues == null || endValues == null )return null;if (endValues.view instanceof ImageView){return super.createAnimator(sceneRoot, startValues, endValues);}ImageView endView = (ImageView) endValues.view;Drawable startDrawable = (Drawable) startValues.values.get(CHANGE_IMAGE);Drawable endDrawable = (Drawable) endValues.values.get(CHANGE_IMAGE);ValueAnimator animator = ValueAnimator.ofFloat(0, 1f);animator.addUpdateListener(animation -> {float animatedValue = (float) animation.getAnimatedValue();if (animatedValue <= 0.5) {endView.setImageDrawable(startDrawable);float ratio = (0.5f - animatedValue) / 0.5f;endView.setAlpha(ratio);} else {endView.setImageDrawable(endDrawable);float ratio = (animatedValue - 0.5f) / 0.5f;endView.setAlpha(ratio);}});animator.setDuration(2000);return animator;}
}
如上定义了一个Transition,这个Transition用来作用于两个不同的图片之间的转换,使之更加平滑自然。关键点在于0-0.5-1之间的变化
5. Transition目标管理
ViewGroup中可能有多个子view,有时我们希望只针对指定的view添加转换效果,此时可以为transition指定目标view:
- 增加动画目标:
addTarget(View target)
addTarget(int targetViewId)
addTarget(String targetName)
addTarget(Class targetType) - 移除动画目标:
removeTarget(View target)
removeTarget(int targetId)
removeTarget(String targetName)
removeTarget(Class target) - 排除不进行动画的view:
excludeTarget(View target, boolean exclude)
excludeTarget(int targetId, boolean exclude)
excludeTarget(Class type, boolean exclude)
excludeTarget(Class type, boolean exclude) - 排除某个 ViewGroup 的所有子View:
excludeChildren(View target, boolean exclude)
excludeChildren(int targetId, boolean exclude)
excludeChildren(Class type, boolean exclude)
除了通过代码形式,也可以在xml中进行配置。
6.Transition的创建
方式一:XML定义(res/transition下)
<?xml version="1.0" encoding="utf-8"?><!-- res/transition/slide_transition.xml -->
<slide xmlns:android="http://schemas.android.com/apk/res/android"android:duration="500"android:interpolator="@android:interpolator/accelerate_decelerate"android:slideEdge="top" />
TransitionSet也是Transition的子类,可以对多个Transition进行组合
<?xml version="1.0" encoding="utf-8"?><!-- res/transition/shared_image.xml -->
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"android:duration="500"android:transitionOrdering="together"><changeClipBounds /><changeTransform /><changeBounds /><changeImageTransform />
</transitionSet>
方式二:代码创建
val slide = Slide().apply {duration = 500interpolator = AccelerateDecelerateInterpolator()slideEdge = Gravity.TOP}val enterTransitionSet = TransitionSet().apply {addTransition(ChangeClipBounds())addTransition(ChangeTransform())addTransition(ChangeBounds())addTransition(ChangeImageTransform())if (!cartoon.hasObtain) {//可以根据条件添加addTransition(CustomTransition())}}
7.布局元素动画
在单个页面内使用Transition有两种方式:
- TransitionManager.go(Scene scene, Transition transition)
layout_scene1.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#f00"><ImageViewandroid:id="@+id/iv_icon"android:layout_width="200dp"android:layout_height="200dp"android:src="@drawable/boy"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><TextViewandroid:id="@+id/tv_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="12dp"android:text="name"android:textColor="#fff"android:textSize="30sp"android:textStyle="bold"app:layout_constraintBottom_toBottomOf="@id/iv_icon"app:layout_constraintStart_toEndOf="@id/iv_icon"app:layout_constraintTop_toTopOf="@id/iv_icon" /></androidx.constraintlayout.widget.ConstraintLayout>
layout_scene2.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#062FF8"><ImageViewandroid:id="@+id/iv_icon"android:layout_width="100dp"android:layout_height="100dp"android:scaleType="centerCrop"android:src="@drawable/boy"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><TextViewandroid:id="@+id/tv_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="20dp"android:text="name"android:textColor="#FFCA28"android:textSize="50sp"android:textStyle="bold"app:layout_constraintEnd_toEndOf="@id/iv_icon"app:layout_constraintStart_toStartOf="@id/iv_icon"app:layout_constraintTop_toBottomOf="@id/iv_icon" /></androidx.constraintlayout.widget.ConstraintLayout>
activity_transition_go.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="horizontal"><FrameLayoutandroid:id="@+id/sceneRoot"android:layout_width="1000dp"android:layout_height="500dp"android:background="@color/color7"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><Buttonandroid:id="@+id/btClick"android:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="onOneClick"android:text="场景一"android:textSize="100sp"app:layout_constraintStart_toStartOf="@id/sceneRoot"app:layout_constraintTop_toBottomOf="@id/sceneRoot" /><Buttonandroid:id="@+id/btClick2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="onTwoClick"android:text="场景二"android:textSize="100sp"app:layout_constraintEnd_toEndOf="@id/sceneRoot"app:layout_constraintTop_toBottomOf="@id/sceneRoot" />
</androidx.constraintlayout.widget.ConstraintLayout>
class TransitionGoActivity : AppCompatActivity() {private var mRoot: FrameLayout? = nullprivate var mScene1: Scene? = null//场景1private var mScene2: Scene? = null//场景2override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_transition_go)mRoot = findViewById(R.id.sceneRoot)mScene1 = Scene.getSceneForLayout(mRoot!!, R.layout.layout_scene1, this)mScene2 = Scene.getSceneForLayout(mRoot!!, R.layout.layout_scene2, this)}fun onOneClick(view: View?) {TransitionManager.go(mScene1!!)}fun onTwoClick(view: View?) {TransitionManager.go(mScene2!!)}
}
以上代码实现点击两个不同的button,切到不同的Scene中。效果如下:
实际效果
- TransitionManager.beginDelayedTransition(ViewGroup sceneRoot, Transition transition)
activity_transition_begin_delay.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/scene_root"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".activity.TransitionBeginDelayActivity"><TextViewandroid:id="@+id/tv_transition1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center"android:text="Hello"android:textSize="@dimen/text_size"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><ImageViewandroid:id="@+id/iv_transition2"android:layout_width="300dp"android:layout_height="300dp"android:gravity="center"android:src="@drawable/girl"android:tag="girl"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/tv_transition1" /><Buttonandroid:id="@+id/btn_slide"android:layout_width="0dp"android:layout_height="wrap_content"android:text="slide"android:textAllCaps="false"android:textSize="30sp"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/iv_transition2" /><Buttonandroid:id="@+id/btn_custom_transition"android:layout_width="0dp"android:layout_height="wrap_content"android:text="customTransition"android:textAllCaps="false"android:textSize="30sp"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@id/btn_slide" /></androidx.constraintlayout.widget.ConstraintLayout>
import android.os.Bundle
import android.text.TextUtils
import android.transition.Slide
import android.transition.TransitionManager
import android.transition.TransitionSet
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import com.example.myapplication.R
import com.example.myapplication.transition.ChangeImageResourceTransitionclass TransitionBeginDelayActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_transition_begin_delay)initListener()}private fun initListener() {//改变文本是否可见findViewById<View>(R.id.btn_slide).setOnClickListener {val sceneRoot1 = findViewById<ViewGroup>(R.id.scene_root)val tvTransition1 = findViewById<View>(R.id.tv_transition1)val slide = Slide(Gravity.END)val transitionSet = TransitionSet()transitionSet.addTransition(slide)TransitionManager.beginDelayedTransition(sceneRoot1, transitionSet)if (tvTransition1.visibility == View.VISIBLE) {tvTransition1.visibility = View.INVISIBLE} else {tvTransition1.visibility = View.VISIBLE}}//改变图片的内容findViewById<View>(R.id.btn_custom_transition).setOnClickListener {val changeImageResourceTransition = ChangeImageResourceTransition()val sceneRoot2 = findViewById<ViewGroup>(R.id.scene_root)val ivTransition = findViewById<ImageView>(R.id.iv_transition2)changeImageResourceTransition.addTarget(ivTransition)TransitionManager.beginDelayedTransition(sceneRoot2, changeImageResourceTransition)val tag = ivTransition.tag as Stringif (TextUtils.equals(tag, "girl")) {ivTransition.setImageResource(R.drawable.boy)ivTransition.tag = "boy"} else {ivTransition.setImageResource(R.drawable.girl)ivTransition.tag = "girl"}}}
}
以上代码实现点击两个不同的button,分别文本和图片的属性,进而触发transition动效。效果如下:
实际效果
8.Content Transition
Content Transition用来实现两个不同的页面切换时的转场效果。可以通过Theme或代码设置四种不同的Transition(exitTransition,enterTransition,returnTransition,reenterTransition)及监听。
theme:
<style name="BaseAppTheme" parent="Theme.AppCompat.Light"><item name="android:windowEnterTransition">@transition/slide_transition</item><item name="android:windowExitTransition">@transition/slide_transition</item></style>
代码:
window.exitTransition = slide
window.enterTransition = slide
activity_content_transition_start.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".activity.ContentTransitionStartActivity"><TextViewandroid:id="@+id/tv_start_one1"android:layout_width="500dp"android:layout_height="500dp"android:layout_marginTop="40dp"android:background="#F00"android:gravity="center"android:text="slide"android:textAllCaps="false"android:textColor="#fff"android:textSize="@dimen/text_size"android:textStyle="bold"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>
class ContentTransitionStartActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_content_transition_start)initListener()}private fun initListener() {findViewById<View>(R.id.tv_start_one1).setOnClickListener {val slide = Slide()//slide.addTarget(R.id.tv_start_one1)//如果不加target,就是对Window的视图进行转场了slide.duration = 1000window.exitTransition = slideval activityOptions =ActivityOptions.makeSceneTransitionAnimation(this@ContentTransitionStartActivity)startActivity(Intent(this@ContentTransitionStartActivity,ContentTransitionEndActivity::class.java), activityOptions.toBundle())}}
}
class ContentTransitionEndActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_content_transition_end)window.allowEnterTransitionOverlap = falseinitListener()}private fun initListener() {findViewById<View>(R.id.tv_end_one1).setOnClickListener {val slide = Slide()//slide.addTarget(R.id.tv_end_one1)如果不加target,就是对Window的视图进行转场了slide.duration = 1000window.returnTransition = slidefinishAfterTransition()}}
}
效果如下:
实际效果
Transition Overlap
默认情况下,内容过渡动画的 进入/返回 转换会在 退出/重新进入 转换结束前一点点开始,产生一个小的重叠来让整体的效果更自然、更协调。这个行为其实可以通过 Window/Fragment 的setAllowEnterTransitionOverlap(boolean)和setAllowReturnTransitionOverlap(boolean)方法来设置,默认overlap是true,进入转换会退出转换开始后尽可能快地开始,如果设置为 false,进入转换只能在退出转换结束后开始。也可以在Theme设置。
9.SharedElement Transition
SharedElement Transition指的是不同的页面切换时,有两个有关联的组件需要进行前后的衔接。比如实现从列表页打开详情页面时,头像图片进行放大。同样地,可以通过Theme或代码进行设置四种不同的SharedElement Transition及监听。
val inflateTransition =TransitionInflater.from(this).inflateTransition(R.transition.shared_image).apply {duration = 1000}window.sharedElementEnterTransition = inflateTransitionwindow.sharedElementExitTransition = inflateTransition
要点:前后两个页面需要有转场的view设置相同transitionName值(可通过xml或代码进行设置)
activity_content_transition_start.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".activity.ShareElementStartActivity"><TextViewandroid:id="@+id/shareElement1_start"android:layout_width="200dp"android:layout_height="200dp"android:layout_marginTop="40dp"android:background="@color/color1"android:gravity="center"android:text="文本一"android:textSize="@dimen/text_size"android:transitionName="shareElement1"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><ImageViewandroid:id="@+id/shareElement2_start"android:layout_width="200dp"android:layout_height="200dp"android:layout_marginTop="20dp"android:src="@drawable/boy"android:transitionName="shareElement2"app:layout_constraintEnd_toEndOf="@id/shareElement1_start"app:layout_constraintStart_toStartOf="@id/shareElement1_start"app:layout_constraintTop_toBottomOf="@id/shareElement1_start" /></androidx.constraintlayout.widget.ConstraintLayout>
class ShareElementStartActivity : AppCompatActivity() {private var shareElement1: View? = nullprivate var shareElement2: View? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_share_element_start)//val inflateTransition =// TransitionInflater.from(this).inflateTransition(R.transition.shared_image).apply {// duration = 1000// }//window.sharedElementEnterTransition = inflateTransition//window.sharedElementExitTransition = inflateTransitionshareElement1 = findViewById(R.id.shareElement1_start)shareElement2 = findViewById(R.id.shareElement2_start)initListener()}private fun initListener() {shareElement1!!.setOnClickListener {val intent = Intent(this@ShareElementStartActivity, ShareElementEndActivity::class.java)val viewStringPair1 = Pair<View?, String>(shareElement1, shareElement1!!.transitionName)val viewStringPair2 = Pair<View?, String>(shareElement2, shareElement2!!.transitionName)val options = ActivityOptions.makeSceneTransitionAnimation(this@ShareElementStartActivity,viewStringPair1,viewStringPair2,)startActivity(intent, options.toBundle())}}
}
activity_share_element_end.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".activity.ContentTransitionEndActivity"><TextViewandroid:id="@+id/shareElement1_end"android:layout_width="200dp"android:layout_height="200dp"android:layout_margin="20dp"android:background="@color/color1"android:gravity="center"android:text="文本一"android:textSize="@dimen/text_size"android:transitionName="shareElement1" /><ImageViewandroid:id="@+id/shareElement2_end"android:layout_width="200dp"android:layout_height="200dp"android:layout_margin="20dp"android:src="@drawable/girl"android:transitionName="shareElement2" />
</LinearLayout>
ShareElementEndActivity
class ShareElementEndActivity : AppCompatActivity() {private var shareElement1: View? = nullprivate var shareElement2: View? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)//val inflateTransition =// TransitionInflater.from(this).inflateTransition(R.transition.shared_image).apply {// duration = 1000// }//window.sharedElementEnterTransition = inflateTransition//window.sharedElementExitTransition = inflateTransitionsetContentView(R.layout.activity_share_element_end)}
}
效果如下:
实际效果
10. 原理
- 1.页面内View场景动效原理
本质是属性动画。Transition负责捕获视图的起始属性和终止属性,创建属性动画,TransitionManager负责Transition的调度执行。 - 2.转场动效原理
- 上述Transition和TransitionManager
- ViewGroupOverlay :覆盖在ViewGroup之上的视图层。是 ViewOverlay 的一个子类,除了支持可绘制对象的功能外,还添加了对 ViewGroups 上的覆盖层视图的管理能力。
- GhostView:这个视图在覆盖层中绘制另一个视图,而不改变父级视图。它的可见性设置为 INVISIBLE,所以父级视图不会将其绘制出来,但是它会使用自己的渲染节点在此处进行绘制。当 GhostView 设置为 INVISIBLE 时,它所遮挡的视图会变为 VISIBLE,当 GhostView 变为 VISIBLE 时,遮挡的视图会变为 INVISIBLE。
- ExitTransitionCoordinator :退出动画核心类,继承自ActivityTransitionCoordinator
- EnterTransitionCoordinator:进入动画核心类,继承自ActivityTransitionCoordinator
- Activity跳转和返回源码逻辑…
一起看下源码………
ActivityA -> ActivityB过程总结:
- ActivityA先执行退场动画
- ActivityA将共享元素的结束位置等属性传递给ActivityB
- ActivityB加载自己的布局,在onStart生命周期左右去找到共享元素 先定位到ActivityA的结束位置
- ActivityB开始执行过度动画,过渡到自己布局中的位置
ActivityB -> ActivityA过程总结:
- 将ActivityB的window背景设置成透明, 并执行非共享元素的退场动画
- 返回到ActivityA时,将会执行到performStart方法,并执行非共享元素的进场动画
- ActivityB接收到ActivityA传过来的共享元素状态,开始执行共享元素的退场动画
- ActivityA接收到ActivityB的共享元素状态,继续执行共享元素动画(但由于两个状态没有变化,所以并不会执行动画,会立马直接动画结束的回调)
11.注意事项和说明
- 页面A->页面B,共享元素的transitionName要唯一
- FragmentA->FragmentB->FragmentC ,共享元素的transitionName要唯一
- 可以调postponeEnterTransition()延迟开启进入动效,然后在合适的时机调用startPostponedEnterTransition()开启进入动效
- Material主题默认会将exit的transition设置成null,而enter的transition设置成Fade .如果reenter或者return transition没有明确设置,则将用exit和enter的transition替代。
- Material主题默认会将exit的共享元素transition设置成null,而enter的共享元素transition设置成@android:transition/move. 如果reenter 或者 return transition没有明确设置,则将用exit 和enter的共享元素transition替代。
- 默认情况下,material主题的应用中enter/return的content transition会在exit/reenter的content transitions结束之前开始播放(只是稍微早于),这样会看起来更加连贯。如果你想明确屏蔽这种行为,可以调用setWindowAllowEnterTransitionOverlap() 和 setWindowAllowReturnTransitionOverlap()方法。
- Fragment的exit, enter, reenter, 和return transition需要调用fragment的相应方法来设置,或者通过theme的属性来设置。
- Fragment的共享元素的enter和return transition也需要调用fragment的相应方法来设置,或者通过theme的属性来设置。
相关文章:
Android Transition转场动效使用全解析
Transition的使用和原理 项目效果 1. 简述 Android 4.4.2 中引入了 Transition 过渡动画,不过功能比较简单。在 Android 5.0 的 Material Design 中引入更完整和强大的 Transition 框架。通过Transition可以实现: 同一个页面中的场景过渡动画Activit…...
第九章Python语言高阶加强-面向对象篇
目录 一.初始对象 二.成员方法 1.成员变量和成员方法 三.类和对象 四.构造方法 五.其他内置方法(魔术方法) 1.__str__字符串方法 2.__lt__小于符号比较方法 3.__le__小于等于比较符号方法 4.__eq__比较运算符实现方法 六.封装 七.继承 1.继承…...
AI重构SEO关键词智能布局
内容概要 随着人工智能技术在搜索引擎优化领域的深入发展,AI驱动的关键词智能布局正在重塑传统SEO策略的核心逻辑。通过整合自然语言处理、深度学习与语义分析技术,现代SEO系统已形成包含智能分词、意图解码、动态优化的三维技术框架,使关键…...
言同数字:法新社AFP海外新闻媒体发稿成功案例——出海品牌背书必备
作者:言同数字全球传播团队 一、品牌困境:当中国技术遇上海外认知壁垒 案例背景: 某中国光伏储能企业(应保密要求匿名,代号"GreenTech"),其家用储能系统在欧洲市场遭遇࿱…...
第三章 react redux的学习之redux和react-redux,@reduxjs/toolkit依赖结合使用
redux系列文章目录 第一章 简单学习redux,单个reducer 第二章 简单学习redux,多个reducer 第四章 react-redux,reduxjs/toolkit依赖,学习 第五章 两张图告诉你redux常使用的api有哪些 前言 前面两章,我们是只使用的redux的依赖。 本章…...
【HTML】纯前端网页小游戏-戳破彩泡
分享一个简单有趣的网页小游戏 - 彩色泡泡爆破。玩家需要点击屏幕上随机出现的彩色泡泡来得分。 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-wi…...
【Python使用】嘿马云课堂web完整实战项目第3篇:增加数据,修改数据【附代码文档】
教程总体简介:项目概述 项目背景 项目的功能构架 项目的技术架构 CMS 什么是CMS CMS需求分析与工程搭建 静态门户工程搭建 SSI服务端包含技术 页面预览开发 4 添加“页面预览”链接 页面发布 需求分析 技术方案 测试 环境搭建 数据字典 服务端 前端 数据模型 页面原…...
数据结构【栈和队列附顺序表应用算法】
栈和队列和顺序表应用算法练习 1.栈1.1概念与结构1.2栈的实现 2.队列2.1概念与结构2.2队列的实现 3.附(顺序表应用算法)3.1移除元素3.2删除有序数组中的重复项3.3合并两个有序数组 1.栈 1.1概念与结构 栈:⼀种特殊的线性表,其只…...
Redis数据结构之String
目录 1.概述2.常见操作2.1 SET/GET2.2 MSET/MGET/MSETNX2.3 GETRANGE/SETRANGE2.4 INCR(BY)/DECR(BY)2.5 STRLEN2.6 APPEND2.7 GETSET 3.小结 1.概述 String是最常用的数据类型,一个key对应一个value。String是二进制安全的,可以包含任何数据࿰…...
Maven 远程仓库推送方法
步骤 1:配置 pom.xml 中的远程仓库地址 在项目的 pom.xml 文件中添加 distributionManagement 配置,指定远程仓库的 URL。 xml 复制 <project>...<distributionManagement><!-- 快照版本仓库 --><snapshotRepository><id…...
uname
在 C 语言中,uname 函数用于获取当前操作系统的相关信息。 它是 POSIX 标准的一部分,定义在 <sys/utsname.h> 头文件中。 通过调用 uname 函数,可以获取系统名称、节点名称(主机名)、操作系统版本、机器硬件架构…...
【无标题】object,wait,notifyAll
在 Java 中,Object类提供了wait()方法,用于线程间的协作和同步。wait()方法使得当前线程暂停执行,并释放当前对象的锁,直到其他线程调用该对象的notify()或notifyAll()方法将其唤醒。这是实现线程间通信和同步的重要机制之一。 w…...
【Vue】 核心特性实战解析:computed、watch、条件渲染与列表渲染
目录 一、计算属性(computed) ✅ 示例: 计算属性-methods实现:在插值模块里,实现函数的调用功能 计算属性-computed的实现: 计算属性-简写: ✅ 特点: ⚠️ 与 methods 的区别…...
精品可编辑PPT | 基于湖仓一体构建数据中台架构大数据湖数据仓库一体化中台解决方案
本文介绍了基于湖仓一体构建数据中台架构的技术创新与实践。它详细阐述了数据湖、数据仓库和数据中台的概念,分析了三者的区别与协作关系,指出数据湖可存储大规模结构化和非结构化数据,数据仓库用于高效存储和快速查询以支持决策,…...
基于Python网络爬虫的智能音乐可视化系统(源码+lw+部署文档+讲解),源码可白嫖!
摘要 时代在飞速进步,每个行业都在努力发展现在先进技术,通过这些先进的技术来提高自己的水平和优势,智能音乐可视化系统当然不能排除在外。我本次开发的基于网络爬虫的智能音乐可视化系统是在实际应用和软件工程的开发原理之上,…...
基于STM32与应变片的协作机械臂力反馈控制系统设计与实现----2.2 机械臂控制系统硬件架构设计
2.2 机械臂控制系统硬件架构设计 一、总体架构拓扑 1.1 典型三级硬件架构 #mermaid-svg-MWmxD3zX6bu4iFCv {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-MWmxD3zX6bu4iFCv .error-icon{fill:#552222;}#mermaid-s…...
在线记事本——支持Markdown
项目地址 https://github.com/Anyuersuper/CloudNotebook 百度网盘 通过网盘分享的文件:CloudNotebook-master.zip 链接: https://pan.baidu.com/s/1kd2qNvm0eXc6_7oYDR769A?pwdyuer 提取码: yuer 📝 云笔记 (Cloud Notebook) 云笔记是一个简洁、安全…...
DDPM 做了什么
本博客主要侧重点在于HOW也就是DDPM怎么做的而不是WHY为什么要这样做 那么第一个问题DDPM做了一件什么事:这个算法通过逐渐向原图像添加噪声来破坏图像,然后再学习如何从噪声成恢复图像。 第二件事如何做到的:通过训练一个网络,…...
Redis数据结构之List
目录 1.概述2.常见操作2.1 LPUSH/RPUSH/LRANGE2.2 LPOP/RPOP2.3 LINDEX2.4 LLEN2.5 LREM2.6 LTRIM2.7 RPOPLPUSH2.8 LSET2.9 LINSERT 1.概述 List是简单的字符串列表,单key多个value,按照插入顺序排序。 支持添加一个元素到列表的头部(左边)或者尾部(右…...
L2-023 图着色问题 #DFS C++邻接矩阵存图
文章目录 题目解读输入格式输出格式 思路Ac CODE 参考 题目解读 给定一个无向图V,询问是否可以用K种颜色为V中每一个顶点分配一种颜色,使得不会有两个相邻顶点具有同一种颜色 输入格式 第一行给出V,E,K, 分别代表无向图的顶点,…...
架构下的按钮效果设置
以下是一个完整的跨QML/Qt Widgets的主题方案实现,包含对按钮阴影的统一管理: 一、项目结构 Project/ ├── core/ │ ├── thememanager.h │ └── thememanager.cpp ├── widgets/ │ ├── mainwindow.h │ ├── mainwindow.cpp …...
Unhandled exception: org.apache.poi.openxml4j.exceptions.InvalidFormatException
代码在main方法里面没有报错,在Controller里面就报错了。 原来Controller类里面少了行代码 import org.apache.poi.openxml4j.exceptions.InvalidFormatException; 加上去就解决了。...
Vue2_Vue.js教程
目录 一、Vue.js安装 1、独立版本 2、CDN 方法 3、npm 方法 二、Vue Al编程助手 三、Vue.js目录结构 目录解析 四、Vue.js 起步 1.如何定义数据对象和方法并渲染进页面 五、Vue.js 模板语法 插值 文本_{{}} Html_v-html 指令 属性_v-bind (数据传输工具)指令 表…...
2025/4/2 心得
第一题 题目描述 给定1001个范围在[1,1000]的数字,保证只有1个数字重复出现2次,其余数字只出现1次。试用O(n)时间复杂度来求出出现2次的这个数字。 不允许用数组 输入格式 第一行:一个整数1001; 第二行:1001个用…...
Deep Reinforcement Learning for Robotics翻译解读
a. 机器人能力 1 单机器人能力(Single-robot competencies) 运动能力(Mobility) 行走(Locomotion)导航(Navigation) 操作能力(Manipulation) 静态操作&…...
【Linux】日志模块实现详解
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
AT_abc212_d [ABC212D] Querying Multiset
链接:AT_abc212_d [ABC212D] Querying Multiset - 洛谷 题目描述 高橋君は何も書かれていないたくさんのボールと 1 つの袋を持っています。 最初、袋は空で、高橋君は Q 回の操作を行います。 それぞれの操作は以下の 3 種類のうちのいずれかです。 操作 1 : ま…...
Android使用OpenGL和MediaCodec录制
目录 一,什么是opengl 二,什么是Android OpenGL ES 三, OpenGL 绘制流程 四, OpenGL坐标系 五, OpenGL 着色器 六, GLSL编程语言 七,使用MediaCodec录制在Opengl中渲染架构 八,代码实现 8.1 自定义渲染view继承GLSurfaceView 8.2 自定义渲染器TigerRender 8.3 创建编…...
Java 实现插入排序:[通俗易懂的排序算法系列之三]
引言 大家好!欢迎继续关注我的排序算法系列。今天,我们要学习的是另一种非常基础且重要的排序算法——插入排序 (Insertion Sort)。 插入排序的思路非常贴近我们日常整理扑克牌的方式,理解起来相对自然。虽然它在最坏情况下的效率不高&…...
HarmonyOS:WebView 控制及 H5 原生交互实现
一、效果展示 二、技术栈 技术栈: 编程语言:使用 TypeScript 进行开发,借助其类型系统提升代码的可读性与稳定性。 框架与库:基于鸿蒙系统相关框架(如kit.ArkWeb、hadss/hmrouter)…...
250405-VSCode编辑launch.json实现Debug调试Open-WebUI
A. 最终效果 根据__init__.py配置launch.json 根据中utils/chat.py中form_data的messages [{role: user, content: 唐老鸭}],可以找到用户输入,进而通过关键词或模型调用的方式,对敏感问题进行特殊处理。 B. 文件配置 launch.json // { // /…...
SQL Server 数据库实验报告
1.1 实验题目:索引和数据完整性的使用 1.2 实验目的: (1)掌握SQL Server的资源管理器界面应用; (2)掌握索引的使用; (3)掌握数据完整性的…...
【寻找Linux的奥秘】第三章:基础开发工具(上)
请君浏览 前言1. 软件包管理器1.1 linux中安装软件1.2 yum的具体操作1.2.1 查找软件包1.2.2 安装软件1.2.3 卸载软件 1.3 小结 2. 编辑器vim2.1 vim的基本概念和操作2.2 命令模式的命令集光标定位其他命令模式切换(常用的) 2.3 末⾏模式的命令集2.4 小结…...
Photoshop 2025 Mac中文Ps图像编辑
Photoshop 2025 Mac中文Ps图像编辑 文章目录 Photoshop 2025 Mac中文Ps图像编辑一、介绍二、效果三、下载 一、介绍 Adobe Photoshop 2025 Mac版集成了多种强大的图像编辑、处理和创作功能。①强化了Adobe Sensei AI的应用,通过智能抠图、自动修复、图像生成等功能…...
#SVA语法滴水穿石# (004)关于 ended 和 triggered 用法
在 SystemVerilog 断言(SVA, SystemVerilog Assertions)中,ended 是一个用于 序列(sequence) 的关键字,它表示某个序列(sequence)在特定时间点已经成功匹配(即“结束”)。 ended 主要用于 同步不同序列的时间关系,尤其是在多序列组合或属性(property)中需要对齐时…...
16.1Linux自带的LED灯驱动实验(知识)_csdn
前面我们都是自己编写 LED 灯驱动,其实像 LED 灯这样非常基础的设备驱动, Linux 内核已经集成了。 Linux 内核的 LED 灯驱动采用 platform 框架,因此我们只需要按照要求在设备树文件中添加相应的 LED 节点即可,本章我们就来学习如…...
普通类、抽象类和接口的区别
1. 普通类 (Concrete Class) 定义:完整的类,可以直接实例化 特点: 可以包含属性、普通方法(有具体实现)和构造方法 可以被直接实例化创建对象 可以被继承(除非用final修饰) 示例࿱…...
使用 Elastic 实现端到端的大语言模型(LLM)可观测性:洞察生成式 AI 应用这个不透明的世界
作者:来自 Elastic Daniela Tzvetkova 及 Bahubali Shetti 在快速发展的人工智能领域,大语言模型(Large Language Models - LLMs)已成为创新的灯塔,为各行各业带来了前所未有的能力。从生成类人文本、翻译语言到提供个…...
15.2linux设备树下的platform驱动编写(程序)_csdn
我尽量讲的更详细,为了关注我的粉丝!!! 修改设备树文件: 这个我们在上一章已经写过了,但是还是带着大家来重写一遍! 1.打开pinctrl-stm32.c 这个文件: strict 成员变量默认为 true&…...
Java的Selenium的特殊元素操作与定位之window切换
当你要操作另外一个窗口页面的元素时,一定要注意先切换窗口 切换方式:传入要操作窗口的name或者句柄handle driver.switchTo.window(nameOrHandle); 如何获取到窗口的句柄 driver.getWindowHandle();//获取当前操作窗口的句柄driver.getWindowHandles();//获取测…...
【Rust学习】Rust环境搭建和Rust基础语法
本文专栏:Rust学习 目录 一,Rust环境搭建 1,C环境安装 2,Rust下载 3,Rust安装 4,Rust环境检测 二,创建Rust项目 1,rustc 2,cargo 三,输出到命令行 …...
在windows环境下通过docker-compose脚本自动创建mysql和redis
一、环境版本 在windows环境下通过docker容器运行各种服务,使用的软件版本如下: docker desktop :V4.39.0 【docker的安装环境设置略】 mysql:9.2 redis:7.4.2 二、各配置文件 1.已经解决了字符集和排序规则问题造成…...
【玩泰山派】2、制作buildroot镜像,并烧录
文章目录 前言制作buildroot镜像过程搭建环境(docker版)下载泰山派开发的sdk利用制作的镜像和下载的sdk去启动开发docker容器编译buildroot镜像 参考 前言 泰山派官方提供了不少现成的镜像 但是都买了泰山派了,肯定是想自己编译折腾下&…...
实验二 VLAN 的配置与应用
一、实验目的 1. 熟悉 VLAN 和 PORT VLAN 的原理; 2. 熟悉华为网络模拟器的使用; 3. 掌握网络拓扑图的绘制; 4. 掌握单交换机内 VLAN 的配置。 二、实验设备 PC、华为模拟器 ENSP。 三、实验步骤 知识准备:VLAN 和 PORT V…...
【C/C++算法】蓝桥杯之递归算法(如何编写想出递归写法)
绪论:冲击蓝桥杯一起加油!! 每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry” 绪论: ———————— 早关注不迷路,话不多说安全带系好,发车啦&am…...
coding ability 展开第九幕(位运算——进阶篇)超详细!!!!
文章目录 前言丢失的数字两整数之和只出现一次的数字II消失的两个数字总结 前言 上一篇博客,我们已经把位运算的基础知识,以及基本运算都掌握啦 上次的习题还是让人意犹未尽,今天我们来尝试一下难一点的题目 位运算熟练起来真的让人觉得做题是…...
Python实现NOA星雀优化算法优化随机森林回归模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档视频讲解),如需数据代码文档视频讲解可以直接到文章最后关注获取。 1.项目背景 在现代数据科学领域,回归分析是解决预测问题的核心工具之一。然而,在面对复…...
蓝桥云客--浓缩咖啡液
4.浓缩咖啡液【算法赛】 - 蓝桥云课 问题描述 蓝桥杯备赛选手小蓝最近刷题刷到犯困,决定靠咖啡续命。他手上有 N 种浓缩咖啡液,浓度分别是 A1%, A2%, …, AN%,每种存货都是无限的。为了提神又不炸脑,小蓝需要按比例混合这…...
异常【C++】
文章目录 异常异常的概念和基本语法异常的三个关键字: 异常抛出和被接收的过程异常的再次抛出再次抛出被非catch(...)捕捉到的异常再次抛出被catch(...)捕捉到的异常 异常规范异常安全异常的优缺点优点缺点总结&#x…...
关于图片分类任务的猜想 | 撰写论文 paper
关于图片分类任务的猜想 | 撰写论文 paper 背景Yolo 是一次巨大的飞跃过滤无关的特征Yolo 的问题 背景 在计算视觉领域,有几个关键的演变。 1)CNN 卷积的出现,这是一个大的创新; 2)从卷积到 AlexNet ,是更…...