当前位置: 首页 > news >正文

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必不可少的就是动画的使用了&#xff0c;在Android版本迭代的过程中&#xff0c;出现了很多动画框架&#xff0c;这里做一个总结。Android动画类型分类逐帧动画【Frame Animation】&#xff0c;即顺序播放事先准备的图片。补间动画【Tween A…...

Linux -- 程序 进程 线程 概念引入

程序与进程 &#xff1a;程序 &#xff1a;什么是程序 &#xff1f;&#xff1f;&#xff1f;伪官方 &#xff1a; 二进制文件&#xff0c;文件存储在磁盘中&#xff0c;例如 /usr/bin 目录下 。 是静态。 简单讲 &#xff1a;# 我们都学习了语言&#xff0c;比如下面这串代…...

Android ART dex2oat

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

「RISC-V Arch」RISC-V 规范结构

日期&#xff1a;20230228 规范分类 根据 RISC-V 设计哲学&#xff0c;其规范文档也是高度模块化的&#xff1a; ISA 规范&#xff08;2 篇&#xff09; 非特权规范特权规范 非 ISA 规范&#xff08;6篇&#xff09; 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);返回值&#xff1a;成功返回0&#xff0c;失败返回错误号。 thread&#xff1a;成功返回后&#xff0c;新创建的线程的…...

Maven工程打jar包的N种方式

Maven工程打jar包 一、IDEA自带打包插件二、maven插件打包2.1 制作瘦包&#xff08;直接打包&#xff0c;不打包依赖包&#xff09;2.2 制作瘦包和依赖包&#xff08;相互分离&#xff09;2.3 制作胖包&#xff08;项目依赖包和项目打为一个包&#xff09;2.4 制作胖包&#xf…...

一文了解GPU并行计算CUDA

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

全网资料最全Java数据结构与算法(1)

一、数据结构和算法概述 1.1什么是数据结构&#xff1f; 官方解释&#xff1a; 数据结构是一门研究非数值计算的程序设计问题中的操作对象&#xff0c;以及他们之间的关系和操作等相关问题的学科。 大白话&#xff1a; 数据结构就是把数据元素按照一定的关系组织起来的集合&a…...

【项目实战】SpringMVC拦截器HandlerInterceptor入门介绍

一、拦截器介绍 拦截器是应用程序级框架中常用的拦截用户请求、实施业务流程控制的模式,它可以将一些公共的、重复发生的业务逻辑从业务处理代码中独立出来,使系统的结构更加清晰,程序的复杂度也减小了。 拦截器是一个常见的特性,它可以实现任何自定义功能,而无需调整业…...

阿里淘宝新势力造型合伙人P8、年薪百万的欧阳娜娜也躲不过的魔鬼面试,看的我心服口服

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

深度学习笔记:不同的反向传播迭代方法

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

ElasticSearch 学习笔记总结(三)

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

深入理解border以及应用

深入border属性以及应用&#x1f44f;&#x1f44f; border这个属性在开发过程中很常用&#xff0c;常常用它来作为边界的。但是大家真的了解border吗&#xff1f;以及它的形状是什么样子的。 我们先来看这样一段代码&#xff1a;&#x1f44f; <!--* Author: syk 185901…...

如何复现论文?什么是论文复现?

参考资料&#xff1a; 学习篇—顶会Paper复现方法 - 知乎 如何读论文&#xff1f;复现代码&#xff1f;_复现代码是什么意思 - CSDN 我是如何复现我人生的第一篇论文的 - 知乎 在我看来&#xff0c;论文复现应该有一个大前提和分为两个层次。 大前提是你要清楚地懂得自己要…...

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&#xff0c;使之满足&#xff1a; 1≤k≤n−11 \leq k \leq n-11≤k≤n−1 , anda1⋅a2⋅……...

Learining C++ No.12【vector】

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

【数电基础】——逻辑代数运算

目录 1.概念 1.基本逻辑概念 2.基本逻辑电路&#xff08;与或非&#xff09; 逻辑与运算 与门电路&#xff1a; 逻辑或运算 或门电路&#xff1a; ​逻辑非运算&#xff08;逻辑反&#xff09; 非门电路​编辑 3.复合逻辑电路&#xff08;运算&#xff09; 与非逻辑…...

【Redis】什么是缓存与数据库双写不一致?怎么解决?

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

互联网衰退期,测试工程师35岁之路怎么走...

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

动态规划(以背包问题为例)

1) 要求达到的目标为装入的背包的总价值最大&#xff0c;并且重量不超出2) 要求装入的物品不能重复动态规划(Dynamic Programming)算法的核心思想是&#xff1a;将大问题划分为小问题进行解决&#xff0c;从而一步步获取最优解的处理算法。动态规划算法与分治算法类似&#xff…...

Java异常

异常的体系结构 在java的Throwable下有Error和Exception两个子类 Error(错误):程序运行中出现了严重的问题,非代码性错误,无法处理,常见的有虚拟机运行错误和内存溢出等Exception(异常):是由于代码本身造成的问题,可以进行处理,异常一个可以分为运行时异常和编译时异常 运行…...

别克GL8改装完工,一起来看看效果

①豪华商务头等舱 别克GL8作为商务车&#xff0c;不管是家用还是商务接待&#xff0c;原车内饰都太掉档次了&#xff0c;所以车主要求全部换掉。>>织布座椅换成航空座椅 主副驾&#xff1a;改装纳帕皮 中排&#xff1a;改装水晶宝座豪华版航空座椅&#xff0c;带通风、加…...

mac 中 shell 一些知识

mac 设置环境变量首先得看你所使用的 shell shell 是一个命令行解释器&#xff0c;顾名思义就是机器外面的一层壳&#xff0c;用于人机交互&#xff0c;只要是人与电脑之间交互的接口&#xff0c;就可以称为 shell。表现为其作用是用户输入一条命令&#xff0c;shell 就立即解…...

CentOS 配置FTP(开启VSFTPD服务)

网上已经有很多关于VSFTPD的配置&#xff0c;但有两个通病&#xff0c;要么就是原理介绍太多&#xff0c;要么就是不完整&#xff0c;操作下来又要查询多篇文章才能用。 我这里不讲原理&#xff0c;只记录操作&#xff0c;尽可能通过复制下面的操作可以实现FTP读写功能。方便自…...

Http的请求方法

Http的请求方法对应的数据传输能力把Http请求分为Url类请求和Body类请求 1.Url类请求包括但不限于GET、HEAD、OPTIONS、TRACE 等请求方法 2.Body类请求包括但不限于POST、PUSH、PATCH、DELETE 等请求方法。 3.原因&#xff1a;get请求没有请求体&#xff08;好像也可以…...

Python字典-- 内附蓝桥题:统计数字

字典 ~~不定时更新&#x1f383;&#xff0c;上次更新&#xff1a;2023/02/28 &#x1f5e1;常用函数&#xff08;方法&#xff09; 1. dic.get(key) --> 判断字典 dic 是否有 key&#xff0c;有返回其对应的值&#xff0c;没有返回 None 举个栗子&#x1f330; dic …...

文本处理工具

Grep工具的基本使用grep作用&#xff1a;grep是行过滤工具&#xff1b;用于根据关键字进行行过滤提示&#xff1a;通过alias命令设置grep别名&#xff0c;搜索参数时带颜色显示alias grepgrep colorauto 命令语法格式&#xff1a;grep [选项] 参数 文件名grep命令选项&#xff…...

C++STL详解(三)——vector的介绍和使用

文章目录vector的介绍vector的使用vector的定义方式vector的空间增长问题reserve和resizevector的迭代器使用begin 和endrbegin和rendinsert 和erasefind函数元素访问vector迭代器失效问题1&#xff1a;inserse插入扩容时空间销毁造成野指针问题2&#xff1a;erase删除或者inse…...

GEBCO海洋数据下载

一、数据集简介 GEBCO&#xff08;General Bathymetric chart of the Oceans&#xff09;旨在为世界海洋提供最权威的、可公开获取的测深数据集。 目前的网格化测深数据集&#xff0c;即GEBCO_2022网格&#xff0c;是一个全球海洋和陆地的地形模型&#xff0c;在15角秒间隔的…...

【C++容器】vector、map、hash_map、unordered_map四大容器的性能分析【2023.02.28】

摘要 vector是标准容器对数组的封装&#xff0c;是一段连续的线性的内存。map底层是二叉排序树。hash_map是C11之前的无序map&#xff0c;unordered_map底层是hash表&#xff0c;涉及桶算法。现对各个容器的查询与”插入“性能做对比分析&#xff0c;方便后期选择。 测试方案…...

ACM-蓝桥杯训练第一周

&#x1f680;write in front&#x1f680; &#x1f4dd;个人主页&#xff1a;认真写博客的夏目浅石.CSDN &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​ &#x1f4e3;系列专栏&#xff1a;ACM周训练题目合集.CSDN &#x1f4ac;总结&#xff1a…...

python基础—字符串操作

&#xff08;1&#xff09;字符串&#xff1a; Python内置了一系列的数据类型&#xff0c;其中最主要的内置类型是数值类型、文本序列&#xff08;字符串&#xff09;类型、序列&#xff08;列表、元组和range&#xff09;类型、集合类型、映射&#xff08;字典&#xff09;类型…...

【Spring】通过JdbcTemplate实现CRUD操作

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 通过JdbcTemplate实现 增删查改一、添加相关依…...

实战|掌握Linux内存监视:free命令详解与使用技巧

文章目录前言一. free命令介绍二. 语法格式及常用选项三. 参考案例3.1 查看free相关的信息3.2 以MB的形式显示内存的使用情况3.3 以总和的形式显示内存的使用情况3.4 周期性的查询内存的使用情况3.5 以更人性化的形式来查看内存的结果输出四. free在脚本中的应用总结前言 大家…...

嵌入式入门必看!调试工具安装——基于 AM64x核心板

本章节内容是为评估板串口安装USB转串口驱动程序。驱动适用于CH340、CH341等USB转串口芯片。 USB转串口驱动安装 适用安装环境:Windows 7 64bit、Windows 10 64bit。 本文测试板卡为创龙科技SOM-TL64x核心板,它是一款基于TI Sitara系列AM64x双核ARM Cortex-A53 + 单/四核Cort…...

JAVA开发(java类加载过程)

1、java语言的平台无关性。 因为java语言可以跑在java虚拟机上&#xff0c;所以只要能装java虚拟机的地方就能跑java程序。java语言以后缀名 .java为文件扩展名。通过java编译器javac编译成字节码文件.class 。java字节码文件通过java虚拟机解析运行。所以java语言可以说是编译…...

【vulhub漏洞复现】Thinkphp 2.x 任意代码执行

一、漏洞详情影响版本 thinkphp 2.x但是由于thinkphp 3.0版本在Lite模式下没有修复该漏洞&#xff0c;所以也存在该漏洞漏洞原因&#xff1a;e 和 /e模式匹配路由&#xff1a;e 配合函数preg_replace()使用, 可以把匹配来的字符串当作正则表达式执行; /e 可执行模式&#xff0c…...

LeetCode 1145. 二叉树着色游戏 -- 简单搜索

二叉树着色游戏 提示 中等 199 相关企业 有两位极客玩家参与了一场「二叉树着色」的游戏。游戏中&#xff0c;给出二叉树的根节点 root&#xff0c;树上总共有 n 个节点&#xff0c;且 n 为奇数&#xff0c;其中每个节点上的值从 1 到 n 各不相同。 最开始时&#xff1a; 「一…...

HyperGBM的三种Early Stopping方式

本文作者&#xff1a;杨健&#xff0c;九章云极 DataCanvas 主任架构师 很多机器学习框架如都提供了Early Stopping策略&#xff0c;主要用来防止模型过拟合。和模型训练提前停止的目标不同&#xff0c;AutoML的Early Stopping策略更多考虑的是算力消耗和模型质量的平衡。 通…...

心系区域发展,高德用一体化出行服务平台“聚”力区域未来

交通&#xff0c;是城市的血脉。通过对人、资源、产业的连接&#xff0c;交通建设往往是城市和区域经济发展的前提。不过&#xff0c;在度过了“要想富&#xff0c;先修路”的初级建设阶段后&#xff0c;交通产业内部也出现了挑战&#xff0c;诸如城市秩序、发展成本、用户使用…...

AI画图_stable-diffusion-webui安装使用指南(1)

本文章适用于: 有一定学习能力和钻研能力&#xff0c;遇到问题能合理使用搜索引擎尝试解决问题的人想在windows系统中尝试使用AI作画工具stable-diffusion-webui进行绘画的人有一定的计算机基础&#xff08;会魔法上网、知道 python和Git&#xff09;和英文阅读能力的人显卡为…...

浅谈MySQL主从复制

目录 1.MySQL主从复制是什么 2.MySQL主从复制的意义 3.MySQL主从复制原理 4.数据同步一致性问题 5.实现方式 1.MySQL主从复制是什么 MySQL主从复制就是指数据可以从一台MySQL的主节点复制到一个或多个从节点。 MySQL默认采用异步复制方式&#xff0c;这样从节点不用一直访…...

docker-compose安装kafka和php简单测试

docker-compose.yml内容&#xff1a; version: 3.1 services: zookeeper: container_name: zookeeper image: zookeeper:3.6 ports: - 2181:2181 kafka: image: wurstmeister/kafka container_name: kafka depends_on: - zookeeper …...

【蓝桥云课】快速幂

问题描述&#xff1a;快速求aba^bab 方法一&#xff1a;常规方法相乘a∗a∗a∗a∗...∗aa*a*a*a*...*aa∗a∗a∗a∗...∗a 方法二&#xff1a;分治方法求aba^bab ab{1,b0a,b1ab2⋅ab2,b为偶数ab−12⋅ab12,b为奇数a^b\begin{cases} 1& \text{,b0}\\ a& \text{,b1}\\ a…...

解决windows安装wxPython安装失败、速度过慢及PyCharm上wx包爆红问题

网上关于wxPython安装失败&#xff0c;安装速度过慢&#xff0c;以及安装成功后PyCharm中import wx仍然爆红的文章有很多&#xff0c;也特别杂&#xff0c;解决起来特别困难&#xff0c;今天在这里对问题的处理进行一个整合&#xff0c;希望能帮助到大家。 安装wxPython这里运用…...

封装小程序request请求[接口函数]

在这篇小程序API的Promise化文章中讲到小程序官方提供的异步API都是基于回调函数来实现的&#xff0c;在大量的使用这种回调函数就会造成回调地狱的问题&#xff0c;以及代码的可读性和可维护性差&#xff0c;通过对小程序API的Promise化能解决&#xff0c;那么本篇是来讲进行对…...

嵌入式 STM32 通讯协议--MODBUS

目录 一、自定义通信协议 1、协议介绍 2、网络协议 3、自定义的通信协议 二、MODBUS通信协议 1、概述 2、MODBUS帧结构 协议描述 3、MODBUS数据模型 4、MODBUS事务处理的定义 5、MODBUS功能码 6、功能码定义 7、MODBUS数据链路层 8、MODBUS地址规则 9、MO…...

互联网人看一看,这些神器你用过哪些?

很多小伙伴在剪辑视频的过程中经常可以看到一些语音素材&#xff0c;经常刷视频的小伙伴也可以看到很多视频中经常出现一些AI合成的声音或者音效&#xff0c;这些配音可以给视频增添很多亮点&#xff01;那么大家都是怎么将文字转语音的呢&#xff1f;今天给大家分享5款非常专业…...

Kotlin学习:5.2、异步数据流 Flow

Flow一、Flow1、Flow是什么东西&#xff1f;2、实现功能3、特点4、冷流和热流5、流的连续性6、流的构建器7、流的上下文8、指定流所在协程9、流的取消9.1、超时取消9.2、主动取消9.3、密集型任务的取消10、背压和优化10.1、buffer 操作符10.2、 flowOn10.3、conflate 操作符10.…...

EPICS synApps介绍

一、synApps是什么&#xff1f; 1&#xff09; 一个用于同步束线用户的EPICS模块集合。 2&#xff09; EPICS模块 alive, autosave, busy, calc, camac, caputRecorder, dac128V, delaygen, dxp, ip, ip330, ipUnidig, love, mca, measComp, modbus, motor, optics, quadEM,…...