Android LayoutTransiton實現(xiàn)簡單的錄制按鈕
最近公司要做的項目中要求實現(xiàn)一個簡單的視頻錄制功能的組件,我簡單設(shè)計了一個,主要功能就是開始,暫停,停止和顯示錄制時間長度。首先看一下效果圖:

可以看到是一個非常簡單的動畫效果,為了方便使用,我把他做成了aar并發(fā)布到了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) /*設(shè)置監(jiān)聽器, void onInitial(); void onStartRecord(); void onPauseRecord(); void onResumeRecord(); void onStopRecord();*/ void setRecordListener(RecordListener) void setDebug(boolean)
LayoutTransition簡介
來源于官方文檔
LayoutTransition能夠在viewgroup的布局發(fā)生變化時產(chǎn)生一個動畫效果??梢酝ㄟ^ ViewGroup.setLayoutTransition(LayoutTransition transition) 來設(shè)置過度效果。調(diào)用這個方法將會使用內(nèi)置的過渡動畫(alpha值變化,xy位置變化等),開發(fā)者可用通過`LayoutTransition.setAnimator(int transitionType,Animator animator)來設(shè)置自己的過渡效果。能夠出發(fā)動畫的情況有兩種:
- item添加(設(shè)置View.VISIBLE也可)
- item移除(設(shè)置View.GON也可)
當(dāng)viewgroup中發(fā)生上述兩種行為時,或者由于添加刪除而引起其他item變化,都會觸發(fā)動畫。
過渡動畫的觸發(fā)種類
這個種類指的是在發(fā)生某種行為時(例如item添加或者刪除),共有5種: CHANGE_APPEARING,CHANGE_DISAPPERING,APPEARING,DISAPPEARING,CHANGING 。每種狀態(tài)有自己的一個位標(biāo)記。
CHANGE_APPEARING
指示動畫將會在新的控件添加到viewgroup中的時候引起其他view變化觸發(fā)。它的標(biāo)志位是0x01。也就是當(dāng)addview或者將非VISIBLE狀態(tài)的view設(shè)置為VISIBILE狀態(tài)時其他的view被影響到時也會觸發(fā)。
CHANGE_DISAPPEARING
指示動畫將會在viewgroup刪除控件的時候引起其他view變化觸發(fā),它的標(biāo)志位是0x02。也就是當(dāng)removeview或者將VISIBLE狀態(tài)的view設(shè)置為非VISIBLE狀態(tài)時其他的view被影響到也會觸發(fā)。
APPEARING
當(dāng)新的view添加到viewgroup中的時候觸發(fā)。它的標(biāo)志位是0x04。也就是當(dāng)addview或者將非VISIBLE狀態(tài)的view設(shè)置為VISIBILE狀態(tài)時會觸發(fā)。
DISAPPERAING
指示動畫將會在viewgroup刪除控件時觸發(fā),它的標(biāo)志位是0x08。也就是當(dāng)removeview或者將VISIBLE狀態(tài)的view設(shè)置為非VISIBLE狀態(tài)時會觸發(fā)。
CHANGING
出去前邊的四種,當(dāng)布局發(fā)生變化時會觸發(fā)動畫。它的標(biāo)志位是0x10。這個標(biāo)志位默認(rèn)是不激活的,但是可以通過enableTransitonType(int)來激活。
了解了這些,這個庫基本就能實現(xiàn)了。
RecordView分析

左邊的開始和暫停按鈕是一個checkbox實現(xiàn)的,通過一個簡單的selector來切換圖片,并在右側(cè)布局出現(xiàn)和消失的時候有一個縮放動畫。我們可以通過設(shè)置一個簡單的ObjectAnimator監(jiān)聽器來實現(xiàn)這個縮放:
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是自定一個一個類,用來設(shè)置顯示的圖片和時間,并保存縮放的狀態(tài):
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來設(shè)置同時更改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了。
原理探究
本人水平有限,這里只進(jìn)行最簡單的一些分析。
LayoutTransition設(shè)置了一系列的默認(rèn)值,這些默認(rèn)值有默認(rèn)的animator,animator的duration,動畫開始的延遲時間,動畫的錯開間隔,插值器,等待執(zhí)行view的動畫map關(guān)系,正在顯示或者消失的view動畫的map關(guān)系,view和view的onlayoutchangelistenr對應(yīng)關(guān)系等等。
默認(rèn)的方法和變量
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;
}
可以看到,默認(rèn)動畫持有的屬性有l(wèi)eft、top、right、bottom、scrollY和scrollX,這里注意一下startDelay這個方法,可以看到其實這個啟動的延遲時間是不一樣的,對應(yīng)的關(guān)系為:
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。這樣做的目的是為了在動畫展示的時候有一個順序展示的視覺效果,看起來更符合邏輯:
當(dāng)一個item添加到viewgroup的時候,其他阿德item首先要移動來調(diào)整出一塊空白區(qū)域供新添加的item顯示,然后執(zhí)行新添加的item的顯示動畫。當(dāng)移除一個item時,是一個逆向的過程。
看另個一有用的變量
private int mTransitionTypes = FLAG_CHANGE_APPEARING | FLAG_CHANGE_DISAPPEARING |
FLAG_APPEARING | FLAG_DISAPPEARING;
這個mTransitionTypes就是在后邊的執(zhí)行動畫中必須使用的一個變量,它默認(rèn)激活了四種種類,只有前邊提到的FLAG_CHAGE未激活.
開發(fā)者可控的變量
這里集中講幾個方法:
//設(shè)置所有的動畫持續(xù)時間 public void setDuration(long duration) //設(shè)置指定種類的動畫持續(xù)時間:CHANGE_APPEARING,CHANGE_DISAPPEARING,APPEARING,DISAPPEARRING,CHANGING public void setDuration(int transitionType, long duration) //獲取指定種類動畫的持續(xù)時間 public long getDuration(int transitionType) //設(shè)置在CHANGEINGXX狀態(tài)下時間的間隔 public void setStagger(int transitionType, long duration) //獲取在CHANGEINGXX狀態(tài)下時間的間隔 public long getStagger(int transitionType) //為指定的種類添加動畫插值器 public void setInterpolator(int transitionType, TimeInterpolator interpolator) //獲取指定的種類添加動畫插值器 public TimeInterpolator getInterpolator(int transitionType) //為指定的種類添加動畫 public void setAnimator(int transitionType, Animator animator) //設(shè)置viewgroup的屬性是否隨著view的變化而變化,比如viewgroup使用的是wrapcontent,添加view時會有一個擴(kuò)張動畫 public void setAnimateParentHierarchy(boolean animateParentHierarchy) //是否正在執(zhí)行引起布局改變動畫 public boolean isChangingLayout() //是否有正在執(zhí)行的動畫 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) //添加監(jiān)聽器 public void addTransitionListener(TransitionListener listener) //移除監(jiān)聽器 public void removeTransitionListener(TransitionListener listener) //獲取監(jiān)聽器 public List<TransitionListener> getTransitionListeners()
這些方法都比較簡單。
執(zhí)行流程
先看一張簡單的圖:

從上面的方法中可以看到,flag全都沒有激活的話,那就沒有任何顯示或者隱藏的動畫了。 CHANGE_DISAPPEARING 和 CHANGE_APPEARING 控制的是父view和非新添加view的動畫, APPEARING 和 DISAPPEARING 控制的是新添加view的動畫。
mAnimateParentHierarchy這個變量控制的是是否顯示父布局的改變動畫,所以這個必須設(shè)置為true后父布局的 CHANGE_DISAPPEARING 和 CHANGE_APPEARING 才能有作用,設(shè)置為false后只有父布局沒有動畫,而子控件中非新添加的view還是用動畫效果。
viewgroup中調(diào)用
addview()用來為viewroup添加一個沒有父控件的view,這個方法最終調(diào)用的是
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);
}
//省略代碼
//省略代碼
}
設(shè)置view的顯示或者隱藏時會調(diào)用以下方法
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中與上面圖中提到的方法調(diào)用是吻合的。
在調(diào)用ViewGroup.setLayoutTransition(LayoutTransition transition)的時候為自身設(shè)置了一個TransitionListener,這個地方加入的目的是為了緩存正在進(jìn)行動畫的view,暫不分析。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
RecyclerView Adapter輔助類詳解及示例代碼
本文主要介紹RecyclerView Adapter輔助類的知識,這里整理了詳細(xì)資料及簡單示例代碼,幫助大家學(xué)習(xí)這部分的內(nèi)容,有興趣的小伙伴可以參考下2016-09-09
Android activity動畫不生效原因及解決方案總結(jié)
android activity動畫是一個比較簡單的功能。但是使用時總會由于各種小問題導(dǎo)致動畫失效,筆者根據(jù)自己經(jīng)驗,整理了各種可能導(dǎo)致的原因,期望能對你有所幫助2021-11-11
Android Listview notifyDataSetChanged() 不起作用的
這篇文章主要介紹了Android Listview notifyDataSetChanged()不起作用的解決方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-08-08
Android實現(xiàn)快速滾動FastScrollView效果
這篇文章主要介紹了Android實現(xiàn)快速滾動FastScrollView效果,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08
Android Studio連接手機(jī)設(shè)備教程
這篇文章主要為大家詳細(xì)介紹了Android Studio連接手機(jī)設(shè)備教程,非常完整的連接步驟,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-07-07
Android實現(xiàn)ImageView陰影和圖層效果
這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)ImageView陰影和圖層效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02
基于Android ContentProvider的總結(jié)詳解
本篇文章是對Android ContentProvider進(jìn)行了詳細(xì)的總結(jié)與分析,需要的朋友參考下2013-05-05
Android?Flutter實現(xiàn)精靈圖的使用詳解
在游戲開發(fā)中,精靈圖會將一個人物所有動作放置在一張圖片中,通過坐標(biāo)定位選取其中一張圖展示。本文就來教你如何使用精靈圖,感興趣的可以了解一下2022-08-08

