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);
}
}
再次运行可以执行出场动画了。