blob: a6d51702c403ddfdf9b748b9d304fdb847ac74f3 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.widget;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
/**
* Base class for a {@link AdapterView} that will perform animations
* when switching between its views.
*
* @attr ref android.R.styleable#AdapterViewAnimator_inAnimation
* @attr ref android.R.styleable#AdapterViewAnimator_outAnimation
* @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView
*/
public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteViewsAdapter.RemoteAdapterConnectionCallback{
private static final String TAG = "RemoteViewAnimator";
int mWhichChild = 0;
boolean mFirstTime = true;
boolean mAnimateFirstTime = true;
AdapterDataSetObserver mDataSetObserver;
View mPreviousView;
View mCurrentView;
Animation mInAnimation;
Animation mOutAnimation;
Adapter mAdapter;
RemoteViewsAdapter mRemoteViewsAdapter;
private Handler mMainQueue;
public AdapterViewAnimator(Context context) {
super(context);
initViewAnimator(context, null);
}
public AdapterViewAnimator(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 AdapterViewAnimator}
*/
private void initViewAnimator(Context context, AttributeSet attrs) {
mMainQueue = new Handler(Looper.myLooper());
}
/**
* Sets which child view will be displayed.
*
* @param whichChild the index of the child view to display
*/
public void setDisplayedChild(int whichChild) {
if (mAdapter != null) {
mWhichChild = whichChild;
if (whichChild >= mAdapter.getCount()) {
mWhichChild = 0;
} else if (whichChild < 0) {
mWhichChild = mAdapter.getCount() - 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);
}
}
}
/**
* Return default inAnimation. To be overriden by subclasses.
*/
public Animation getDefaultInAnimation() {
return null;
}
/**
* Return default outAnimation. To be overriden by subclasses.
*/
public Animation getDefaultOutAnimation() {
return null;
}
/**
* Returns the index of the currently displayed child view.
*/
public int getDisplayedChild() {
return mWhichChild;
}
/**
* Manually shows the next child.
*/
public void showNext() {
setDisplayedChild(mWhichChild + 1);
}
/**
* Manually shows the previous child.
*/
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) {
showOnly(childIndex, animate, false);
}
private LayoutParams makeLayoutParams() {
int width = mMeasuredWidth - mPaddingLeft - mPaddingRight;
int height = mMeasuredHeight - mPaddingTop - mPaddingBottom;
return new LayoutParams(width, height);
}
protected void showOnly(int childIndex, boolean animate, boolean onLayout) {
if (mAdapter != null) {
// The previous view should be removed from the ViewGroup
if (mPreviousView != null) {
mPreviousView.clearAnimation();
// TODO: this is where we would store the the view for
// recycling
removeViewInLayout(mPreviousView);
}
// If the current view is still being animated, we should
// force the animation to end
if (mCurrentView != null) {
mCurrentView.clearAnimation();
}
// load the new mCurrentView from our adapter
mPreviousView = mCurrentView;
mCurrentView = mAdapter.getView(childIndex, null, this);
if (mPreviousView != mCurrentView) {
addViewInLayout(mCurrentView, 0, makeLayoutParams(), true);
mCurrentView.bringToFront();
}
// Animate as necessary
if (mPreviousView != null && mPreviousView != mCurrentView) {
if (animate && mOutAnimation != null) {
mPreviousView.startAnimation(mOutAnimation);
}
// This line results in the view becoming invisible *after*
// the above animation is complete, or, if there is no animation
// then it becomes invisble immediately
mPreviousView.setVisibility(View.GONE);
}
if (mCurrentView != null && animate && mInAnimation != null) {
mCurrentView.startAnimation(mInAnimation);
}
mFirstTime = false;
if (!onLayout) {
requestLayout();
invalidate();
} else {
// If the Adapter tries to layout the current view when we get it using getView above
// the layout will end up being ignored since we are currently laying out, so
// we post a delayed requestLayout and invalidate
mMainQueue.post(new Runnable() {
@Override
public void run() {
mCurrentView.requestLayout();
mCurrentView.invalidate();
}
});
}
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
boolean dataChanged = mDataChanged;
if (dataChanged) {
handleDataChanged();
// if the data changes, mWhichChild might be out of the bounds of the adapter
// in this case, we reset mWhichChild to the beginning
if (mWhichChild >= mAdapter.getCount())
mWhichChild = 0;
showOnly(mWhichChild, true, true);
}
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
int childRight = mPaddingLeft + child.getMeasuredWidth();
int childBottom = mPaddingTop + child.getMeasuredHeight();
child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom);
}
mDataChanged = false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getChildCount();
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
Log.v(TAG, "onMeasure");
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.width = widthSpecSize - mPaddingLeft - mPaddingRight;
lp.height = heightSpecSize - mPaddingTop - mPaddingBottom;
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
MeasureSpec.EXACTLY);
int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childheightMeasureSpec);
}
setMeasuredDimension(widthSpecSize, heightSpecSize);
}
/**
* 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);
}
/**
* Returns the View corresponding to the currently displayed child.
*
* @return The View currently displayed.
*
* @see #getDisplayedChild()
*/
public View getCurrentView() {
return mCurrentView;
}
/**
* 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));
}
/**
* Indicates whether the current View should be animated the first time
* the ViewAnimation 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 Adapter getAdapter() {
return mAdapter;
}
@Override
public void setAdapter(Adapter adapter) {
mAdapter = adapter;
if (mAdapter != null) {
if (mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
}
}
/**
* Sets up this AdapterViewAnimator to use a remote views adapter which connects to a RemoteViewsService
* through the specified intent.
* @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
*/
@android.view.RemotableViewMethod
public void setRemoteViewsAdapter(Intent intent) {
mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this);
}
@Override
public void setSelection(int position) {
setDisplayedChild(position);
}
@Override
public View getSelectedView() {
return mCurrentView;
}
/**
* Called back when the adapter connects to the RemoteViewsService.
*/
public void onRemoteAdapterConnected() {
if (mRemoteViewsAdapter != mAdapter) {
setAdapter(mRemoteViewsAdapter);
}
}
/**
* Called back when the adapter disconnects from the RemoteViewsService.
*/
public void onRemoteAdapterDisconnected() {
if (mRemoteViewsAdapter != mAdapter) {
mRemoteViewsAdapter = null;
setAdapter(mRemoteViewsAdapter);
}
}
}