Android 动画详解

news/2024/4/26 8:37:47/文章来源:https://blog.csdn.net/qq_39312146/article/details/129240637

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 为了在当前页面关闭的时候及时的停止动画释放资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_75246.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NSGA-Ⅲ源代码

NSGA-Ⅲ源代码如下&#xff0c;供大家学习和应用。该算法在梯级水电-火电的应用订阅专栏即可查看&#xff1a; 1、主函数 % % Copyright (c) 2016, Mostapha Kalami Heris & Yarpiz (www.yarpiz.com) % All rights reserved. Please read the "LICENSE" file…

前端经典react面试题及答案

为什么 React 元素有一个 $$typeof 属性 目的是为了防止 XSS 攻击。因为 Synbol 无法被序列化&#xff0c;所以 React 可以通过有没有 $$typeof 属性来断出当前的 element 对象是从数据库来的还是自己生成的。 如果没有 $$typeof 这个属性&#xff0c;react 会拒绝处理该元素。…

C++plog库,轻量级日志框架(日志库)

文章目录主要头文件及使用方法解释plog/Log.hplog/Appenders/ColorConsoleAppender.hplog/Appenders/RollingFileAppender.hplog/Formatters/TxtFormatter.h三个核心概念Formatter&#xff1a;格式化程序格式化程序举例TxtFormatterJsonFormatterCsvFormatterSyslogFormatterAp…

真的,MyBatis 核心源码入门看这个就够了(四)

4 SQL执行 再次回到demo的示例代码&#xff0c; org.junit.jupiter.api.Testvoid queryBySn() throws IOException {//1 读取配置文件InputStream in Resources.getResourceAsStream("mybatis-config.xml");//2 加载解析配置文件并获取SqlSessionFactory对象SqlSes…

华为OD机试模拟题 用 C++ 实现 - 寻找连续区间(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 最多获得的短信条数(2023.Q1)) 文章目录 最近更新的博客使用说明寻找连续区间题目输入输出示例一输入输出说明示例二输入输出Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率…

华为OD机试题,用 Java 解【求解连续数列】问题

最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…

大数据算法自检

1 大数据亚线性空间算法 1.1 流模型的计数问题 问题定义&#xff1f;用什么算法&#xff1f;算法步骤&#xff1f;(提示&#xff1a;三层递进) 切比雪夫不等式&#xff1f;怎么证明&#xff1f;期望&#xff0c;方差&#xff0c;空间复杂度&#xff1f; 极其有限的空间存储极…

数据结构与算法(六):图结构

图是一种比线性表和树更复杂的数据结构&#xff0c;在图中&#xff0c;结点之间的关系是任意的&#xff0c;任意两个数据元素之间都可能相关。图是一种多对多的数据结构。 一、基本概念 图&#xff08;Graph&#xff09;是由顶点的有穷非空集合和顶点之间边的集合组成&#x…

每日分享(免登录积分商城系统 动力商城 兑换商城源码)

​demo软件园每日更新资源,请看到最后就能获取你想要的: 1.Python教程2022&#xff1a;100天从新手到大师 完整版 Python 100天从新手到大师是一个Python入门教程&#xff0c;Python从入门到精通&#xff0c;专门为热爱python的新手量身定做的学习计划&#xff0c;100天速成pyt…

OOM的俩种情况---主动kill/被动kill

出现OOM, 有两种处理方式&#xff1a;1. 主动Kill; 2. 被动Kill 例&#xff1a;HBase Region Server OOM定位问题复盘 现象 在HBase资源隔离项目中&#xff0c;对测试集群进行压测时&#xff0c;发现region server会出现崩溃的情况&#xff0c;单机请求量从>200到~50每秒都…

【Git使用教程】从入门到学废

文章目录1. 基础git流程图常用命令基本配置快捷指令解决GitBash乱码获取本地仓库基础操作指令查看修改的状态&#xff08;status&#xff09;添加工作区到暂存区(add)提交暂存区到本地仓库(commit)查看提交日志(log)版本回退添加文件至忽略列表总结2. 分支查看本地分支创建本地…

程序员多赚20k的接私活必备网站

为什么都是程序员&#xff0c;就有人能多赚20k&#xff1f;那是因为副业搞得那么溜啊&#xff01; 今天分享一些程序员搞钱必备的接私活网站&#xff0c;让更多程序员们在工作之余能有另外一份收入。 1.程序员客栈&#xff1a;http://proginn.com 专为程序员服务的软件外包对…

超级品牌符号怎么设计?大咖有方法

怎么设计超级LOGO图标&#xff1f;有方法&#xff01; LOGO设计大趋势&#xff1a;卡通化、拟人化 抽象符号已经泛滥 但卡通形象也已经泛滥 趣讲大白话&#xff1a;设计容易出名难 【安志强趣讲信息科技89期】 ******************************* 别以为设计一个卡通就牛X闪闪 比…

React Native使用echart——wrn-echarts

这里写自定义目录标题前言Tips详细使用过程如下1、开发环境搭建2、准备RN工程3、build App包4、 安装相关依赖5、试用Skia模式6、试用Svg模式7、封装Chart组件8、多个图表使用总结前言 平时写图表相关需求&#xff0c;用得最多的图表库就是echarts。echarts在web端的表现已经相…

机器学习:基于朴素贝叶斯对花瓣花萼的宽度和长度分类预测

机器学习&#xff1a;基于朴素贝叶斯对花瓣花萼的宽度和长度分类预测 作者&#xff1a;AOAIYI 作者简介&#xff1a;Python领域新星作者、多项比赛获奖者&#xff1a;AOAIYI首页 &#x1f60a;&#x1f60a;&#x1f60a;如果觉得文章不错或能帮助到你学习&#xff0c;可以点赞…

毕业设计 基于51单片机环境监测设计 光照 PM2.5粉尘 温湿度 2.4G无线通信

基于51单片机环境监测设计 光照 PM2.5粉尘 温湿度 2.4G无线通信1、项目简介1.1 系统构成1.2 系统功能2、部分电路设计2.1 STC89C52单片机核心系统电路设计2.2 dht11温湿度检测电路设计2.3 NRF24L01无线通信电路设计3、部分代码展示3.1 NRF24L01初始化3.2 NRF24L01的SPI写时序3.…

【数据库】数据库基本概念和类型

一、数据库基本概念 1、数据 所谓数据&#xff08;Data&#xff09;是指对客观事物进行描述并可以鉴别的符号&#xff0c;这些符号是可识别的、抽象的。它不仅仅指狭义上的数字&#xff0c;而是有多种表现形式&#xff1a;字母、文字、文本、图形、音频、视频等。现在…

STM32开发(16)----CubeMX配置DMA

CubeMX配置DMA前言一、什么是DMA&#xff1f;二、实验过程1.CubeMX配置2.代码实现3.实验结果总结前言 本章介绍使用STM32CubeMX对DMA进行配置的方法&#xff0c;DMA的原理、概念和特点&#xff0c;配置各个步骤的功能&#xff0c;并通过串口DMA传输实验方式验证。 一、什么是…

【学习笔记汇总】Github Note

本科毕业设计 Internet of Things environmental monitoring system based on STM32 STM32系列单片机工程模板 【STM32F103_Libary】基于STM32F103开发板的工程模板 ST7735屏幕【STM32F103Template】基于STM32F103开发板的工程模板 ILI9341屏幕【STM32F103_LibaryFinalVersio…

服务拆分及远程调用

目录 服务拆分 服务拆分注意事项 服务间调用 步骤一&#xff1a;注册RestTemplate 步骤二&#xff1a;修改业务层代码 总结&#xff1a; 提供者和消费者 思考 服务调用关系 服务拆分 服务拆分注意事项 单一职责&#xff1a;不同微服务&#xff0c;不要重复开发相同业…