API Guides(七)—— Property Animation To Media Playback

来源:互联网 发布:ubuntu chrome xijie 编辑:程序博客网 时间:2024/06/17 10:00

Property Animation

特点:可以改变对象的属性,无论视图是否被画在屏幕上,刷新频率默认是10ms,持续时间默认是300ms,可以组合多种动画,可以指定重复次数和行为(文档未提及自定义属性动画)

属性动画的工作原理

ValueAnimator在动画的过程中追踪记录,它封装了用来定义动画插值(比如水平速度LinearInterpolator)的TimeInterpolator和定义如何计算被动画属性的值(比如IntEvaluator)的TypeEvaluator。开始动画后,ValueAnimator持续计算elapsed fraction(代表动画已经完成的百分比),这个动作完成后,它会调用TimeInterpolator计算interpolated fraction(An interpolated fraction maps the elapsed fraction to a new fraction that takes into account the time interpolation that is set. ),这个动作完成后,ValueAnimator会调用TypeEvaluator计算基于interpolated fraction、开始值和结束值正在动画的属性的值。

属性动画和视图动画的区别

  • 视图动画只作用于View对象
  • 视图动画只作用于View对象的有限aspects,比如rotation,但背景颜色就不适用
  • 视图动画只修改View画的地方,不是实际的View对象

属性动画的API概览

一般使用ValueAnimator, ObjectAnimator, AnimatorSet类来定义属性动画
- ValueAnimator是属性动画最主要的时间选择引擎,它包含了(几乎)所有属性动画的功能
- ObjectAnimator是ValueAnimator的子类,它允许你设置一个目标对象和对象的属性进行动画,这个类使实现属性动画变得更容易,大部分时间你都会想使用这个类,不过它有一些限制,比如在目标对象必需特定的访问方法
- AnimatorSet,提供一个组合多动画的机制,更好地处理它们之间的关系
Evaluator四种类或接口分别如下:IntEvaluator, FloatEvaluator, ArgbEvaluator, TypeEvaluator;Interpolators十种类或接口分别如下:AccelerateDecelerateInterpolator, AccelerateInterpolator, AnticipateInterpolator, AnticipateOvershootInterpolator, BounceInterpolator, CycleInterpolator, DecelerateInterpolator, LinearInterpolator, OvershootInterpolator, TimeInterpolator

使用ValueAnimation进行动画

ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);animation.setDuration(1000);animation.start();

上面这个代码片段实际不会作用于一个对象,因为valueAnimator不直接作用于对象或属性, The most likely thing that you want to do is modify the objects that you want to animate with these calculated values.

使用ObjectAnimation进行动画

ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);anim.setDuration(1000);anim.start();

需要注意的地方:

  • 正在动画的对象属性必须有setter方法,否则只使用ValueAnimator
  • 只指定参数values…的一个值,它是动画的结束值,它也必须有setter方法
  • 属性的getter和setter值的类型必须和ObjectAnimator一致
  • 取决于你正在动画的属性,你可能需要调用invalidate()根据动画更新值来强制重绘,在onAnimationUpdate()回调里做重绘,比如Drawable对象的color属性;但是View的所有setter属性,比如setAlpha()和setTranslationX(),你不需要调用invalidate()

组合多种动画

AnimatorSet bouncer = new AnimatorSet();bouncer.play(bounceAnim).before(squashAnim1);bouncer.play(squashAnim1).with(squashAnim2);bouncer.play(bounceBackAnim).after(stretchAnim2);ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);fadeAnim.setDuration(250);AnimatorSet animatorSet = new AnimatorSet();animatorSet.play(bouncer).before(fadeAnim);animatorSet.start();

动画的监听器

  • Animator.AnimatorListener包含onAnimationStart(), onAnimationEnd(), onAnimationRepeat(), onAnimationCancel(),cancel这个方法也会调用onAnimationEnd(),不管它们是如何结束的;ValueAnimator.AnimatorUpdateListener下的主要方法是onAnimationUpdate(), 在动画的每帧改变后调用,也可能会处理一些视图的重绘
  • 一般都是继承AnimatorListenerAdapter类来使用AnimatorListener,它提供了这些方法的空实现,你可以选择性override
ValueAnimatorAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);fadeAnim.setDuration(250);fadeAnim.addListener(new AnimatorListenerAdapter() {public void onAnimationEnd(Animator animation) {    balls.remove(((ObjectAnimator)animation).getTarget());}

进行ViewGroups布局的属性动画改变

可以通过LayoutTransition类来动画ViewGroup的布局改变,通过调用setAnimator()方法传入一个Animator对象,这个对象有下面4个LayoutTransition常量之一:APPEARING, CHANGE_APPEARING, DISAPPEARING, CHANGE_DISAPPEARING;但是你也可以简单地在layout.xml里面设置ViewGroup属性就可以使用默认的动画效果

android:animateLayoutChanges="true"

指定关键帧 keyframe

A Keyframe object consists of a time/value pair that lets you define a specific state at a specific time of an animation.

动画View

  • View会自动调用invalidate(),支持的属性动画有这些:translationX, translationY, rotation, rotationX, rotationY, scaleX, scaleY, pivotX, pivotY, x, y, alpha
  • 使用ViewPropertyAnimator会使属性动画更简洁和易读,下面是一种功能的三种实现
// Multiple ObjectAnimator objectsObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);AnimatorSet animSetXY = new AnimatorSet();animSetXY.playTogether(animX, animY);animSetXY.start();
// One ObjectAnimatorPropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
// ViewPropertyAnimatormyView.animate().x(50f).y(100f);

Drawable Animation

就是用一组AnimationDrawable组成的动画,通过设置oneshot为true,这组动画就只执行一次,否则无线循环,需要注意的是动画的start()方法,它不能在oncreate()的时候调用,因为它并没有完全attach在windows上,如果你希望不经过交互就开启动画,你应该在onWindowFocusChanged()中调用它。

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"    android:oneshot="true">    <item android:drawable="@drawable/rocket_thrust1" android:duration="200" />    <item android:drawable="@drawable/rocket_thrust2" android:duration="200" /></animation-list>
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);  rocketImage.setBackgroundResource(R.drawable.rocket_thrust);  rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
rocketAnimation.start();

Hardware Acceleration

硬件加速在目标API在14和之后,是默认开启的;硬件加速并不支持所有2D绘制操作(详情看文档),使用不当会造成异常,如果自定义视图使用硬件加速,应该经过真机测试

控制硬件加速

硬件加速可控的级别有Application, Activity, Window and View

<application android:hardwareAccelerated="true" ...>
<activity android:hardwareAccelerated="false" />
getWindow().setFlags(    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);// 这个Window级别不能disable
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);// 这个View级别不能enable

查询一个视图是否硬件加速

  • View.isHardwareAccelerated(), returns true if the View is attached to a hardware accelerated window.
  • Canvas.isHardwareAccelerated() returns true if the Canvas is hardware accelerated
  • 在你的drawing code即(ondraw())时检测,使用Canvas.isHardwareAccelerated()

Android的绘制模型

基于软件的绘制模型

  • invalidate the hierarchy
  • draw the hierarchy

这个模型有两个缺点

  • 这个模型在每次绘制的时候都需要执行大量的代码
  • 这个模型可能会在应用隐藏bug

基于硬件的绘制模型

  • invalidate the hierarchy
  • record and update display lists
  • draw the display lists

比如一个包含ListView和Button的LinearLayout的display list是这样的

  • DrawDisplayList(ListView)
  • DrawDisplayList(Button)

现在你改变ListView的不透明度(setAlpha(0.5)),display list就会变成这样

  • SaveLayerAlpha(0.5)
  • DrawDisplayList(ListView)
  • Restore
  • DrawDisplayList(Button)
  • 复杂的ListView绘制代码就不会执行

View Layer 视图的层级

  • LAYER_TYPE_NONE: 视图正常渲染,不支持off-screen buffer,这是默认行为
  • LAYER_TYPE_HARDWARE: 视图硬件渲染,应用没开启硬件加速就和SOFTWARE一样
  • LAYER_TYPE_SOFTWARE: 视图软件渲染into a bitmap

优化视图的提示和技巧

  • 减少应用的视图数量
  • 避免视图重叠覆盖
  • 不要在draw方法里面创建渲染对象如,Paint
  • 不要频繁修改形状
  • 不要频繁修改Bitmap
  • 小心使用alpha,当在大视图使用alpha,推荐把该视图的layer设置为HARDWARE

Media Playback

如果你希望应用避免屏幕变暗或者是进程休眠,可以使用MediaPlayer.setScreenOnWhilePlaying() or MediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK),但是必须先请求权限

<uses-permission android:name="android.permission.WAKE_LOCK" />

如果你通过网络播放media,你也需要请求WifiLock,并在暂停或者停止的时候释放 wifiLock.release()

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");wifiLock.acquire();

使用Media

MediaPlayer支持的媒体资源

  • 本地资源
  • 内部URI,通过Content Resolver提供的
  • 外部URI(streaming)
  • 支持的媒体格式:http://developer.android.com/guide/appendix/media-formats.html

异步准备

prepare()方法会需要一段时间执行,所以不应该在UI线程调用,可以新建一个线程,或者是使用MediaPlayer框架提供的prepareAsync()方法,当准备完成后,the onPrepared() method of the MediaPlayer.OnPreparedListener, configured through setOnPreparedListener() is called.

MediaPlayer的状态state

一共有八种,分别为Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error,知道这个的原因是,一些特定的操作只有在player处于一个特定的状态的时候才有效,不然会抛出异常或者是造成其它不可预料的行为

释放MediaPlayer

mediaPlayer.release();mediaPlayer = null;

Handling audio focus

因为audio输出只有一个,所以会竞争资源,Android提供了一个机制去处理这种情况,叫Audio Focus

请求audio focus

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,    AudioManager.AUDIOFOCUS_GAIN);if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {    // could not get audio focus.}

处理audio focus

在媒体播放器的service或者activity实现AudioManager.OnAudioFocusChangeListener接口,监听audio focus的改变,这个接口有一个onAudioFocusChange方法,你就在这个方法里面处理audio focus

public void onAudioFocusChange(int focusChange) {    switch (focusChange) {        case AudioManager.AUDIOFOCUS_GAIN:            // resume playback            if (mMediaPlayer == null) initMediaPlayer();            else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();            mMediaPlayer.setVolume(1.0f, 1.0f);            break;        case AudioManager.AUDIOFOCUS_LOSS:            // Lost focus for an unbounded amount of time: stop playback and release media player            if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();            mMediaPlayer.release();            mMediaPlayer = null;            break;        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:            // Lost focus for a short time, but we have to stop            // playback. We don't release the media player because playback            // is likely to resume            if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();            break;        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:            // Lost focus for a short time, but it's ok to keep playing            // at an attenuated level            if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);            break;    }}

处理AUDIO_BECOMING_NOISY Intent

其实就是拔耳机操作,系统是不会自动停止播放,你需要自己通过一个Receiver实现

<receiver android:name=".MusicIntentReceiver">   <intent-filter>      <action android:name="android.media.AUDIO_BECOMING_NOISY" />   </intent-filter></receiver>
public class MusicIntentReceiver extends android.content.BroadcastReceiver {   @Override   public void onReceive(Context ctx, Intent intent) {      if (intent.getAction().equals(                    android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {          // signal your service to stop playback          // (via an Intent, for instance)      }   }}

通过Content Resolver检索用户拥有的多媒体数据

ContentResolver contentResolver = getContentResolver();Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;Cursor cursor = contentResolver.query(uri, null, null, null, null);if (cursor == null) {    // query failed, handle error.} else if (!cursor.moveToFirst()) {    // no media on the device} else {    int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);    int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);    do {       long thisId = cursor.getLong(idColumn);       String thisTitle = cursor.getString(titleColumn);       // ...process entry...    } while (cursor.moveToNext());}
long id = /* retrieve it from somewhere */;Uri contentUri = ContentUris.withAppendedId(        android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);mMediaPlayer = new MediaPlayer();mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mMediaPlayer.setDataSource(getApplicationContext(), contentUri);// ...prepare and start...
0 0
原创粉丝点击