ViewFlipper实现幻灯片非常容易,下面我们先来看看代码是怎么实现功能的.
布局也很简单:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ViewFlipper
android:id="@+id/viewFlipper"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ViewFlipper>
</LinearLayout>
就一个ViewFlipper控件就好了. 代码如下:
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.ViewFlipper;
import com.example.testdemo.R;
public class MainActivity extends Activity implements OnClickListener {
private ViewFlipper flipper;
private int[] urls = { R.drawable.c, R.drawable.g, R.drawable.h,
R.drawable.i, R.drawable.j, R.drawable.k, R.drawable.o };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
flipper = (ViewFlipper) findViewById(R.id.viewFlipper);
flipper.setOnClickListener(this);
//启动
startSlide();
}
private void startSlide() {
//设置进入动画
flipper.setInAnimation(AnimationUtils.loadAnimation(this,
R.anim.slide_left_in));
//设置退出动画
flipper.setOutAnimation(AnimationUtils.loadAnimation(this,
R.anim.slide_left_out));
addFlipperView();
}
private void addFlipperView() {
for (int i = 0; i < urls.length; i++) {
ImageView iv = new ImageView(this);
iv.setScaleType(ScaleType.CENTER_INSIDE);
iv.setBackgroundResource(urls[i]);
flipper.addView(iv);
}
//开始
flipper.startFlipping();
//指定从那个位置开始
flipper.setDisplayedChild(0);
}
@Override
public void onClick(View v) {//停止幻灯片
flipper.stopFlipping();
//移除flipper中所有的图片
flipper.removeAllViews();
}
}
这是这么简单,很容易的实现了换灯片功能.看看ViewFlipper源码中它继承了ViewAnimator类,
源码如下:
@RemoteView
public class ViewFlipper extends ViewAnimator {
private static final String TAG = "ViewFlipper";
private static final boolean LOGD = false;
private static final int DEFAULT_INTERVAL = 3000;
private int mFlipInterval = DEFAULT_INTERVAL;
private boolean mAutoStart = false;
private boolean mRunning = false;
private boolean mStarted = false;
private boolean mVisible = false;
private boolean mUserPresent = true;
public ViewFlipper(Context context) {
super(context);
}
public ViewFlipper(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.ViewFlipper);
mFlipInterval = a.getInt(
com.android.internal.R.styleable.ViewFlipper_flipInterval, DEFAULT_INTERVAL);
mAutoStart = a.getBoolean(
com.android.internal.R.styleable.ViewFlipper_autoStart, false);
a.recycle();
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_SCREEN_OFF.equals(action)) {
mUserPresent = false;
updateRunning();
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
mUserPresent = true;
updateRunning(false);
}
}
};
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// Listen for broadcasts related to user-presence
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
// OK, this is gross but needed. This class is supported by the
// remote views machanism and as a part of that the remote views
// can be inflated by a context for another user without the app
// having interact users permission - just for loading resources.
// For exmaple, when adding widgets from a user profile to the
// home screen. Therefore, we register the receiver as the current
// user not the one the context is for.
getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
filter, null, mHandler);
if (mAutoStart) {
// Automatically start when requested
startFlipping();
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mVisible = false;
getContext().unregisterReceiver(mReceiver);
updateRunning();
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
mVisible = visibility == VISIBLE;
updateRunning(false);
}
/**
* How long to wait before flipping to the next view
*
* @param milliseconds
* time in milliseconds
*/
@android.view.RemotableViewMethod
public void setFlipInterval(int milliseconds) {
mFlipInterval = milliseconds;
}
/**
* Start a timer to cycle through child views
*/
public void startFlipping() {
mStarted = true;
updateRunning();
}
/**
* No more flips
*/
public void stopFlipping() {
mStarted = false;
updateRunning();
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(ViewFlipper.class.getName());
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(ViewFlipper.class.getName());
}
/**
* Internal method to start or stop dispatching flip {@link Message} based
* on {@link #mRunning} and {@link #mVisible} state.
*/
private void updateRunning() {
updateRunning(true);
}
/**
* Internal method to start or stop dispatching flip {@link Message} based
* on {@link #mRunning} and {@link #mVisible} state.
*
* @param flipNow Determines whether or not to execute the animation now, in
* addition to queuing future flips. If omitted, defaults to
* true.
*/
private void updateRunning(boolean flipNow) {
boolean running = mVisible && mStarted && mUserPresent;
if (running != mRunning) {
if (running) {
showOnly(mWhichChild, flipNow);
Message msg = mHandler.obtainMessage(FLIP_MSG);
mHandler.sendMessageDelayed(msg, mFlipInterval);
} else {
mHandler.removeMessages(FLIP_MSG);
}
mRunning = running;
}
if (LOGD) {
Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
+ ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
}
}
/**
* Returns true if the child views are flipping.
*/
public boolean isFlipping() {
return mStarted;
}
/**
* Set if this view automatically calls {@link #startFlipping()} when it
* becomes attached to a window.
*/
public void setAutoStart(boolean autoStart) {
mAutoStart = autoStart;
}
/**
* Returns true if this view automatically calls {@link #startFlipping()}
* when it becomes attached to a window.
*/
public boolean isAutoStart() {
return mAutoStart;
}
private final int FLIP_MSG = 1;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == FLIP_MSG) {
if (mRunning) {
showNext();
msg = obtainMessage(FLIP_MSG);
sendMessageDelayed(msg, mFlipInterval);
}
}
}
};
}
源码中我们很清楚的看出ViewFlipper中运用了广播实现了切换的交互,具体功能和设置属性可想而知是在ViewAnimator类中实现的我们来看看ViewAnimator类的源码:
public class ViewAnimator extends FrameLayout {
int mWhichChild = 0;
boolean mFirstTime = true;
boolean mAnimateFirstTime = true;
Animation mInAnimation;
Animation mOutAnimation;
public ViewAnimator(Context context) {
super(context);
initViewAnimator(context, null);
}
public ViewAnimator(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewAnimator);
int resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_inAnimation, 0);
if (resource > 0) {
setInAnimation(context, resource);
}
resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_outAnimation, 0);
if (resource > 0) {
setOutAnimation(context, resource);
}
boolean flag = a.getBoolean(com.android.internal.R.styleable.ViewAnimator_animateFirstView, true);
setAnimateFirstView(flag);
a.recycle();
initViewAnimator(context, attrs);
}
/**
* Initialize this {@link ViewAnimator}, possibly setting
* {@link #setMeasureAllChildren(boolean)} based on {@link FrameLayout} flags.
*/
private void initViewAnimator(Context context, AttributeSet attrs) {
if (attrs == null) {
// For compatibility, always measure children when undefined.
mMeasureAllChildren = true;
return;
}
// For compatibility, default to measure children, but allow XML
// attribute to override.
final TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.FrameLayout);
final boolean measureAllChildren = a.getBoolean(
com.android.internal.R.styleable.FrameLayout_measureAllChildren, true);
setMeasureAllChildren(measureAllChildren);
a.recycle();
}
/**
* Sets which child view will be displayed.
*
* @param whichChild the index of the child view to display
*/
@android.view.RemotableViewMethod
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);
}
}
/**
* Returns the index of the currently displayed child view.
*/
public int getDisplayedChild() {
return mWhichChild;
}
/**
* Manually shows the next child.
*/
@android.view.RemotableViewMethod
public void showNext() {
setDisplayedChild(mWhichChild + 1);
}
/**
* Manually shows the previous child.
*/
@android.view.RemotableViewMethod
public void showPrevious() {
setDisplayedChild(mWhichChild - 1);
}
/**
* Shows only the specified child. The other displays Views exit the screen,
* optionally with the with the {@link #getOutAnimation() out animation} and
* the specified child enters the screen, optionally with the
* {@link #getInAnimation() in animation}.
*
* @param childIndex The index of the child to be shown.
* @param animate Whether or not to use the in and out animations, defaults
* to true.
*/
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);
}
}
}
/**
* Shows only the specified child. The other displays Views exit the screen
* with the {@link #getOutAnimation() out animation} and the specified child
* enters the screen with the {@link #getInAnimation() in animation}.
*
* @param childIndex The index of the child to be shown.
*/
void showOnly(int childIndex) {
final boolean animate = (!mFirstTime || mAnimateFirstTime);
showOnly(childIndex, animate);
}
@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);
}
}
@Override
public void removeAllViews() {
super.removeAllViews();
mWhichChild = 0;
mFirstTime = true;
}
@Override
public void removeView(View view) {
final int index = indexOfChild(view);
if (index >= 0) {
removeViewAt(index);
}
}
@Override
public void removeViewAt(int index) {
super.removeViewAt(index);
final int childCount = getChildCount();
if (childCount == 0) {
mWhichChild = 0;
mFirstTime = true;
} else if (mWhichChild >= childCount) {
// Displayed is above child count, so float down to top of stack
setDisplayedChild(childCount - 1);
} else if (mWhichChild == index) {
// Displayed was removed, so show the new child living in its place
setDisplayedChild(mWhichChild);
}
}
public void removeViewInLayout(View view) {
removeView(view);
}
public void removeViews(int start, int count) {
super.removeViews(start, count);
if (getChildCount() == 0) {
mWhichChild = 0;
mFirstTime = true;
} else if (mWhichChild >= start && mWhichChild < start + count) {
// Try showing new displayed child, wrapping if needed
setDisplayedChild(mWhichChild);
}
}
public void removeViewsInLayout(int start, int count) {
removeViews(start, count);
}
/**
* Returns the View corresponding to the currently displayed child.
*
* @return The View currently displayed.
*
* @see #getDisplayedChild()
*/
public View getCurrentView() {
return getChildAt(mWhichChild);
}
/**
* Returns the current animation used to animate a View that enters the screen.
*
* @return An Animation or null if none is set.
*
* @see #setInAnimation(android.view.animation.Animation)
* @see #setInAnimation(android.content.Context, int)
*/
public Animation getInAnimation() {
return mInAnimation;
}
/**
* Specifies the animation used to animate a View that enters the screen.
*
* @param inAnimation The animation started when a View enters the screen.
*
* @see #getInAnimation()
* @see #setInAnimation(android.content.Context, int)
*/
public void setInAnimation(Animation inAnimation) {
mInAnimation = inAnimation;
}
/**
* Returns the current animation used to animate a View that exits the screen.
*
* @return An Animation or null if none is set.
*
* @see #setOutAnimation(android.view.animation.Animation)
* @see #setOutAnimation(android.content.Context, int)
*/
public Animation getOutAnimation() {
return mOutAnimation;
}
/**
* Specifies the animation used to animate a View that exit the screen.
*
* @param outAnimation The animation started when a View exit the screen.
*
* @see #getOutAnimation()
* @see #setOutAnimation(android.content.Context, int)
*/
public void setOutAnimation(Animation outAnimation) {
mOutAnimation = outAnimation;
}
/**
* Specifies the animation used to animate a View that enters the screen.
*
* @param context The application's environment.
* @param resourceID The resource id of the animation.
*
* @see #getInAnimation()
* @see #setInAnimation(android.view.animation.Animation)
*/
public void setInAnimation(Context context, int resourceID) {
setInAnimation(AnimationUtils.loadAnimation(context, resourceID));
}
/**
* Specifies the animation used to animate a View that exit the screen.
*
* @param context The application's environment.
* @param resourceID The resource id of the animation.
*
* @see #getOutAnimation()
* @see #setOutAnimation(android.view.animation.Animation)
*/
public void setOutAnimation(Context context, int resourceID) {
setOutAnimation(AnimationUtils.loadAnimation(context, resourceID));
}
/**
* Returns whether the current View should be animated the first time the ViewAnimator
* is displayed.
*
* @return true if the current View will be animated the first time it is displayed,
* false otherwise.
*
* @see #setAnimateFirstView(boolean)
*/
public boolean getAnimateFirstView() {
return mAnimateFirstTime;
}
/**
* Indicates whether the current View should be animated the first time
* the ViewAnimator is displayed.
*
* @param animate True to animate the current View the first time it is displayed,
* false otherwise.
*/
public void setAnimateFirstView(boolean animate) {
mAnimateFirstTime = animate;
}
@Override
public int getBaseline() {
return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(ViewAnimator.class.getName());
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(ViewAnimator.class.getName());
}
}
在ViewAnimator类中它继承了FrameLayout,这里我们就能明白他是用碎片来做切换的,源码还是比较简单的,都是一些设置属性的方法,比如setDisplayedChild(0)设置启动位置,setInAnimation()设置进入的动画,setOutAnimation()设置出去的动画等方法.看完源码我们现在是不是更清楚的知道ViewFlipper控件真的是为幻灯片连身定制的,但是有个问题就是图片多了容易报内存溢出.希望能帮助大大家.
动画效果可以自由设置,下面是从右到左的切换,先在res文件夹中创建一个anim文件夹在anim文件夹中创建xml文件选择set属性;R.anim.slide_left_in代码如下:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<!--
画面转换位置移动动画效果
开始向左动画
fromXDelta : 动画开始时 X坐标位置
toXDelta :动画结束时 X坐标位置
duration :动画持续时间
-->
<translate
android:duration="800"
android:fromXDelta="100%p"
android:toXDelta="0" />
</set>
R.anim.slide_left_out代码如下:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="800"
android:fromXDelta="0"
android:toXDelta="-100%p" />
</set>