Android 动画详解
Android动画的分类与使用
学习Android必不可少的就是动画的使用了,在Android版本迭代的过程中,出现了很多动画框架,这里做一个总结。
Android动画类型分类
逐帧动画【Frame Animation】,即顺序播放事先准备的图片。
补间动画【Tween Animation】,View的动画效果可以实现简单的平移、缩放、旋转。
属性动画【Property Animation】,补间动画增强版,支持对对象执行动画。
过渡动画【Transition Animation】,实现Activity或View过渡动画效果。包括5.0之后的MD过渡动画等。
动画的分类与版本
Android动画实现方式分类都可以分为xml定义和java定义。
Android 3.0之前版本,逐帧动画,补间动画 Android 3.0之后版本,属性动画 Android 4.4中,过渡动画 Android 5.0以上 MD的动画效果。
下面一起看看简单的实现吧。
逐帧动画
推荐使用一些小图片,它的性能不是很好,如果使用大图的帧动画,会出现性能问题导致卡顿。
比较常用的方式,在res/drawable目录下新建动画XML文件:

设置或清除动画代码:
//开始动画
mIvRefreshIcon.setImageResource(R.drawable.anim_loading);
mAnimationDrawable = (AnimationDrawable) mIvRefreshIcon.getDrawable();
mAnimationDrawable.start();//停止动画
mIvRefreshIcon.clearAnimation();
if (mAnimationDrawable != null){mAnimationDrawable.stop();
}
设置Background和设置ImageResource是一样的效果:

ImageView voiceIcon = new ImageView(CommUtils.getContext());
voiceIcon.setBackgroundResource(message.isSelf() ? R.drawable.right_voice : R.drawable.left_voice);
final AnimationDrawable frameAnim = (AnimationDrawable) voiceIcon.getBackground();frameAnimatio.start();MediaUtil.getInstance().setEventListener(new MediaUtil.EventListener() {@Overridepublic void onStop() {frameAnimatio.stop();frameAnimatio.selectDrawable(0);}
});
补间动画
一句话说明补间动画:只能给View加,不能给对象加,并且不会改变对象的真实属性。
无需关注每一帧,只需要定义动画开始与结束两个关键帧,并指定动画变化的时间与方式等 。主要有四种基本的效果。
透明度变化
大小缩放变化
位移变化
旋转变化
可以在xml中定义,也可以在代码中定义!
透明度的定义:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" > <alpha android:duration="1000" android:fromAlpha="0.0" android:toAlpha="1.0" />
</set>
缩放的定义:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" > <scale android:duration="1000" android:fillAfter="false" android:fromXScale="0.0" android:fromYScale="0.0" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:pivotX="50%" android:pivotY="50%" android:toXScale="1.4" android:toYScale="1.4" />
</set>
平移的定义:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" > <translate android:duration="2000" android:fromXDelta="30" android:fromYDelta="30" android:toXDelta="-80" android:toYDelta="300" />
</set>
旋转的定义:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" > <rotate android:duration="3000" android:fromDegrees="0" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:pivotX="50%" android:pivotY="50%" android:toDegrees="+350" />
</set>
Java代码中使用补间动画(推荐):
透明度定义:
AlphaAnimation alpha = new AlphaAnimation(0, 1);
alpha.setDuration(500); //设置持续时间
alpha.setFillAfter(true); //动画结束后保留结束状态
alpha.setInterpolator(new AccelerateInterpolator()); //添加差值器
ivImage.setAnimation(alpha);
缩放定义:
ScaleAnimation scale = new ScaleAnimation(1.0f, scaleXY, 1.0f, scaleXY, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
scale.setDuration(durationMillis);
scale.setFillAfter(true);
ivImage.setAnimation(scale);
平移定义:
TranslateAnimation translate = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);
translate.setDuration(durationMillis);
translate.setFillAfter(true);
ivImage.setAnimation(translate);
RotateAnimation rotate = new RotateAnimation(fromDegrees, toDegrees, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotate.setDuration(durationMillis);
rotate.setFillAfter(true);
ivImage.setAnimation(rotate);
组合Set的定义:
RelativeLayout rlRoot = (RelativeLayout) findViewById(R.id.rl_root);// 旋转动画
RotateAnimation animRotate = new RotateAnimation(0, 360,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,0.5f);
animRotate.setDuration(1000);// 动画时间
animRotate.setFillAfter(true);// 保持动画结束状态// 缩放动画
ScaleAnimation animScale = new ScaleAnimation(0, 1, 0, 1,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,0.5f);
animScale.setDuration(1000);
animScale.setFillAfter(true);// 保持动画结束状态// 渐变动画
AlphaAnimation animAlpha = new AlphaAnimation(0, 1);
animAlpha.setDuration(2000);// 动画时间
animAlpha.setFillAfter(true);// 保持动画结束状态// 动画集合
AnimationSet set = new AnimationSet(true);
set.addAnimation(animRotate);
set.addAnimation(animScale);
set.addAnimation(animAlpha);// 启动动画
rlRoot.startAnimation(set);set.setAnimationListener(new AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationRepeat(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {// 动画结束,跳转页面// 如果是第一次进入, 跳新手引导// 否则跳主页面boolean isFirstEnter = PrefUtils.getBoolean(SplashActivity.this, "is_first_enter", true);Intent intent;if (isFirstEnter) {// 新手引导intent = new Intent(getApplicationContext(),GuideActivity.class);} else {// 主页面intent = new Intent(getApplicationContext(),MainActivity.class);}startActivity(intent);finish();}
});
属性动画
补间动画增强版本。补充补间动画的一些缺点。
作用对象:任意 Java 对象,不再局限于 视图View对象。
实现的动画效果:可自定义各种动画效果,不再局限于4种基本变换:平移、旋转、缩放 & 透明度。
分为ObjectAnimator和ValueAnimator。
3.1 一个简单的属性动画
先用xml的方式实现:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"> <animator android:valueFrom="0" android:valueTo="100" android:valueType="intType" android:duration="3000" android:startOffset ="1000" android:fillBefore = "true" android:fillAfter = "false" android:fillEnabled= "true" android:repeatMode= "restart" android:repeatCount = "0" android:interpolator="@android:anim/accelerate_interpolator"/>
</set>
使用:
Button b3 = (Button) findViewById(R.id.b3);
Animator mAnim = AnimatorInflater.loadAnimator(this, R.animator.animator_1_0);
mAnim.setTarget(b3);
mAnim.start();
当然我们可以直接使用Java代码实现:
public static ObjectAnimator setObjectAnimator(View view , String type , int start , int end , long time){ ObjectAnimator mAnimator = ObjectAnimator.ofFloat(view, type, start, end); // 设置动画重复播放次数 = 重放次数+1 // 动画播放次数 = infinite时,动画无限重复 mAnimator.setRepeatCount(ValueAnimator.INFINITE); // 设置动画运行的时长 mAnimator.setDuration(time); // 设置动画延迟播放时间 mAnimator.setStartDelay(0); // 设置重复播放动画模式 mAnimator.setRepeatMode(ValueAnimator.RESTART); // ValueAnimator.RESTART(默认):正序重放 // ValueAnimator.REVERSE:倒序回放 //设置差值器 mAnimator.setInterpolator(new LinearInterpolator()); return mAnimator;
}
3.2 ValueAnimator与ObjectAnimator区别:
• ValueAnimator 类是先改变值,然后手动赋值 给对象的属性从而实现动画;是间接对对象属性进行操作;
• ObjectAnimator 类是先改变值,然后自动赋值 给对象的属性从而实现动画;是直接对对象属性进行操作;
//不同的定义方式
ValueAnimator animator = null;if (isOpen) {//要关闭if (longHeight > shortHeight) {isOpen = false;animator = ValueAnimator.ofInt(longHeight, shortHeight);}
} else {//要打开if (longHeight > shortHeight) {isOpen = true;animator = ValueAnimator.ofInt(shortHeight, longHeight);}
}animator.start();//不同的定义方式
ObjectAnimator animatorX = ObjectAnimator.ofFloat(mSplashImage, "scaleX", 1f, 2f);
animatorX.start();
3.3 监听动画的方式:
mAnim2.addListener(new AnimatorListenerAdapter() { // 向addListener()方法中传入适配器对象AnimatorListenerAdapter() // 由于AnimatorListenerAdapter中已经实现好每个接口 // 所以这里不实现全部方法也不会报错 @Override public void onAnimationCancel(Animator animation) { super.onAnimationCancel(animation); ToastUtils.showShort("动画结束了"); }
});
3.4 组合动画AnimatorSet:
xml的组合
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="sequentially" > <!--表示Set集合内的动画按顺序进行--> <!--ordering的属性值:sequentially & together--> <!--sequentially:表示set中的动画,按照先后顺序逐步进行(a 完成之后进行 b )--> <!--together:表示set中的动画,在同一时间同时进行,为默认值--> <set android:ordering="together" > <!--下面的动画同时进行--> <objectAnimator android:duration="2000" android:propertyName="translationX" android:valueFrom="0" android:valueTo="300" android:valueType="floatType" > </objectAnimator> <objectAnimator android:duration="3000" android:propertyName="rotation" android:valueFrom="0" android:valueTo="360" android:valueType="floatType" > </objectAnimator> </set> <set android:ordering="sequentially" > <!--下面的动画按序进行--> <objectAnimator android:duration="1500" android:propertyName="alpha" android:valueFrom="1" android:valueTo="0" android:valueType="floatType" > </objectAnimator> <objectAnimator android:duration="1500" android:propertyName="alpha" android:valueFrom="0" android:valueTo="1" android:valueType="floatType" > </objectAnimator> </set>
</set>
Java方式的组合
ObjectAnimator translation = ObjectAnimator.ofFloat(mButton, "translationX", curTranslationX, 300,curTranslationX); // 平移动画
ObjectAnimator rotate = ObjectAnimator.ofFloat(mButton, "rotation", 0f, 360f); // 旋转动画
ObjectAnimator alpha = ObjectAnimator.ofFloat(mButton, "alpha", 1f, 0f, 1f); // 透明度动画 // 创建组合动画的对象
AnimatorSet animSet = new AnimatorSet(); // 根据需求组合动画
animSet.play(translation).with(rotate).before(alpha);
animSet.setDuration(5000); //启动动画
animSet.start();
常用的组合方法
• AnimatorSet.play(Animator anim) :播放当前动画。
• AnimatorSet.after(long delay) :将现有动画延迟x毫秒后执行。
• AnimatorSet.with(Animator anim) :将现有动画和传入的动画同时执行。
• AnimatorSet.after(Animator anim) :将现有动画插入到传入的动画之后执行。
• AnimatorSet.before(Animator anim) :将现有动画插入到传入的动画之前执行。
3.5 Evaluator估值器
表示计算某个时间点,动画需要更新 view 的值。
Evaluator.evaluate(float fraction, T startValue, T endValue) 是核心方法。其中,fraction 表示一个百分比。startValue 和 endValue 表示动画的起始值和结束值。通过 fraction、startValue、endValue 计算 view 对应的属性位置。
常用的就那么几个:
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(animationView, "X", 0, 500);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setEvaluator(new FloatEvaluator());
objectAnimator.setDuration(5 * 1000);
objectAnimator.start();
3.6 简单Demo
实现开始隐藏在屏幕顶部,已动画的形式慢慢返回:
text.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {@TargetApi(Build.VERSION_CODES.JELLY_BEAN)@Overridepublic void onGlobalLayout() {text.getViewTreeObserver().removeOnGlobalLayoutListener(this);textHeight = text.getHeight();Log.e("tag", "textHeight: "+textHeight);//一开始需要先让text往上移动它自身的高度ViewHelper.setTranslationY(text, -textHeight);Log.e("tag", "top:"+text.getTop());//再以动画的形式慢慢滚动下拉text.animate(text).translationYBy(textHeight).setDuration(500).setStartDelay(1000).start();
属性动画设置控件的高度,实现动画关闭和打开的效果:
private boolean isOpen = false;/*** 状态的开关。上下关闭的属性动画*/private void toggle() {ValueAnimator animator = null;if (isOpen) {isOpen = false;//开启属性动画animator = ValueAnimator.ofInt(mDesHeight, 0);} else {isOpen = true;animator = ValueAnimator.ofInt(0, mDesHeight);}//动画的过程监听animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator valueAnimator) {Integer height = (Integer) valueAnimator.getAnimatedValue();mParams.height = height;llDesRoot.setLayoutParams(mParams);}});//设置动画的状态监听。给小箭头设置状态animator.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animator) {}@Overridepublic void onAnimationEnd(Animator animator) {//结束的时候,更换小箭头的图片if (isOpen){ivArrow.setImageResource(R.drawable.arrow_up);}else {ivArrow.setImageResource(R.drawable.arrow_down);}}@Overridepublic void onAnimationCancel(Animator animator) {}@Overridepublic void onAnimationRepeat(Animator animator) {}});animator.setDuration(200); //动画时间animator.start(); //启动}
属性动画讲的好乱,太多了,比较复杂。后面会有更详细的代码!
过渡动画
4.1 Android5.0以前的过渡动画
同样可以在xml中定义 ,也可以使用java代码控制。
我们在style文件夹中定义。
<!--左右进出场的activity动画-->
<style name="My_AnimationActivity" mce_bogus="1" parent="@android:style/Animation.Activity"><item name="android:activityOpenEnterAnimation">@anim/open_enter</item><item name="android:activityCloseExitAnimation">@anim/close_exit</item>
</style><!--上下进出场的activity动画-->
<style name="up_down_activity_anim" mce_bogus="1" parent="@android:style/Animation.Activity"><item name="android:activityOpenEnterAnimation">@anim/open_up</item><item name="android:activityCloseExitAnimation">@anim/close_down</item>
</style>
定义的文件如下,补间动画的方式:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"><translateandroid:duration="270"android:fromXDelta="100%p"android:toXDelta="0%p" /></set><?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"><translateandroid:duration="270"android:fromXDelta="0%p"android:toXDelta="-100%p" /></set>
对应的Activity实现指定的样式即可实现。
在Java文件中同样可以通过 overridePendingTransition 来实现。
大致实现如下:
startActivity(intent);
overridePendingTransition(R.anim.bottom_top_anim, R.anim.alpha_hide);finish();
overridePendingTransition(R.anim.alpha_show, R.anim.top_bottom_anim);
4.2 Android5.0以后的过渡动画
5.0之后,Android就自带几种动画特效。3种转场动画 ,1种共享元素。
三种转场动画如下:
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void explode(View view) {intent = new Intent(this, TransitionActivity.class);intent.putExtra("flag", 0);startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle());}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void slide(View view) {intent = new Intent(this, TransitionActivity.class);intent.putExtra("flag", 1);startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle());}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void fade(View view) {intent = new Intent(this, TransitionActivity.class);intent.putExtra("flag", 2);startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle());}
通过对面的页面来指定实现的方式:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);int flag = getIntent().getExtras().getInt("flag");switch (flag) {case 0://分解效果 上面的上面消失 下面的下面消失 分解掉了getWindow().setEnterTransition(new Explode());break;case 1://滑动效果 默认上下滑动getWindow().setEnterTransition(new Slide());break;case 2://淡出效果 透明度getWindow().setEnterTransition(new Fade());getWindow().setExitTransition(new Fade());break;case 3:break;}setContentView(R.layout.activity_transition);}
5.0的Share共享动画:
跳转的方法:
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)public void share(View view) {View fab = findViewById(R.id.fab_button);intent = new Intent(this, TransitionActivity.class);intent.putExtra("flag", 3);//创建单个共享
// startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, view, "share")
// .toBundle());//创建多个共享startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, Pair.create(view, "share"),Pair.create(fab,"fab")).toBundle());}
share的方式,不需要对方页面接收设置过渡动画,而是需要在xml中配置transitionName属性:
<Viewandroid:background="?android:colorPrimary"android:id="@+id/holder_view"android:transitionName="share"android:layout_width="match_parent"android:layout_height="300dp"/>
那边是一个button 共享名字叫“share” 那边是拿到的view 不是button 转过来定义的是view。
那边共享的是button 共享名字叫tab 共享过来也定义的button。
如果Share动画 想Share一个ViewGroup怎么办?比如一个Item跳转到Detail页面 可以直接使用这种过渡效果。
private void toActivity(View sharedElement) {Intent intent = new Intent(getContext(), TimeTableAcivity.class);ActivityOptions options =ActivityOptions.makeSceneTransitionAnimation(getActivity(), sharedElement, "shared_element_end_root");startActivity(intent, options.toBundle());
}
@Override
protected void onCreate(Bundle savedInstanceState) {getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);findViewById(android.R.id.content).setTransitionName("shared_element_end_root");setEnterSharedElementCallback(new MaterialContainerTransformSharedElementCallback());getWindow().setSharedElementEnterTransition(buildContainerTransform(true));getWindow().setSharedElementReturnTransition(buildContainerTransform(false));super.onCreate(savedInstanceState);
}private MaterialContainerTransform buildContainerTransform(boolean entering) {MaterialContainerTransform transform = new MaterialContainerTransform(this, entering);transform.setAllContainerColors(MaterialColors.getColor(findViewById(android.R.id.content), R.attr.colorSurface));transform.addTarget(android.R.id.content);//设置动画持续时间(毫秒)transform.setDuration(666);return transform;
}
5.0之后在MD中还有其他的动画,比如揭露动画,不知道算不算转场动画的一种。因为一般也是用于转场的时候使用,但是这个动画我们使用的很少很少。
简单的使用如下:
View myView = findView(R.id.awesome_card);int cx = (myView.getLeft() + myView.getRight()) / 2;
int cy = (myView.getTop() + myView.getBottom()) / 2;int dx = Math.max(cx, myView.getWidth() - cx);
int dy = Math.max(cy, myView.getHeight() - cy);
float finalRadius = (float) Math.hypot(dx, dy);Animator animator =ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.setDuration(1500);
animator.start();
这些动画虽然牛皮,但是记得5.0以上才生效的哦,同时我们也不能看着什么动画炫酷都想上,转场动画也是在主线程执行的,如果定义不当也会造成卡顿的。
异步动画
在子线程中执行动画?我懂了,看我操作!
Thread {val animatorscaleX = ObjectAnimator.ofFloat(mBinding.ivAnim, "scaleX", 2f)val animatorscaleY = ObjectAnimator.ofFloat(mBinding.ivAnim, "scaleY", 2f)val animatortranslationX = ObjectAnimator.ofFloat(mBinding.ivAnim, "translationX", 200f)val animatortranslationY = ObjectAnimator.ofFloat(mBinding.ivAnim, "translationY", 200f)val set = AnimatorSet()set.setDuration(1000).play(animatorscaleX).with(animatorscaleY).with(animatortranslationX).with(animatortranslationY)set.start()}.start()
开个线程,执行属性动画。so easy! 等等,怎么写个属性动画这么多代码,修改一下,优雅一点,同样的效果一行代码解决。
Thread {mBinding.ivAnim.animate().scaleX(2f).scaleY(2f).translationX(200f).translationY(200f).setDuration(1000).start()
}.start()
运行居然报错?不能运行在没有looper的子线程?哦...我懂了,子线程不能更新UI来着。
到此就引出一个经典面试题,子线程真的不能更新UI吗?当然可以更新UI了。看我操作!
public class MyLooperThread extends Thread {// 子线程的looperprivate Looper myLooper;// 子线程的handlerprivate Handler mHandler;// 用于测试的textviewprivate TextView testView;private Activity activity;public Looper getLooper() {return myLooper;}public Handler getHandler() {return mHandler;}public MyLooperThread(Context context, TextView view) {this.activity = (Activity) context;testView = view;}@Overridepublic void run() {super.run();// 调用了此方法后,当前线程拥有了一个looper对象Looper.prepare();YYLogUtils.w("消息循环开始");if (myLooper == null) {while (myLooper == null) {try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}// 调用此方法获取当前线程的looper对象myLooper = Looper.myLooper();}}// 当前handler与当前线程的looper关联mHandler = new Handler(myLooper) {@Overridepublic void handleMessage(Message msg) {YYLogUtils.w("处理消息:" + msg.obj);//此线程,此Looper创建的ui可以随便修改addTextViewInChildThread().setText(String.valueOf(msg.obj));//发现跟ui创建的位置有关。如果ui是在main线程创建的,则在子线程中不可以更改此ui;// 如果在含有looper的子线程中创建的ui,则可以任意修改// 这里传进来的是主线程的ui,不能修改!低版本可能可以修改//CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
// try {
// if (testView != null) {
// testView.setText(String.valueOf(msg.obj));
// }
// } catch (Exception e) {
// e.printStackTrace();
//
// }}};Looper.loop();YYLogUtils.w("looper消息循环结束,线程终止");}/*** 创建TextView*/private TextView addTextViewInChildThread() {TextView textView = new TextView(activity);textView.setBackgroundColor(Color.GRAY); //背景灰色textView.setGravity(Gravity.CENTER); //居中展示textView.setTextSize(20);WindowManager windowManager = activity.getWindowManager();WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,WindowManager.LayoutParams.WRAP_CONTENT,0, 0,WindowManager.LayoutParams.FIRST_SUB_WINDOW,WindowManager.LayoutParams.TYPE_TOAST,PixelFormat.TRANSPARENT);windowManager.addView(textView, params);return textView;}
}
我们需要定义线程,然后准备Looper,并创建内部的Handler处理数据。我们内部线程创建TextView,我们发送handle消息创建textview并赋值。
val looperThread = MyLooperThread(this, mBinding.tvRMsg)looperThread.start()mBinding.ivAnim.click {looperThread.handler.obtainMessage(200, "test set tv'msg").sendToTarget()}
正常显示子线程创建的textview,但是我们传入线程对象的tvRMsg是不能在子线程赋值的,会报错:
CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
结论:如果ui是在main线程创建的,则在子线程中不可以更改此ui;如果在含有looper的子线程中创建的ui,则可以任意修改。
既然子线程都可以更新UI了,那么子线程执行动画行不行?当然行!
我们直接修改代码:
val looperThread = MyLooperThread(this, mBinding.tvRMsg)
looperThread.start()mBinding.ivAnim.click {//试试子线程执行动画看看looperThread.handler.post {mBinding.ivAnim.animate().scaleX(2f).scaleY(2f).translationX(200f).translationY(200f).setDuration(1000).start()}}
完美运行!
其实官方早有说明,RenderThread 中运行动画。其实我们上面的Thread类就是仿 HandlerThread 来写的。我们可以使用 HandlerThread 很方便的实现子线程动画。具体的使用方式和我们自定义的 Thread 类似。
我们可以基于系统类 HandlerThread 封装一个异步动画工具类:
class AsynAnimUtil private constructor() : LifecycleObserver {private var mHandlerThread: HandlerThread? = HandlerThread("anim_run_in_thread")private var mHandler: Handler? = mHandlerThread?.run {start()Handler(this.looper)}private var mOwner: LifecycleOwner? = nullprivate var mAnim: ViewPropertyAnimator? = nullcompanion object {val instance: AsynAnimUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {AsynAnimUtil()}}//启动动画fun startAnim(owner: LifecycleOwner?, animator: ViewPropertyAnimator) {try {if (mOwner != owner) {mOwner = owneraddLoopLifecycleObserver()}if (mHandlerThread?.isAlive != true) {YYLogUtils.w("handlerThread restart")mHandlerThread = HandlerThread("anim_run_in_thread")mHandler = mHandlerThread?.run {start()Handler(this.looper)}}mHandler?.post {mAnim = animator.setListener(object : AnimatorListenerAdapter() {override fun onAnimationEnd(animation: Animator?) {super.onAnimationEnd(animation)destory()}override fun onAnimationCancel(animation: Animator?) {super.onAnimationCancel(animation)destory()}override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {super.onAnimationEnd(animation, isReverse)destory()}})mAnim?.start()}} catch (e: Exception) {e.printStackTrace()}}// 绑定当前页面生命周期private fun addLoopLifecycleObserver() {mOwner?.lifecycle?.addObserver(this)}@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)fun onDestroy() {YYLogUtils.i("AsynAnimUtil Lifecycle -> onDestroy")mAnim?.cancel()destory()}private fun destory() {YYLogUtils.w("handlerThread quit")try {mHandlerThread?.quitSafely()mAnim = nullmOwner = nullmHandler = nullmHandlerThread = null} catch (e: Exception) {e.printStackTrace()}}}
使用的时候就可以直接拿工具类来进行异步动画。
mBinding.ivAnim.click {//试试HandlerThread执行动画val anim = mBinding.ivAnim.animate().scaleX(2f).scaleY(2f).translationXBy(200f).translationYBy(200f).setDuration(2000)AsynAnimUtil.instance.startAnim(this, anim)}
Ok,完美运行。这里注意需要传入LifecycleOwner 为了在当前页面关闭的时候及时的停止动画释放资源。
相关文章:

Android 动画详解
Android动画的分类与使用学习Android必不可少的就是动画的使用了,在Android版本迭代的过程中,出现了很多动画框架,这里做一个总结。Android动画类型分类逐帧动画【Frame Animation】,即顺序播放事先准备的图片。补间动画【Tween A…...

Linux -- 程序 进程 线程 概念引入
程序与进程 :程序 :什么是程序 ???伪官方 : 二进制文件,文件存储在磁盘中,例如 /usr/bin 目录下 。 是静态。 简单讲 :# 我们都学习了语言,比如下面这串代…...

Android ART dex2oat
一、什么是dex2oat Dex2oat (dalvik excutable file to optimized art file) ,是一个对 dex 文件进行编译优化的程序,在我们的 Android 手机中的位置是 /system/bin/dex2oat,对应的源码路径为 android/art/dex2oat/dex2oat.cc,通…...

「RISC-V Arch」RISC-V 规范结构
日期:20230228 规范分类 根据 RISC-V 设计哲学,其规范文档也是高度模块化的: ISA 规范(2 篇) 非特权规范特权规范 非 ISA 规范(6篇) Trace规范ABI 规范外部调试规范PLIC 规范SBI 规范UEFI 协…...
【C】线程控制
创建线程 #include <pthread.h>int pthread_create(pthread_t * thread,const pthread_attr_t * attr,void *(*start_routine)(void*), void * arg);返回值:成功返回0,失败返回错误号。 thread:成功返回后,新创建的线程的…...

Maven工程打jar包的N种方式
Maven工程打jar包 一、IDEA自带打包插件二、maven插件打包2.1 制作瘦包(直接打包,不打包依赖包)2.2 制作瘦包和依赖包(相互分离)2.3 制作胖包(项目依赖包和项目打为一个包)2.4 制作胖包…...

一文了解GPU并行计算CUDA
了解GPU并行计算CUDA一、CUDA和GPU简介二、GPU工作原理与结构2.1、基础GPU架构2.2、GPU编程模型2.3、软件和硬件的对应关系三、GPU应用领域四、GPUCPU异构计算五、MPI与CUDA的区别一、CUDA和GPU简介 CUDA(Compute Unified Device Architecture)…...

全网资料最全Java数据结构与算法(1)
一、数据结构和算法概述 1.1什么是数据结构? 官方解释: 数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及他们之间的关系和操作等相关问题的学科。 大白话: 数据结构就是把数据元素按照一定的关系组织起来的集合&a…...
【项目实战】SpringMVC拦截器HandlerInterceptor入门介绍
一、拦截器介绍 拦截器是应用程序级框架中常用的拦截用户请求、实施业务流程控制的模式,它可以将一些公共的、重复发生的业务逻辑从业务处理代码中独立出来,使系统的结构更加清晰,程序的复杂度也减小了。 拦截器是一个常见的特性,它可以实现任何自定义功能,而无需调整业…...

阿里淘宝新势力造型合伙人P8、年薪百万的欧阳娜娜也躲不过的魔鬼面试,看的我心服口服
阿里淘宝新势力造型合伙人P8、年薪百万的欧阳娜娜跳槽了,这不是关键。 她参加了网易有道明星语音录音员/代言人的面试,这也不是关键。 关键是她教科书式的面试过程,狠狠地给我们上了一课。 我是无意间刷到的这个视频的时候,就一…...

深度学习笔记:不同的反向传播迭代方法
1 随机梯度下降法SGD 随机梯度下降法每次迭代取梯度下降最大的方向更新。这一方法实现简单,但是在很多函数中,梯度下降的方向不一定指向函数最低点,这使得梯度下降呈现“之”字形,其效率较低 class SGD:"""随机…...

ElasticSearch 学习笔记总结(三)
文章目录一、ES 相关名词 专业介绍二、ES 系统架构三、ES 创建分片副本 和 elasticsearch-head插件四、ES 故障转移五、ES 应对故障六、ES 路由计算 和 分片控制七、ES集群 数据写流程八、ES集群 数据读流程九、ES集群 更新流程 和 批量操作十、ES 相关重要 概念 和 名词十一、…...

深入理解border以及应用
深入border属性以及应用👏👏 border这个属性在开发过程中很常用,常常用它来作为边界的。但是大家真的了解border吗?以及它的形状是什么样子的。 我们先来看这样一段代码:👏 <!--* Author: syk 185901…...
如何复现论文?什么是论文复现?
参考资料: 学习篇—顶会Paper复现方法 - 知乎 如何读论文?复现代码?_复现代码是什么意思 - CSDN 我是如何复现我人生的第一篇论文的 - 知乎 在我看来,论文复现应该有一个大前提和分为两个层次。 大前提是你要清楚地懂得自己要…...
22.2.28打卡 Codeforces Round #851 (Div. 2) A~C
A题 One and Two 题面翻译 题目描述 给你一个数列 a1,a2,…,ana_1, a_2, \ldots, a_na1,a2,…,an . 数列中的每一个数的值要么是 111 要么是 222 . 找到一个最小的正整数 kkk,使之满足: 1≤k≤n−11 \leq k \leq n-11≤k≤n−1 , anda1⋅a2⋅……...

Learining C++ No.12【vector】
引言: 北京时间:2023/2/27/11:42,高数考试还在进行中,我充分意识到了学校的不高级,因为题目真的没什么意思,虽然挺平易近人,但是……,考试期间时间比较放松,所以不能耽误…...

【数电基础】——逻辑代数运算
目录 1.概念 1.基本逻辑概念 2.基本逻辑电路(与或非) 逻辑与运算 与门电路: 逻辑或运算 或门电路: 逻辑非运算(逻辑反) 非门电路编辑 3.复合逻辑电路(运算) 与非逻辑…...

【Redis】什么是缓存与数据库双写不一致?怎么解决?
1. 热点缓存重建 我们以热点缓存 key 重建来一步步引出什么是缓存与数据库双写不一致,及其解决办法。 1.1 什么是热点缓存重建 在实际开发中,开发人员使用 “缓存 过期时间” 的策略来实现加速数据读写和内存使用率,这种策略能满足大多数…...

互联网衰退期,测试工程师35岁之路怎么走...
国内的互联网行业发展较快,所以造成了技术研发类员工工作强度比较大,同时技术的快速更新又需要员工不断的学习新的技术。因此淘汰率也比较高,超过35岁的基层研发类员工,往往因为家庭原因、身体原因,比较难以跟得上工作…...

动态规划(以背包问题为例)
1) 要求达到的目标为装入的背包的总价值最大,并且重量不超出2) 要求装入的物品不能重复动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法。动态规划算法与分治算法类似ÿ…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

自然语言处理——文本分类
文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益(IG) 分类器设计贝叶斯理论:线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别, 有单标签多类别文本分类和多…...
LangChain【6】之输出解析器:结构化LLM响应的关键工具
文章目录 一 LangChain输出解析器概述1.1 什么是输出解析器?1.2 主要功能与工作原理1.3 常用解析器类型 二 主要输出解析器类型2.1 Pydantic/Json输出解析器2.2 结构化输出解析器2.3 列表解析器2.4 日期解析器2.5 Json输出解析器2.6 xml输出解析器 三 高级使用技巧3…...