ViewFlipper可以達到視圖的切換功能,因此利用它也能實現跑馬燈的效果,但是本人用的時候遇到一個問題就是,進場動畫執行結束後不會執行出場動畫,再翻看ViewFlipper源碼之後有了頭緒
ViewFlipper重點的源碼如下
//ViewFlipper內部的方法,根據mVisible 和 mRunning的狀態來決定開始或者停止切換
private void updateRunning(boolean flipNow) {
boolean running = mVisible && mStarted && mUserPresent;
if (running != mRunning) {
if (running) {
showOnly(mWhichChild, flipNow);
postDelayed(mFlipRunnable, mFlipInterval);
} else {
removeCallbacks(mFlipRunnable);
}
mRunning = running;
}
if (LOGD) {
Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
+ ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
}
}
//顯示指定的child。其他顯示在屏幕的view將要退出,移除的動畫由getOutAnimation自定義實現,被指定的child進入屏 //幕會由getInAnimation自定義實現
void showOnly(int childIndex, boolean animate) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (i == childIndex) {
if (animate && mInAnimation != null) {
child.startAnimation(mInAnimation);
}
child.setVisibility(View.VISIBLE);
mFirstTime = false;
} else {
if (animate && mOutAnimation != null && child.getVisibility() == View.VISIBLE) {
child.startAnimation(mOutAnimation);
} else if (child.getAnimation() == mInAnimation)
child.clearAnimation();
child.setVisibility(View.GONE);
}
}
}
//重寫了addView方法,如果子child數量等於1 那麼就顯示,如果是多個,就都隱藏
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params);
if (getChildCount() == 1) {
child.setVisibility(View.VISIBLE);
} else {
child.setVisibility(View.GONE);
}
if (index >= 0 && mWhichChild >= index) {
// Added item above current one, increment the index of the displayed child
setDisplayedChild(mWhichChild + 1);
}
}
//設置哪個child將要顯示
public void setDisplayedChild(int whichChild) {
mWhichChild = whichChild;
if (whichChild >= getChildCount()) {
mWhichChild = 0;
} else if (whichChild < 0) {
mWhichChild = getChildCount() - 1;
}
boolean hasFocus = getFocusedChild() != null;
// This will clear old focus if we had it
showOnly(mWhichChild);
if (hasFocus) {
// Try to retake focus if we had it
requestFocus(FOCUS_FORWARD);
}
}
//循環顯示view
private final Runnable mFlipRunnable = new Runnable() {
@Override
public void run() {
if (mRunning) {
showNext();
postDelayed(mFlipRunnable, mFlipInterval);
}
}
};
//手動顯示下一個view
public void showNext() {
setDisplayedChild(mWhichChild + 1);
}
ViewFlipper的源碼讀起來不是很複雜,這時候就要改動它,使之有跑馬燈的效果,
簡單需求:實現一行文字從下進入屏幕,中間停留4秒,從上面滑出屏幕,然後再從下面進入,一直循環。
思路:新建類MyMarqueeView 繼承 ViewFlipper,然後根據要顯示的內容去創建兩個TextView,一個TextView滑出屏幕時,另一個TextView進入屏幕。
代碼很簡單就不附上了,最終顯示效果的時候,進場動畫是有的,但是在中間停留一段時間後,出場動畫沒有執行,而是直接消失從底部又開始進場動畫,停留,如此循環,看了代碼之後發現outAnimation設置了,並且inAnimation和outAnimaton的AnimationListener都打印出數據了證明都是執行了,此時表示很奇怪,由於MyMarqueeView是根據用戶自定義顯示在界面某個地方的,且根據讀取的配置數據來顯示多大區域,因此MyMarqueeView的父佈局是重寫了onMeasure和onLayout方法,MyMarquee也得重寫onMeasure方法,於是本人就在MyMarqueeView的onMeasure方法中尋找線索,onMeasure方法中調用了measureChildren去設置子View的大小,並且利用setMeasuredDimension改變了自己顯示的大小,於是就打印MyMarqueeView子View的寬高,即那兩個TextView的寬高,發現一個寬高是正常要顯示的大小,一個是整個手機屏幕的大小,正常來說不都應該顯示成自定義的大小嗎?!帶着疑問,看ViewFlipper的源碼,尤其是着重看界面切換的代碼,此時發現showOnly方法,要顯示的View當然是進行進場動畫,而其他View按本意應該走下面的代碼,
並且執行startAnimation(mOutAnimation)
if (animate && mOutAnimation != null && child.getVisibility() == View.VISIBLE) {
child.startAnimation(mOutAnimation);
} else if (child.getAnimation() == mInAnimation)
child.clearAnimation();
child.setVisibility(View.GONE);
結果if和else if都沒有執行,這說明將要退場的View的visiability值應該是GONE,那這個View的visiability在哪設置成GONE了呢,
開始MyMarqueeView是沒有child的,是代碼中添加了兩個TextView,因此從addView爲出發點開始繼續尋找線索,最終在ViewAnimator的addView方法中找到了緣由,當我們調用了MyMarqueeView的addView(new TextView)時,必然會調用父類ViewAnimator的addView方法,而它重寫的addView方法(見上面)明確定義了,當childCount>1時,新添加的child的visiability值就是GONE,回到上面measureChildren過後打印兩個TextView的大小不一樣,是因爲visiability一個是VISIABLE一個是GONE的原因?進入ViewGroup的measureChildren的方法,源碼如下:
/**
* Ask all of the children of this view to measure themselves, taking into
* account both the MeasureSpec requirements for this view and its padding.
* We skip children that are in the GONE state The heavy lifting is done in
* getChildMeasureSpec.
*
* @param widthMeasureSpec The width requirements for this view
* @param heightMeasureSpec The height requirements for this view
*/
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
發現隱藏的View確實沒有按父類提供的大小去measure,因此出現了一個TextView的大小不是自定義的大小,超出了父類MyMarqueeView大小的child出場動畫當然是看不到。
解決:
重寫MyMarqueeView的measureChildren方法,讓隱藏的TextView也要計算出它的大小
@Override
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
//顯示的child和未顯示的child都要去計算大小
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
再次運行可以執行出場動畫了。