Android LayoutTransiton实现简单的录制按钮

最近公司要做的项目中要求实现一个简单的视频录制功能的组件,我简单设计了一个,主要功能就是开始,暂停,停止和显示录制时间长度。首先看一下效果图:

可以看到是一个非常简单的动画效果,为了方便使用,我把他做成了aar并发布到了jCenter,集成方式:

compile 'com.rangaofei:sakarecordview:0.0.2'

组件里用到的库也非常简单,包括databinding,属性动画和layouttransition。通过这个简单的库简单的介绍一下LayoutTransition的用法,其中也会插入一些简单的databinding和属性动画的知识点,遇到困难请自行解决。

使用方法: 在xml文件中添加自定义控件:

<com.hanlinbode.sakarecordview.RecordView
    android:id="@+id/rv_saka"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_margin="30dp"
    app:record_view_time_string="HHMMSS" />

record_view_time_string 属性是枚举类型,用来表示时间表示形式:

HHMMSS 00:00:00
MMSS 00:00
HH_MM_SS 00-00-00
MM_SS 00-00

//更新时间
void updateTime(long)
/*设置监听器,
  void onInitial();

  void onStartRecord();

  void onPauseRecord();

  void onResumeRecord();

  void onStopRecord();*/
void setRecordListener(RecordListener)

void setDebug(boolean)

LayoutTransition简介

来源于官方文档

LayoutTransition能够在viewgroup的布局发生变化时产生一个动画效果。可以通过 ViewGroup.setLayoutTransition(LayoutTransition transition) 来设置过度效果。调用这个方法将会使用内置的过渡动画(alpha值变化,xy位置变化等),开发者可用通过`LayoutTransition.setAnimator(int transitionType,Animator animator)来设置自己的过渡效果。能够出发动画的情况有两种:

  1. item添加(设置View.VISIBLE也可)
  2. item移除(设置View.GON也可)

当viewgroup中发生上述两种行为时,或者由于添加删除而引起其他item变化,都会触发动画。

过渡动画的触发种类

这个种类指的是在发生某种行为时(例如item添加或者删除),共有5种: CHANGE_APPEARING,CHANGE_DISAPPERING,APPEARING,DISAPPEARING,CHANGING 。每种状态有自己的一个位标记。

CHANGE_APPEARING

指示动画将会在新的控件添加到viewgroup中的时候引起其他view变化触发。它的标志位是0x01。也就是当addview或者将非VISIBLE状态的view设置为VISIBILE状态时其他的view被影响到时也会触发。

CHANGE_DISAPPEARING

指示动画将会在viewgroup删除控件的时候引起其他view变化触发,它的标志位是0x02。也就是当removeview或者将VISIBLE状态的view设置为非VISIBLE状态时其他的view被影响到也会触发。

APPEARING

当新的view添加到viewgroup中的时候触发。它的标志位是0x04。也就是当addview或者将非VISIBLE状态的view设置为VISIBILE状态时会触发。

DISAPPERAING

指示动画将会在viewgroup删除控件时触发,它的标志位是0x08。也就是当removeview或者将VISIBLE状态的view设置为非VISIBLE状态时会触发。

CHANGING

出去前边的四种,当布局发生变化时会触发动画。它的标志位是0x10。这个标志位默认是不激活的,但是可以通过enableTransitonType(int)来激活。

了解了这些,这个库基本就能实现了。

RecordView分析

左边的开始和暂停按钮是一个checkbox实现的,通过一个简单的selector来切换图片,并在右侧布局出现和消失的时候有一个缩放动画。我们可以通过设置一个简单的ObjectAnimator监听器来实现这个缩放:

ObjectAnimator animShow = ObjectAnimator.ofFloat(null, "scaleX", 0, 1);
  animShow.setInterpolator(new OvershootInterpolator());
  animShow.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
      if (isDebug()) {
        Log.e(TAG, "show anim value=" + (float) animation.getAnimatedValue());
      }
      recordState.setPlayScale(1 + (float) animation.getAnimatedValue() / 5);
    }
  });
  layoutTransition.setAnimator(LayoutTransition.APPEARING, animShow);
  ObjectAnimator animHide = ObjectAnimator.ofFloat(null, "alpha", 1, 0);
  animHide.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
      if (isDebug()) {
        Log.e(TAG, "hide anim value=" + (float) animation.getAnimatedValue());
      }
      recordState.setPlayScale(1 + (float) animation.getAnimatedValue() / 5);
    }
  });
  layoutTransition.addTransitionListener(this);
  layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, animHide);
  binding.rootView.setLayoutTransition(layoutTransition);
  binding.rootContainer.setLayoutTransition(layoutTransition);

record是自定一个一个类,用来设置显示的图片和时间,并保存缩放的状态:

public class RecordState extends BaseObservable implements Parcelable {
  private boolean recording;
  private String time = "00:00:00";
  private float playScale = 1;

  @DrawableRes
  private int playDrawable;
  @DrawableRes
  private int stopDrawable;

  public RecordState(int playDrawable, int stopDrawable) {
    this.playDrawable = playDrawable;
    this.stopDrawable = stopDrawable;
  }

  @Bindable
  public boolean isRecording() {
    return recording;
  }

  public void setRecording(boolean recording) {
    this.recording = recording;
    notifyPropertyChanged(BR.recording);
  }

  //省略其他的getter和setter

  @Bindable
  public float getPlayScale() {
    return playScale;
  }

  public void setPlayScale(float playScale) {
    this.playScale = playScale;
    notifyPropertyChanged(BR.playScale);
  }

  //省略parcelable代码
}

这里需要提一个view的局限性,就是只能改变x或者y的缩放,不能同时改变,所以这里做了一个双向绑定并写了一个adapter来设置同时更改X和Y的scale值:

public class CheckboxAttrAdapter {
  @BindingAdapter("checkListener")
  public static void setCheckBoxListener(CheckBox view, CompoundButton.OnCheckedChangeListener listener) {
    view.setOnCheckedChangeListener(listener);
  }

  @BindingAdapter("android:button")
  public static void setButton(CheckBox view, @DrawableRes int drawableId) {
    view.setButtonDrawable(drawableId);
  }

  @BindingAdapter("recordScale")
  public static void setRecordScale(CheckBox view, float scale) {
    view.setScaleX(scale);
    view.setScaleY(scale);
  }
}

然后在xml文件中可以直接饮用属性:

<CheckBox
  android:id="@+id/start"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_centerVertical="true"
  android:layout_marginLeft="30dp"
  android:button="@{state.playDrawable}"
  android:checked="@{state.recording}"
  app:checkListener="@{checkListener}"
  app:recordScale="@{state.playScale}" />

这样就基本完成了动画操作,然后暴露一些接口即可:

public interface RecordListener {
  void onInitial();

  void onStartRecord();

  void onPauseRecord();

  void onResumeRecord();

  void onStopRecord();

}

这样就完成了一个最简单的RecordView了。

原理探究

本人水平有限,这里只进行最简单的一些分析。

LayoutTransition设置了一系列的默认值,这些默认值有默认的animator,animator的duration,动画开始的延迟时间,动画的错开间隔,插值器,等待执行view的动画map关系,正在显示或者消失的view动画的map关系,view和view的onlayoutchangelistenr对应关系等等。

默认的方法和变量

public LayoutTransition() {
  if (defaultChangeIn == null) {
    PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1);
    PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
    PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1);
    PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1);
    PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1);
    PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1);
    defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder((Object)null,
        pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY);
    defaultChangeIn.setDuration(DEFAULT_DURATION);
    defaultChangeIn.setStartDelay(mChangingAppearingDelay);
    defaultChangeIn.setInterpolator(mChangingAppearingInterpolator);
    defaultChangeOut = defaultChangeIn.clone();
    defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
    defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
    defaultChange = defaultChangeIn.clone();
    defaultChange.setStartDelay(mChangingDelay);
    defaultChange.setInterpolator(mChangingInterpolator);

    defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
    defaultFadeIn.setDuration(DEFAULT_DURATION);
    defaultFadeIn.setStartDelay(mAppearingDelay);
    defaultFadeIn.setInterpolator(mAppearingInterpolator);
    defaultFadeOut = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
    defaultFadeOut.setDuration(DEFAULT_DURATION);
    defaultFadeOut.setStartDelay(mDisappearingDelay);
    defaultFadeOut.setInterpolator(mDisappearingInterpolator);
  }
  mChangingAppearingAnim = defaultChangeIn;
  mChangingDisappearingAnim = defaultChangeOut;
  mChangingAnim = defaultChange;
  mAppearingAnim = defaultFadeIn;
  mDisappearingAnim = defaultFadeOut;
}

可以看到,默认动画持有的属性有left、top、right、bottom、scrollY和scrollX,这里注意一下startDelay这个方法,可以看到其实这个启动的延迟时间是不一样的,对应的关系为:

private long mAppearingDelay = DEFAULT_DURATION;
private long mDisappearingDelay = 0;
private long mChangingAppearingDelay = 0;
private long mChangingDisappearingDelay = DEFAULT_DURATION;
private long mChangingDelay = 0;

官方文档中特别说明了:

By default, the DISAPPEARING animation begins immediately, as does the CHANGE_APPEARING animation. The other animations begin after a delay that is set to the default duration of the animations.

DISAPPEARING和CHANGE_APPEARING没有延迟时间,其他的动画都会有延迟300ms。这样做的目的是为了在动画展示的时候有一个顺序展示的视觉效果,看起来更符合逻辑:

当一个item添加到viewgroup的时候,其他阿德item首先要移动来调整出一块空白区域供新添加的item显示,然后执行新添加的item的显示动画。当移除一个item时,是一个逆向的过程。

看另个一有用的变量

private int mTransitionTypes = FLAG_CHANGE_APPEARING | FLAG_CHANGE_DISAPPEARING |
      FLAG_APPEARING | FLAG_DISAPPEARING;

这个mTransitionTypes就是在后边的执行动画中必须使用的一个变量,它默认激活了四种种类,只有前边提到的FLAG_CHAGE未激活.

开发者可控的变量

这里集中讲几个方法:

//设置所有的动画持续时间
public void setDuration(long duration)
//设置指定种类的动画持续时间:CHANGE_APPEARING,CHANGE_DISAPPEARING,APPEARING,DISAPPEARRING,CHANGING
public void setDuration(int transitionType, long duration)
//获取指定种类动画的持续时间
public long getDuration(int transitionType)
//设置在CHANGEINGXX状态下时间的间隔
public void setStagger(int transitionType, long duration)
//获取在CHANGEINGXX状态下时间的间隔
public long getStagger(int transitionType)
//为指定的种类添加动画插值器
public void setInterpolator(int transitionType, TimeInterpolator interpolator)
//获取指定的种类添加动画插值器
public TimeInterpolator getInterpolator(int transitionType)
//为指定的种类添加动画
public void setAnimator(int transitionType, Animator animator)
//设置viewgroup的属性是否随着view的变化而变化,比如viewgroup使用的是wrapcontent,添加view时会有一个扩张动画
public void setAnimateParentHierarchy(boolean animateParentHierarchy)
//是否正在执行引起布局改变动画
public boolean isChangingLayout()
//是否有正在执行的动画
public boolean isRunning()
//添加item
public void addChild(ViewGroup parent, View child)
//移除item
public void removeChild(ViewGroup parent, View child)
//显示item
public void showChild(ViewGroup parent, View child, int oldVisibility)
//隐藏item
public void hideChild(ViewGroup parent, View child, int newVisibility)
//添加监听器
public void addTransitionListener(TransitionListener listener)
//移除监听器
public void removeTransitionListener(TransitionListener listener)
//获取监听器
public List<TransitionListener> getTransitionListeners()

这些方法都比较简单。

执行流程

先看一张简单的图:

从上面的方法中可以看到,flag全都没有激活的话,那就没有任何显示或者隐藏的动画了。 CHANGE_DISAPPEARINGCHANGE_APPEARING 控制的是父view和非新添加view的动画, APPEARINGDISAPPEARING 控制的是新添加view的动画。

mAnimateParentHierarchy这个变量控制的是是否显示父布局的改变动画,所以这个必须设置为true后父布局的 CHANGE_DISAPPEARINGCHANGE_APPEARING 才能有作用,设置为false后只有父布局没有动画,而子控件中非新添加的view还是用动画效果。

viewgroup中调用

addview()用来为viewroup添加一个没有父控件的view,这个方法最终调用的是

private void addViewInner(View child, int index, LayoutParams params,
      boolean preventRequestLayout){
        //省略代码
  if (mTransition != null) {
    // Don't prevent other add transitions from completing, but cancel remove
    // transitions to let them complete the process before we add to the container
    mTransition.cancel(LayoutTransition.DISAPPEARING);
  }
  //省略代码
  if (mTransition != null) {
    mTransition.addChild(this, child);
  }
  //省略代码
  //省略代码

}

设置view的显示或者隐藏时会调用以下方法

protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
    if (mTransition != null) {
      if (newVisibility == VISIBLE) {
        mTransition.showChild(this, child, oldVisibility);
      } else {
        mTransition.hideChild(this, child, newVisibility);
        if (mTransitioningViews != null && mTransitioningViews.contains(child)) {
          // Only track this on disappearing views - appearing views are already visible
          // and don't need special handling during drawChild()
          if (mVisibilityChangingChildren == null) {
            mVisibilityChangingChildren = new ArrayList<View>();
          }
          mVisibilityChangingChildren.add(child);
          addDisappearingView(child);
        }
      }
    }

    // in all cases, for drags
    if (newVisibility == VISIBLE && mCurrentDragStartEvent != null) {
      if (!mChildrenInterestedInDrag.contains(child)) {
        notifyChildOfDragStart(child);
      }
    }
  }

可以看到在viewgroup中与上面图中提到的方法调用是吻合的。

在调用ViewGroup.setLayoutTransition(LayoutTransition transition)的时候为自身设置了一个TransitionListener,这个地方加入的目的是为了缓存正在进行动画的view,暂不分析。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • android仿微信聊天界面 语音录制功能

    本例为模仿微信聊天界面UI设计,文字发送以及语言录制UI. 1先看效果图: 第一:chat.xml设计 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" andro

  • Android录制mp3格式文件

    前言 最近做一个即时通信类的项目,由于要保证pc端,iOS端和Android端的通用性,最终统一为MP3格式,一直担心MP3格式会不会很大,但是实测还是可以接受的.下面来看看具体步骤: 工具 MP3格式是用一个开源项目转的,MP3lame,由于该项目用到了jni,所以需要大家配置好ndk环境,环境配置在此就不多说了,大家可以自行百度,最新的应该很好配置. 创建jni 拷贝文件 下载好后(我下载的是3.98.4版本)打开,找到libmp3lame文件,将里面的.h和.c拷贝下来,在自己的工程里创建

  • Android录制声音文件(音频)并播放

    本文实例为大家分享了Android录制音频文件的具体代码,供大家参考,具体内容如下 1.这个demo中没有对多次点击同一个声音文件做详细处理,偶尔会有崩溃,用的时候需要注意. 2.按住录音按钮录音过程中,只对竖直方向处理了一下,水平方向没写: 3.没有做删除某个声音文件的操作,但是测试的时候实现了功能,需要用到的话,在MainActivity->onItemClick中的TODO中有详细说明: 4.这只是个demo,如果要在项目中使用,先写出demo,没问题了,再引入项目,在写成demo后,在真

  • Android仿微信语音消息的录制和播放功能

    一.简述 效果: 实现功能: 长按Button时改变Button显示文字,弹出Dialog(动态更新音量),动态生成录音文件,开始录音: 监听手指动作,规定区域.录音状态下手指划出规定区域取消录音,删除生成的录音文件: 监听手指动作.当手指抬起时,判断是否开始录音,录音时长是否过短,符合条件则提示录音时长过短:正常结束时通过回调返回该次录音的文件路径和时长. 4.点击录音列表的item时,播放动画,播放对应的音频文件. 主要用到4个核心类: 自定义录音按钮(AudioRecordButton):

  • Android5.1 取消录制屏幕跳出的权限对话框问题

    当录制屏幕时总是跳出一个对话框让确定是否录制,总是会引起不必要的麻烦,于是找到启动获取屏幕信息的代码 Intent captureIntent = mMediaProjectionManager.createScreenCaptureIntent(); startActivityForResult(captureIntent, REQUEST_CODE); 找到createScreenCaptureIntent()方法 在源码中找到 frameworks/base/packages/System

  • Android使用MediaRecorder类进行录制视频

    我们提醒大家使用MediaRecorder录音录像的设置代码步骤一定要按照API指定的顺序来设置,否则报错 步骤为: 1.设置视频源,音频源,即输入源 2.设置输出格式 3.设置音视频的编码格式 一.首先看布局文件,这里有一个SurfaceView,这是一个绘制容器,可以直接从内存或者DMA等硬件接口取得图像数据, <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tool

  • Android 5.0+ 屏幕录制实现的示例代码

    前言 Android 从 4.0 开始就提供了手机录屏方法,但是需要 root 权限,比较麻烦不容易实现.但是从 5.0 开始,系统提供给了 app 录制屏幕的一系列方法,不需要 root 权限,只需要用户授权即可录屏,相对来说较为简单.本文是在参考了网络上其他录屏资料后完成的, 感谢 .以下将介绍开发录屏功能的一系列步骤以及实现过程中所遇到的一些需要注意的事项. 实现步骤 1.在清单文件中声明需要的权限 因为录制用到麦克风,所以需要加上 AUDIO 权限, <uses-permission a

  • Android仿微信录制小视频

    本文实例为大家分享了Android仿微信录制小视频的具体代码,供大家参考,具体内容如下 先上张图片看看效果 简单叙述下 首先通过Camera类调用系统相机 通过surfaceview绘制出来 通过MediaRecorder来录制视频 闪光灯 和 切换摄像头 需要重新配置Camera的参数 Camera预览界面画面拉升的原因是因为Surfaceview的大小与设定的比例不一致的 **本次版本更新了 切换前置摄像头录制视频问题 Android部分手机录制视频适配IOS手机问题 (OPPO手机部分不适

  • Android自定义录制视频功能

    Android录制视频MediaRecorder+SurfaceView的使用方法,供大家参考,具体内容如下 先看效果图: <1>将视频动画显示到SurfaceView控件上 <2>使用MediaRecorder类进行视频的录制 常用的方法: mediaRecorder.reset(); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); //从照相机采集视频 mediaRecorder.setAudioS

  • 解决Android MediaRecorder录制视频过短问题

    具体表现: 调用MediaRecorder的start()与stop()间隔不能小于1秒(有时候大于1秒也崩),否则必崩.  错误信息: java.lang.RuntimeException: stop failed. at android.media.MediaRecorder.stop(Native Method)  解决办法: 在stop以前调用setOnErrorListener(null);就行了! 相关代码: /** 开始录制 */ @Override public MediaPar

随机推荐