Android UI系列之3D星体旋转效果
转载自:http://blog.****.net/johnwcheung/article/details/52496652
在Android中,如果想要实现3D动画效果一般有两种选择:一是使用Open GL ES,二是使用Camera。Open GL ES使用起来太过复杂,一般是用于比较高级的3D特效或游戏,并且这个也不是开源的,像比较简单的一些3D效果,使用Camera就足够了。
一些熟知的android 3D动画如对某个View进行旋转或翻转的 Rotate3dAnimation类,还有使用Gallery( Gallery目前已过时,现在都推荐使用 HorizontalScrollView或 RecyclerView替代其实现相应功能) 实现的3D画廊效果等,当然有一些特效要通过伪3D变换来实现,比如CoverFlow效果,它使用标准Android 2D库,还是继承的Gallery类并自定义一些方法,具体实现和使用请参照http://www.cnblogs.com/zealotrouge/p/3380682.html。
本文要实现的3D星体旋转效果也是从这个CoverFlow演绎而来,不过CoverFlow只是对图像进行转动,我这里要实现的效果是要对所有的View进行类似旋转木马的转动,并且CoverFlow还存在很多已知bug,所以我这里需要重写一些类,并且将Scroller类用Rotator类替代,使界面看起来具有滚动效果,实际上是在转动一组图像。
首先我们需要自定义控件的一些属性,我们将控件取名Carousel,需要设置子项的最小个数和最大个数、当前选中项以及定义旋转角度等,attrs.xml
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <declare-styleable name="Carousel">
- <attr name="android:gravity" />
- <attr name="android:animationDuration" />
- <attr name="UseReflection" format="boolean" />
- <attr name="Items" format="integer" />
- <attr name="SelectedItem" format="integer" />
- <attr name="maxTheta" format="float" />
- <attr name="minQuantity" format="integer" />
- <attr name="maxQuantity" format="integer" />
- <attr name="Names" format="string" />
- </declare-styleable>
- </resources>
The CarouselImageView Class
这个类装载控件子项在3D空间的位置、子项的索引和当前子项的角度,通过实现Comparable接口,帮助我们确定子项绘制的顺序
- package com.john.carousel.lib;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.widget.ImageView;
- public class CarouselImageView extends ImageView implements Comparable<CarouselImageView>
- {
- private int index;
- private float currentAngle;
- private float x;
- private float y;
- private float z;
- private boolean drawn;
- public CarouselImageView(Context context)
- {
- this(context, null, 0);
- }
- public CarouselImageView(Context context, AttributeSet attrs)
- {
- this(context, attrs, 0);
- }
- public CarouselImageView(Context context, AttributeSet attrs, int defStyle)
- {
- super(context, attrs, defStyle);
- }
- public void setIndex(int index)
- {
- this.index = index;
- }
- public int getIndex()
- {
- return index;
- }
- public void setCurrentAngle(float currentAngle)
- {
- this.currentAngle = currentAngle;
- }
- public float getCurrentAngle()
- {
- return currentAngle;
- }
- public int compareTo(CarouselImageView another)
- {
- return (int) (another.z - this.z);
- }
- public void setX(float x)
- {
- this.x = x;
- }
- public float getX()
- {
- return x;
- }
- public void setY(float y)
- {
- this.y = y;
- }
- public float getY()
- {
- return y;
- }
- public void setZ(float z)
- {
- this.z = z;
- }
- public float getZ()
- {
- return z;
- }
- public void setDrawn(boolean drawn)
- {
- this.drawn = drawn;
- }
- public boolean isDrawn()
- {
- return drawn;
- }
- }
The Carousel Item Class
这个类简化我上面定义的 CarouselImageView一些控件属性- package com.john.carousel.lib;
- import android.content.Context;
- import android.graphics.Bitmap;
- import android.graphics.Matrix;
- import android.util.Log;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.widget.FrameLayout;
- import android.widget.ImageView;
- import android.widget.TextView;
- public class CarouselItem extends FrameLayout implements Comparable<CarouselItem>
- {
- public ImageView mImage;
- public TextView mText, mTextUp;
- public Context context;
- public int index;
- public float currentAngle;
- public float itemX;
- public float itemY;
- public float itemZ;
- public float degX;
- public float degY;
- public float degZ;
- public boolean drawn;
- // It's needed to find screen coordinates
- private Matrix mCIMatrix;
- public CarouselItem(Context context)
- {
- super(context);
- this.context = context;
- FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
- this.setLayoutParams(params);
- LayoutInflater inflater = LayoutInflater.from(context);
- View itemTemplate = inflater.inflate(R.layout.carousel_item, this, true);
- mImage = (ImageView) itemTemplate.findViewById(R.id.item_image);
- mText = (TextView) itemTemplate.findViewById(R.id.item_text);
- mTextUp = (TextView) itemTemplate.findViewById(R.id.item_text_up);
- }
- public void setTextColor(int i)
- {
- this.mText.setTextColor(context.getResources().getColorStateList(i));
- this.mTextUp.setTextColor(context.getResources().getColorStateList(i));
- }
- public String getName()
- {
- return mText.getText().toString();
- }
- public void setIndex(int index)
- {
- this.index = index;
- }
- public int getIndex()
- {
- return index;
- }
- public void setCurrentAngle(float currentAngle)
- {
- if (index == 0 && currentAngle > 5)
- {
- Log.d("", "");
- }
- this.currentAngle = currentAngle;
- }
- public float getCurrentAngle()
- {
- return currentAngle;
- }
- public int compareTo(CarouselItem another)
- {
- return (int) (another.itemZ - this.itemZ);
- }
- public void setItemX(float x)
- {
- this.itemX = x;
- }
- public float getItemX()
- {
- return itemX;
- }
- public void setItemY(float y)
- {
- this.itemY = y;
- }
- public float getItemY()
- {
- return itemY;
- }
- public void setItemZ(float z)
- {
- this.itemZ = z;
- }
- public float getItemZ()
- {
- return itemZ;
- }
- public float getDegX()
- {
- return degX;
- }
- public void setDegX(float degX)
- {
- this.degX = degX;
- }
- public float getDegY()
- {
- return degY;
- }
- public void setDegY(float degY)
- {
- this.degY = degY;
- }
- public float getDegZ()
- {
- return degZ;
- }
- public void setDegZ(float degZ)
- {
- this.degZ = degZ;
- }
- public void setDrawn(boolean drawn)
- {
- this.drawn = drawn;
- }
- public boolean isDrawn()
- {
- return drawn;
- }
- public void setImageBitmap(Bitmap bitmap)
- {
- mImage.setImageBitmap(bitmap);
- }
- public void setText(int i)
- {
- String s = context.getResources().getString(i);
- mText.setText(s);
- mTextUp.setText(s);
- }
- public void setText(String txt)
- {
- mText.setText(txt);
- mTextUp.setText(txt);
- }
- Matrix getCIMatrix()
- {
- return mCIMatrix;
- }
- void setCIMatrix(Matrix mMatrix)
- {
- this.mCIMatrix = mMatrix;
- }
- public void setImage(int i)
- {
- mImage.setImageDrawable(context.getResources().getDrawable(i));
- }
- public void setVisiblity(int id)
- {
- if (id == 0)
- {
- mText.setVisibility(View.INVISIBLE);
- mTextUp.setVisibility(View.VISIBLE);
- }
- else
- {
- mTextUp.setVisibility(View.INVISIBLE);
- mText.setVisibility(View.VISIBLE);
- }
- }
- }
The Rotator Class
如果你去查看Scroller类方法,你会发现它定义了两种操作模式:滑动模式和抛动作,用来计算当前相对于给出的起始位置的偏移量,我们需要移除一些不需要的成员变量,添加我们自己的成员,并且修改相应的计算方法- package com.john.carousel.lib;
- import android.content.Context;
- import android.view.animation.AnimationUtils;
- /**
- * This class encapsulates rotation. The duration of the rotation can be passed
- * in the constructor and specifies the maximum time that the rotation animation
- * should take. Past this time, the rotation is automatically moved to its final
- * stage and computeRotationOffset() will always return false to indicate that
- * scrolling is over.
- */
- public class Rotator
- {
- private float mStartAngle;
- private float mCurrAngle;
- private long mStartTime;
- private long mDuration;
- private float mDeltaAngle;
- private boolean mFinished;
- private int direction;
- private float mCurrDeg;
- public Rotator(Context context)
- {
- mFinished = true;
- }
- public final boolean isFinished()
- {
- return mFinished;
- }
- /**
- * Force the finished field to a particular value.
- *
- * @param finished
- * The new finished value.
- */
- public final void forceFinished(boolean finished)
- {
- mFinished = finished;
- }
- /**
- * Returns how long the scroll event will take, in milliseconds.
- *
- * @return The duration of the scroll in milliseconds.
- */
- public final long getDuration()
- {
- return mDuration;
- }
- /**
- * Returns the current X offset in the scroll.
- *
- * @return The new X offset as an absolute distance from the origin.
- */
- public final float getCurrAngle()
- {
- return mCurrAngle;
- }
- public final float getStartAngle()
- {
- return mStartAngle;
- }
- /**
- * Returns the time elapsed since the beginning of the scrolling.
- *
- * @return The elapsed time in milliseconds.
- */
- public int timePassed()
- {
- return (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
- }
- public int getdirection()
- {
- return this.direction;
- }
- public float getCurrDeg()
- {
- return this.mCurrDeg;
- }
- /**
- * Extend the scroll animation.
- */
- public void extendDuration(int extend)
- {
- int passed = timePassed();
- mDuration = passed + extend;
- mFinished = false;
- }
- /**
- * Stops the animation. Contrary to {@link #forceFinished(boolean)},
- * aborting the animating cause the scroller to move to the final x and y
- * position
- *
- * @see #forceFinished(boolean)
- */
- public void abortAnimation()
- {
- mFinished = true;
- }
- /**
- * Call this when you want to know the new location. If it returns true, the
- * animation is not yet finished. loc will be altered to provide the new
- * location.
- */
- public boolean computeAngleOffset()
- {
- if (mFinished)
- {
- return false;
- }
- long systemClock = AnimationUtils.currentAnimationTimeMillis();
- long timePassed = systemClock - mStartTime;
- if (timePassed < mDuration)
- {
- float sc = (float) timePassed / mDuration;
- mCurrAngle = mStartAngle + Math.round(mDeltaAngle * sc);
- mCurrDeg = direction == 0 ? (Math.round(360 * sc)) : (Math.round(-360 * sc));
- return true;
- }
- else
- {
- mCurrAngle = mStartAngle + mDeltaAngle;
- mCurrDeg = direction == 0 ? 360 : -360;
- mFinished = true;
- return false;
- }
- }
- public void startRotate(float startAngle, float dAngle, int duration, int direction)
- {
- mFinished = false;
- mDuration = duration;
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
- mStartAngle = startAngle;
- mDeltaAngle = dAngle;
- this.direction = direction;
- }
- }
The CarouselSpinner Class
- package com.john.carousel.lib;
- import android.content.Context;
- import android.database.DataSetObserver;
- import android.graphics.Rect;
- import android.os.Parcel;
- import android.os.Parcelable;
- import android.util.AttributeSet;
- import android.util.SparseArray;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.AbsSpinner;
- import android.widget.SpinnerAdapter;
- public abstract class CarouselSpinner extends CarouselAdapter<SpinnerAdapter>
- {
- SpinnerAdapter mAdapter;
- int mHeightMeasureSpec;
- int mWidthMeasureSpec;
- boolean mBlockLayoutRequests;
- int mSelectionLeftPadding = 0;
- int mSelectionTopPadding = 0;
- int mSelectionRightPadding = 0;
- int mSelectionBottomPadding = 0;
- final Rect mSpinnerPadding = new Rect();
- final RecycleBin mRecycler = new RecycleBin();
- private DataSetObserver mDataSetObserver;
- public CarouselSpinner(Context context)
- {
- super(context);
- initCarouselSpinner();
- }
- public CarouselSpinner(Context context, AttributeSet attrs)
- {
- this(context, attrs, 0);
- }
- public CarouselSpinner(Context context, AttributeSet attrs, int defStyle)
- {
- super(context, attrs, defStyle);
- initCarouselSpinner();
- }
- /**
- * Common code for different constructor flavors
- */
- private void initCarouselSpinner()
- {
- setFocusable(true);
- setWillNotDraw(false);
- }
- @Override
- public SpinnerAdapter getAdapter()
- {
- return mAdapter;
- }
- @Override
- public void setAdapter(SpinnerAdapter adapter)
- {
- if (null != mAdapter)
- {
- mAdapter.unregisterDataSetObserver(mDataSetObserver);
- resetList();
- }
- mAdapter = adapter;
- mOldSelectedPosition = INVALID_POSITION;
- mOldSelectedRowId = INVALID_ROW_ID;
- if (mAdapter != null)
- {
- mOldItemCount = mItemCount;
- mItemCount = mAdapter.getCount();
- checkFocus();
- mDataSetObserver = new AdapterDataSetObserver();
- mAdapter.registerDataSetObserver(mDataSetObserver);
- int position = mItemCount > 0 ? 0 : INVALID_POSITION;
- setSelectedPositionInt(position);
- setNextSelectedPositionInt(position);
- if (mItemCount == 0)
- {
- // Nothing selected
- checkSelectionChanged();
- }
- }
- else
- {
- checkFocus();
- resetList();
- // Nothing selected
- checkSelectionChanged();
- }
- requestLayout();
- }
- @Override
- public View getSelectedView()
- {
- if (mItemCount > 0 && mSelectedPosition >= 0)
- {
- return getChildAt(mSelectedPosition - mFirstPosition);
- }
- else
- {
- return null;
- }
- }
- /**
- * Jump directly to a specific item in the adapter data.
- */
- public void setSelection(int position, boolean animate)
- {
- // Animate only if requested position is already on screen somewhere
- boolean shouldAnimate = animate && mFirstPosition <= position && position <= mFirstPosition + getChildCount() - 1;
- setSelectionInt(position, shouldAnimate);
- }
- /**
- * Makes the item at the supplied position selected.
- *
- * @param position
- * Position to select
- * @param animate
- * Should the transition be animated
- *
- */
- void setSelectionInt(int position, boolean animate)
- {
- if (position != mOldSelectedPosition)
- {
- mBlockLayoutRequests = true;
- int delta = position - mSelectedPosition;
- setNextSelectedPositionInt(position);
- layout(delta, animate);
- mBlockLayoutRequests = false;
- }
- }
- abstract void layout(int delta, boolean animate);
- @Override
- public void setSelection(int position)
- {
- setSelectionInt(position, false);
- }
- /**
- * Clear out all children from the list
- */
- void resetList()
- {
- mDataChanged = false;
- mNeedSync = false;
- removeAllViewsInLayout();
- mOldSelectedPosition = INVALID_POSITION;
- mOldSelectedRowId = INVALID_ROW_ID;
- setSelectedPositionInt(INVALID_POSITION);
- setNextSelectedPositionInt(INVALID_POSITION);
- invalidate();
- }
- /**
- * @see android.view.View#measure(int, int)
- *
- * Figure out the dimensions of this Spinner. The width comes from the
- * widthMeasureSpec as Spinnners can't have their width set to
- * UNSPECIFIED. The height is based on the height of the selected item
- * plus padding.
- */
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
- {
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int widthSize;
- int heightSize;
- mSpinnerPadding.left = getPaddingLeft() > mSelectionLeftPadding ? getPaddingLeft() : mSelectionLeftPadding;
- mSpinnerPadding.top = getPaddingTop() > mSelectionTopPadding ? getPaddingTop() : mSelectionTopPadding;
- mSpinnerPadding.right = getPaddingRight() > mSelectionRightPadding ? getPaddingRight() : mSelectionRightPadding;
- mSpinnerPadding.bottom = getPaddingBottom() > mSelectionBottomPadding ? getPaddingBottom() : mSelectionBottomPadding;
- if (mDataChanged)
- {
- handleDataChanged();
- }
- int preferredHeight = 0;
- int preferredWidth = 0;
- boolean needsMeasuring = true;
- int selectedPosition = getSelectedItemPosition();
- if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount())
- {
- // Try looking in the recycler. (Maybe we were measured once
- // already)
- View view = mRecycler.get(selectedPosition);
- if (view == null)
- {
- // Make a new one
- view = mAdapter.getView(selectedPosition, null, this);
- }
- if (view != null)
- {
- // Put in recycler for re-measuring and/or layout
- mRecycler.put(selectedPosition, view);
- }
- if (view != null)
- {
- if (view.getLayoutParams() == null)
- {
- mBlockLayoutRequests = true;
- view.setLayoutParams(generateDefaultLayoutParams());
- mBlockLayoutRequests = false;
- }
- measureChild(view, widthMeasureSpec, heightMeasureSpec);
- preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
- preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
- needsMeasuring = false;
- }
- }
- if (needsMeasuring)
- {
- // No views -- just use padding
- preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom;
- if (widthMode == MeasureSpec.UNSPECIFIED)
- {
- preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right;
- }
- }
- preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight());
- preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth());
- heightSize = resolveSize(preferredHeight, heightMeasureSpec);
- widthSize = resolveSize(preferredWidth, widthMeasureSpec);
- setMeasuredDimension(widthSize, heightSize);
- mHeightMeasureSpec = heightMeasureSpec;
- mWidthMeasureSpec = widthMeasureSpec;
- }
- int getChildHeight(View child)
- {
- return child.getMeasuredHeight();
- }
- int getChildWidth(View child)
- {
- return child.getMeasuredWidth();
- }
- @Override
- protected ViewGroup.LayoutParams generateDefaultLayoutParams()
- {
- /*
- * Carousel expects Carousel.LayoutParams.
- */
- return new Carousel.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- }
- void recycleAllViews()
- {
- final int childCount = getChildCount();
- final CarouselSpinner.RecycleBin recycleBin = mRecycler;
- final int position = mFirstPosition;
- // All views go in recycler
- for (int i = 0; i < childCount; i++)
- {
- View v = getChildAt(i);
- int index = position + i;
- recycleBin.put(index, v);
- }
- }
- /**
- * Override to prevent spamming ourselves with layout requests as we place
- * views
- *
- * @see android.view.View#requestLayout()
- */
- @Override
- public void requestLayout()
- {
- if (!mBlockLayoutRequests)
- {
- super.requestLayout();
- }
- }
- @Override
- public int getCount()
- {
- return mItemCount;
- }
- /**
- * Maps a point to a position in the list.
- *
- * @param x
- * X in local coordinate
- * @param y
- * Y in local coordinate
- * @return The position of the item which contains the specified point, or
- * {@link #INVALID_POSITION} if the point does not intersect an
- * item.
- */
- public int pointToPosition(int x, int y)
- {
- // All touch events are applied to selected item
- return mSelectedPosition;
- }
- static class SavedState extends BaseSavedState
- {
- long selectedId;
- int position;
- /**
- * Constructor called from {@link AbsSpinner#onSaveInstanceState()}
- */
- SavedState(Parcelable superState)
- {
- super(superState);
- }
- /**
- * Constructor called from {@link #CREATOR}
- */
- private SavedState(Parcel in)
- {
- super(in);
- selectedId = in.readLong();
- position = in.readInt();
- }
- @Override
- public void writeToParcel(Parcel out, int flags)
- {
- super.writeToParcel(out, flags);
- out.writeLong(selectedId);
- out.writeInt(position);
- }
- @Override
- public String toString()
- {
- return "AbsSpinner.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " selectedId=" + selectedId + " position=" + position + "}";
- }
- public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>()
- {
- public SavedState createFromParcel(Parcel in)
- {
- return new SavedState(in);
- }
- public SavedState[] newArray(int size)
- {
- return new SavedState[size];
- }
- };
- }
- @Override
- public Parcelable onSaveInstanceState()
- {
- Parcelable superState = super.onSaveInstanceState();
- SavedState ss = new SavedState(superState);
- ss.selectedId = getSelectedItemId();
- if (ss.selectedId >= 0)
- {
- ss.position = getSelectedItemPosition();
- }
- else
- {
- ss.position = INVALID_POSITION;
- }
- return ss;
- }
- @Override
- public void onRestoreInstanceState(Parcelable state)
- {
- SavedState ss = (SavedState) state;
- super.onRestoreInstanceState(ss.getSuperState());
- if (ss.selectedId >= 0)
- {
- mDataChanged = true;
- mNeedSync = true;
- mSyncRowId = ss.selectedId;
- mSyncPosition = ss.position;
- mSyncMode = SYNC_SELECTED_POSITION;
- requestLayout();
- }
- }
- class RecycleBin
- {
- private final SparseArray<View> mScrapHeap = new SparseArray<View>();
- public void put(int position, View v)
- {
- mScrapHeap.put(position, v);
- }
- View get(int position)
- {
- // System.out.print("Looking for " + position);
- View result = mScrapHeap.get(position);
- if (result != null)
- {
- // System.out.println(" HIT");
- mScrapHeap.delete(position);
- }
- else
- {
- // System.out.println(" MISS");
- }
- return result;
- }
- void clear()
- {
- final SparseArray<View> scrapHeap = mScrapHeap;
- final int count = scrapHeap.size();
- for (int i = 0; i < count; i++)
- {
- final View view = scrapHeap.valueAt(i);
- if (view != null)
- {
- removeDetachedView(view, true);
- }
- }
- scrapHeap.clear();
- }
- }
- }
The CarouselAdapter Class
[The CarouselAdapter vs. AdapterView]
The only changes are in updateEmptyStatus method where unavailable variables were replaced with their getters.
The Carousel Class
- package com.john.carousel.lib;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.Comparator;
- import android.annotation.SuppressLint;
- import android.content.Context;
- import android.content.res.TypedArray;
- import android.graphics.Bitmap;
- import android.graphics.Camera;
- import android.graphics.Matrix;
- import android.graphics.Rect;
- import android.graphics.drawable.BitmapDrawable;
- import android.graphics.drawable.Drawable;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.Gravity;
- import android.view.KeyEvent;
- import android.view.View;
- import android.view.ViewGroup;
- import android.view.animation.Transformation;
- import android.widget.BaseAdapter;
- public class Carousel extends CarouselSpinner implements Constants
- {
- private int mAnimationDuration = 100;
- private int mAnimationDurationMin = 50;
- private Camera mCamera = null;
- private FlingRotateRunnable mFlingRunnable = null;
- private int mGravity = 0;
- private View mSelectedChild = null;
- private static int mSelectedItemIndex = 2;
- private boolean mShouldStopFling = false;
- private static final int LEFT = 0;
- private static final int RIGHT = 1;
- /**
- * If true, do not callback to item selected listener.
- */
- private boolean mSuppressSelectionChanged = false;
- private float mTheta = 0.0f;
- private boolean isFocus = true;
- private ImageAdapter adapter = null;
- private static final int ONE_ITEM = 1;
- private CarouselItemClickListener callback = null;
- public Carousel(Context context)
- {
- this(context, null);
- }
- public Carousel(Context context, AttributeSet attrs)
- {
- this(context, attrs, 0);
- }
- public Carousel(Context context, AttributeSet attrs, int defStyle)
- {
- super(context, attrs, defStyle);
- setChildrenDrawingOrderEnabled(false);
- setStaticTransformationsEnabled(true);
- TypedArray arr = getContext().obtainStyledAttributes(attrs, R.styleable.Carousel);
- int imageArrayID = arr.getResourceId(R.styleable.Carousel_Items, -1);
- TypedArray images = getResources().obtainTypedArray(imageArrayID);
- int namesForItems = arr.getResourceId(R.styleable.Carousel_Names, -1);
- TypedArray names = null;
- if (namesForItems != -1)
- {
- names = getResources().obtainTypedArray(namesForItems);
- }
- initView(images, names);
- arr.recycle();
- images.recycle();
- if (names != null)
- {
- names.recycle();
- }
- }
- private void initView(TypedArray images, TypedArray names)
- {
- // TODO Auto-generated method stub
- mCamera = new Camera();
- mFlingRunnable = new FlingRotateRunnable();
- mTheta = (float) (15.0f * (Math.PI / 180.0));
- adapter = new ImageAdapter(getContext());
- adapter.setImages(images, names);
- setAdapter(adapter);
- setSelectedPositionInt(mSelectedItemIndex);
- }
- @Override
- protected int computeHorizontalScrollExtent()
- {
- // Only 1 item is considered to be selected
- return ONE_ITEM;
- }
- @Override
- protected int computeHorizontalScrollOffset()
- {
- // Current scroll position is the same as the selected position
- return mSelectedPosition;
- }
- @Override
- protected int computeHorizontalScrollRange()
- {
- // Scroll range is the same as the item count
- return mItemCount;
- }
- public void setFocusFlag(boolean flag)
- {
- this.isFocus = flag;
- adapter.notifyDataSetChanged();
- }
- public boolean getFocusFlag()
- {
- return this.isFocus;
- }
- public void setSelected(int index)
- {
- setNextSelectedPositionInt(index);
- mSelectedItemIndex = index;
- }
- public void setCarouselItemClickCallBack(CarouselItemClickListener listener)
- {
- callback = listener;
- }
- public interface CarouselItemClickListener
- {
- public void CarouselClickCallBack(int itemPosition);
- }
- /**
- * Handles left, right, and clicking
- *
- * @see android.view.View#onKeyDown
- */
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event)
- {
- switch (keyCode)
- {
- case KEY_OK:
- case KEY_CENTER:
- callback.CarouselClickCallBack(mSelectedItemIndex);
- return true;
- case KEY_LEFT:
- toNextLeftItem();
- return true;
- case KEY_RIGHT:
- toNextRightItem();
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
- @Override
- protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)
- {
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- /*
- * The gallery shows focus by focusing the selected item. So, give focus
- * to our selected item instead. We steal keys from our selected item
- * elsewhere.
- */
- if (gainFocus && mSelectedChild != null)
- {
- mSelectedChild.requestFocus(direction);
- }
- }
- @Override
- protected boolean checkLayoutParams(ViewGroup.LayoutParams p)
- {
- return p instanceof LayoutParams;
- }
- @Override
- protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p)
- {
- return new LayoutParams(p);
- }
- @Override
- public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)
- {
- return new LayoutParams(getContext(), attrs);
- }
- @Override
- protected void dispatchSetPressed(boolean pressed)
- {
- if (mSelectedChild != null)
- {
- mSelectedChild.setPressed(pressed);
- }
- }
- @Override
- public boolean dispatchKeyEvent(KeyEvent event)
- {
- return false;
- }
- /**
- * Transform an item depending on it's coordinates
- */
- @Override
- protected boolean getChildStaticTransformation(View child, Transformation transformation)
- {
- transformation.clear();
- transformation.setTransformationType(Transformation.TYPE_MATRIX);
- // Center of the view
- float centerX = (float) getWidth() / 2, centerY = (float) getHeight() / 2;
- mCamera.save();
- final Matrix matrix = transformation.getMatrix();
- mCamera.translate(((CarouselItem) child).getItemX(), ((CarouselItem) child).getItemY(), ((CarouselItem) child).getItemZ());
- mCamera.getMatrix(matrix);
- matrix.preTranslate(-centerX, -centerY);
- matrix.postTranslate(centerX, centerY);
- float[] values = new float[9];
- matrix.getValues(values);
- mCamera.restore();
- Matrix mm = new Matrix();
- mm.setValues(values);
- ((CarouselItem) child).setCIMatrix(mm);
- child.invalidate();
- return true;
- }
- // CarouselAdapter overrides
- /**
- * Setting up images
- */
- void layout(int delta, boolean animate)
- {
- Log.d("ORDER", "layout");
- if (mDataChanged)
- {
- handleDataChanged();
- }
- if (mNextSelectedPosition >= 0)
- {
- setSelectedPositionInt(mNextSelectedPosition);
- }
- recycleAllViews();
- detachAllViewsFromParent();
- int count = getAdapter().getCount();
- float angleUnit = 360.0f / count;
- float angleOffset = mSelectedPosition * angleUnit;
- for (int i = 0; i < getAdapter().getCount(); i++)
- {
- float angle = angleUnit * i - angleOffset;
- if (angle < 0.0f)
- {
- angle = 360.0f + angle;
- }
- makeAndAddView(i, angle);
- }
- mRecycler.clear();
- invalidate();
- setNextSelectedPositionInt(mSelectedPosition);
- checkSelectionChanged();
- mNeedSync = false;
- updateSelectedItemMetadata();
- }
- /**
- * Setting up images after layout changed
- */
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b)
- {
- super.onLayout(changed, l, t, r, b);
- Log.d("ORDER", "onLayout");
- /*
- * Remember that we are in layout to prevent more layout request from
- * being generated.
- */
- mInLayout = true;
- layout(0, false);
- mInLayout = false;
- }
- @Override
- void selectionChanged()
- {
- if (!mSuppressSelectionChanged)
- {
- super.selectionChanged();
- }
- }
- @Override
- void setSelectedPositionInt(int position)
- {
- super.setSelectedPositionInt(position);
- super.setNextSelectedPositionInt(position);
- updateSelectedItemMetadata();
- }
- private class FlingRotateRunnable implements Runnable
- {
- private Rotator mRotator;
- private float mLastFlingAngle;
- public FlingRotateRunnable()
- {
- mRotator = new Rotator(getContext());
- }
- private void startCommon()
- {
- removeCallbacks(this);
- }
- public void startUsingDistance(float deltaAngle, int flag, int direction)
- {
- if (deltaAngle == 0)
- return;
- startCommon();
- mLastFlingAngle = 0;
- synchronized (this)
- {
- mRotator.startRotate(0.0f, -deltaAngle, flag == 0 ? mAnimationDuration : mAnimationDurationMin, direction);
- }
- post(this);
- }
- private void endFling(boolean scrollIntoSlots, int direction)
- {
- synchronized (this)
- {
- mRotator.forceFinished(true);
- }
- if (scrollIntoSlots)
- {
- scrollIntoSlots(direction);
- }
- }
- public void run()
- {
- Log.d("ORDER", "run");
- mShouldStopFling = false;
- final Rotator rotator;
- final float angle;
- final float deg;
- boolean more;
- int direction;
- synchronized (this)
- {
- rotator = mRotator;
- more = rotator.computeAngleOffset();
- angle = rotator.getCurrAngle();
- deg = rotator.getCurrDeg();
- direction = rotator.getdirection();
- }
- if (more && !mShouldStopFling)
- {
- Log.d("GETVIEW", "========go");
- float delta = mLastFlingAngle - angle;
- trackMotionScroll(delta, deg);
- mLastFlingAngle = angle;
- post(this);
- }
- else
- {
- Log.d("GETVIEW", "========end");
- float delta = mLastFlingAngle - angle;
- trackMotionScroll(delta, deg);
- mLastFlingAngle = 0.0f;
- endFling(false, direction);
- }
- }
- }
- private class ImageAdapter extends BaseAdapter
- {
- private Context mContext;
- private CarouselItem[] mImages;
- private int[] lightImages = { R.drawable.icons_light_network, R.drawable.icons_light_update, R.drawable.icons_light_app, R.drawable.icons_light_stb, R.drawable.icons_light_other,
- R.drawable.icons_light_wallpaper, R.drawable.icons_light_media };
- private final int[] normalImages = { R.drawable.icons_normal_network0, R.drawable.icons_normal_update0, R.drawable.icons_normal_app0, R.drawable.icons_normal_stb0,
- R.drawable.icons_normal_other0, R.drawable.icons_normal_wallpaper0, R.drawable.icons_normal_meida0 };
- private final int[] colors = { R.color.network_text_color, R.color.update_text_color, R.color.app_text_color, R.color.stb_text_color, R.color.other_text_color, R.color.wallpaper_text_color,
- R.color.media_text_color, R.color.text_color_white };
- // private final int[] names = { R.string.STR_NETWORK,
- // R.string.STR_UPDATE, R.string.STR_APP, R.string.STR_STB,
- // R.string.STR_OTHER, R.string.STR_WALLPAPER, R.string.STR_MEDIA };
- public ImageAdapter(Context c)
- {
- mContext = c;
- }
- public void setImages(TypedArray array, TypedArray names)
- {
- Drawable[] drawables = new Drawable[array.length()];
- mImages = new CarouselItem[array.length()];
- for (int i = 0; i < array.length(); i++)
- {
- drawables[i] = array.getDrawable(i);
- Bitmap originalImage = ((BitmapDrawable) drawables[i]).getBitmap();
- CarouselItem item = new CarouselItem(mContext);
- item.setIndex(i);
- item.setImageBitmap(originalImage);
- if (names != null)
- {
- item.setText(names.getString(i));
- }
- if (i == mSelectedItemIndex || (i + 6) % 7 == mSelectedItemIndex || (i + 1) % 7 == mSelectedItemIndex)
- {
- item.setVisiblity(1);
- }
- else
- {
- item.setVisiblity(0);
- }
- mImages[i] = item;
- }
- }
- public int getCount()
- {
- if (mImages == null)
- {
- return 0;
- }
- else
- {
- return mImages.length;
- }
- }
- public Object getItem(int position)
- {
- return position;
- }
- public long getItemId(int position)
- {
- return position;
- }
- public View getView(int position, View convertView, ViewGroup parent)
- {
- if (position == mSelectedItemIndex || (position + 6) % 7 == mSelectedItemIndex || (position + 1) % 7 == mSelectedItemIndex)
- {
- mImages[position].setVisiblity(1);
- }
- else
- {
- mImages[position].setVisiblity(0);
- }
- if (position == mSelectedItemIndex && isFocus)
- {
- mImages[position].setImage(lightImages[position]);
- mImages[position].setTextColor(colors[position]);
- }
- else
- {
- mImages[position].setImage(normalImages[position]);
- mImages[position].setTextColor(colors[7]);
- }
- Log.d("GETVIEW", position + ":getView");
- return mImages[position];
- }
- }
- @SuppressLint("FloatMath")
- private void Calculate3DPosition(CarouselItem child, int diameter, float angleOffset)
- {
- angleOffset = angleOffset * (float) (Math.PI / 180.0f);
- float x = -(float) (diameter / 2 * android.util.FloatMath.sin(angleOffset) * 1.05) + diameter / 2 - child.getWidth() / 2;
- float z = diameter / 2 * (1.0f - (float) android.util.FloatMath.cos(angleOffset));
- float y = -getHeight() / 2 + (float) (z * android.util.FloatMath.sin(mTheta)) + 120;
- child.setItemX(x);
- child.setItemZ(z);
- child.setItemY(y);
- }
- /**
- * Figure out vertical placement based on mGravity
- *
- * @param child
- * Child to place
- * @return Where the top of the child should be
- */
- private int calculateTop(View child, boolean duringLayout)
- {
- int myHeight = duringLayout ? getMeasuredHeight() : getHeight();
- int childHeight = duringLayout ? child.getMeasuredHeight() : child.getHeight();
- int childTop = 0;
- switch (mGravity)
- {
- case Gravity.TOP:
- childTop = mSpinnerPadding.top;
- break;
- case Gravity.CENTER_VERTICAL:
- int availableSpace = myHeight - mSpinnerPadding.bottom - mSpinnerPadding.top - childHeight;
- childTop = mSpinnerPadding.top + (availableSpace / 2);
- break;
- case Gravity.BOTTOM:
- childTop = myHeight - mSpinnerPadding.bottom - childHeight;
- break;
- }
- return childTop;
- }
- private void makeAndAddView(int position, float angleOffset)
- {
- Log.d("ORDER", "makeAndAddView");
- CarouselItem child;
- if (!mDataChanged)
- {
- child = (CarouselItem) mRecycler.get(position);
- if (child != null)
- {
- // Position the view
- setUpChild(child, child.getIndex(), angleOffset);
- }
- else
- {
- // Nothing found in the recycler -- ask the adapter for a view
- child = (CarouselItem) mAdapter.getView(position, null, this);
- Log.d("GETVIEW", "makeAndAddView1");
- // Position the view
- setUpChild(child, child.getIndex(), angleOffset);
- }
- return;
- }
- // Nothing found in the recycler -- ask the adapter for a view
- child = (CarouselItem) mAdapter.getView(position, null, this);
- Log.d("GETVIEW", "makeAndAddView2");
- // Position the view
- setUpChild(child, child.getIndex(), angleOffset);
- }
- private void onFinishedMovement()
- {
- if (mSuppressSelectionChanged)
- {
- mSuppressSelectionChanged = false;
- super.selectionChanged();
- }
- checkSelectionChanged();
- invalidate();
- }
- /**
- * Brings an item with nearest to 0 degrees angle to this angle and sets it
- * selected
- */
- private void scrollIntoSlots(int direction)
- {
- Log.d("ORDER", "scrollIntoSlots");
- float angle;
- int position;
- ArrayList<CarouselItem> arr = new ArrayList<CarouselItem>();
- for (int i = 0; i < getAdapter().getCount(); i++)
- {
- arr.add(((CarouselItem) getAdapter().getView(i, null, null)));
- Log.d("GETVIEW", "scrollIntoSlots");
- }
- Collections.sort(arr, new Comparator<CarouselItem>()
- {
- public int compare(CarouselItem c1, CarouselItem c2)
- {
- int a1 = (int) c1.getCurrentAngle();
- if (a1 > 180)
- {
- a1 = 360 - a1;
- }
- int a2 = (int) c2.getCurrentAngle();
- if (a2 > 180)
- {
- a2 = 360 - a2;
- }
- return (a1 - a2);
- }
- });
- angle = arr.get(0).getCurrentAngle();
- if (angle > 180.0f)
- {
- angle = -(360.0f - angle);
- }
- if (Math.abs(angle) > 0.5f)
- {
- mFlingRunnable.startUsingDistance(-angle, 1, direction);
- }
- else
- {
- position = arr.get(0).getIndex();
- setSelectedPositionInt(position);
- onFinishedMovement();
- }
- }
- public int getIndex()
- {
- return mSelectedItemIndex;
- }
- private void resetIndex()
- {
- if (mSelectedItemIndex == 7)
- {
- mSelectedItemIndex = 0;
- }
- if (mSelectedItemIndex == -1)
- {
- mSelectedItemIndex = 6;
- }
- }
- public void toNextRightItem()
- {
- mSelectedItemIndex = mSelectedItemIndex - 1;
- resetIndex();
- scrollToChild(mSelectedItemIndex, RIGHT);
- setSelectedPositionInt(mSelectedItemIndex);
- }
- public void toNextLeftItem()
- {
- mSelectedItemIndex = mSelectedItemIndex + 1;
- resetIndex();
- scrollToChild(mSelectedItemIndex, LEFT);
- setSelectedPositionInt(mSelectedItemIndex);
- }
- void scrollToChild(int i, int v)
- {
- Log.d("ORDER", "scrollToChild");
- CarouselItem view = (CarouselItem) getAdapter().getView(i, null, null);
- Log.d("GETVIEW", "scrollToChild");
- float angle = view.getCurrentAngle();
- Log.d("selectCurrentAngle", "Angle:" + angle);
- if (angle == 0)
- {
- return;
- }
- if (angle > 180.0f)
- {
- angle = 360.0f - angle;
- }
- else
- {
- angle = -angle;
- }
- mFlingRunnable.startUsingDistance(angle, 0, v);
- }
- public void setGravity(int gravity)
- {
- if (mGravity != gravity)
- {
- mGravity = gravity;
- requestLayout();
- }
- }
- private void setUpChild(CarouselItem child, int index, float angleOffset)
- {
- Log.d("ORDER", "setUpChild");
- // Ignore any layout parameters for child, use wrap content
- addViewInLayout(child, -1 /* index */, generateDefaultLayoutParams());
- child.setSelected(index == mSelectedPosition);
- int h;
- int w;
- int d;
- if (mInLayout)
- {
- w = child.getMeasuredWidth();
- h = child.getMeasuredHeight();
- d = getMeasuredWidth();
- }
- else
- {
- w = child.getMeasuredWidth();
- h = child.getMeasuredHeight();
- d = getWidth();
- }
- child.setCurrentAngle(angleOffset);
- child.measure(w, h);
- int childLeft;
- int childTop = calculateTop(child, true);
- childLeft = 0;
- child.layout(childLeft, childTop - 45, w, h);
- Calculate3DPosition(child, d, angleOffset);
- }
- /**
- * Tracks a motion scroll. In reality, this is used to do just about any
- * movement to items (touch scroll, arrow-key scroll, set an item as
- * selected).
- */
- void trackMotionScroll(float deltaAngle, float deg)
- {
- Log.d("ORDER", "trackMotionScroll");
- for (int i = 0; i < getAdapter().getCount(); i++)
- {
- CarouselItem child = (CarouselItem) getAdapter().getView(i, null, null);
- Log.d("GETVIEW", "trackMotionScroll");
- float angle = child.getCurrentAngle();
- angle += deltaAngle;
- while (angle > 360.0f)
- {
- angle -= 360.0f;
- }
- while (angle < 0.0f)
- {
- angle += 360.0f;
- }
- child.setCurrentAngle(angle);
- child.setDegY(deg);
- Calculate3DPosition(child, getWidth(), angle);
- }
- mRecycler.clear();
- invalidate();
- }
- private void updateSelectedItemMetadata()
- {
- View oldSelectedChild = mSelectedChild;
- View child = mSelectedChild = getChildAt(mSelectedPosition - mFirstPosition);
- if (child == null)
- {
- return;
- }
- child.setSelected(true);
- child.setFocusable(true);
- if (hasFocus())
- {
- child.requestFocus();
- }
- if (oldSelectedChild != null)
- {
- oldSelectedChild.setSelected(false);
- oldSelectedChild.setFocusable(false);
- }
- }
- }
- package com.john.carousel.test;
- import com.john.carousel.lib.Carousel;
- import com.john.carousel.lib.Carousel.CarouselItemClickListener;
- import com.john.carousel.lib.CarouselAdapter;
- import com.john.carousel.lib.CarouselAdapter.cOnItemClickListener;
- import com.john.carousel.lib.Constants;
- import com.john.carousel.lib.R;
- import android.app.Activity;
- import android.os.Bundle;
- import android.util.Log;
- import android.view.Gravity;
- import android.view.KeyEvent;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.View.OnKeyListener;
- import android.widget.LinearLayout;
- public class AndroidActivity extends Activity implements CarouselItemClickListener, Constants
- {
- private Carousel carousel;
- private final String TAG = AndroidActivity.class.getSimpleName();
- private LinearLayout layoutMain = null;
- private final int NETWORK = 0;
- private final int UPDATE = 1;
- private final int APK = 2;
- private final int STB = 3;
- private final int OTHER = 4;
- private final int WALLPAPER = 5;
- private final int MEDIA = 6;
- private int initSelection = 2;
- private long lastClickTime, currClickTime;
- @Override
- protected void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- View mainView = LayoutInflater.from(this).inflate(R.layout.activity_android, null);
- setContentView(mainView);
- initSelection = this.getIntent().getExtras().getInt("selection", 2);
- if (initSelection >= 6 || initSelection <= 0)
- {
- initSelection = initSelection % 7;
- }
- buildView();
- }
- private void buildView()
- {
- carousel = (Carousel) findViewById(R.id.carousel);
- layoutMain = (LinearLayout) findViewById(R.id.layoutMain);
- layoutMain.setBackground(getResources().getDrawable(R.drawable.main_back00));
- carousel.setDrawingCacheEnabled(true);
- carousel.setGravity(Gravity.TOP);
- carousel.setFocusFlag(true);
- carouselGetFocus();
- carousel.setSelected(initSelection);
- carousel.setCarouselItemClickCallBack(this);
- carousel.setOnItemClickListener(new cOnItemClickListener()
- {
- @Override
- public void onItemClick(CarouselAdapter<?> parent, View view, int position, long id)
- {
- onItemClickOrCallback(position);
- }
- });
- carousel.setOnKeyListener(new OnKeyListener()
- {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event)
- {
- if (event.equals(KeyEvent.ACTION_DOWN))
- {
- switch (keyCode)
- {
- case KEY_LEFT:
- carousel.toNextLeftItem();
- break;
- case KEY_RIGHT:
- carousel.toNextRightItem();
- break;
- case KEY_OK:
- case KEY_CENTER:
- onItemClickOrCallback(carousel.getIndex());
- break;
- }
- }
- carouselGetFocus();
- return true;
- }
- });
- }
- private void onItemClickOrCallback(int position)
- {
- switch (position)
- {
- case NETWORK:
- break;
- case UPDATE:
- break;
- case APK:
- break;
- case STB:
- break;
- case OTHER:
- break;
- case WALLPAPER:
- break;
- case MEDIA:
- break;
- default:
- break;
- }
- }
- @Override
- public void CarouselClickCallBack(int itemPosition)
- {
- onItemClickOrCallback(itemPosition);
- }
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event)
- {
- switch (keyCode)
- {
- case KEY_OK:
- case KEY_CENTER:
- onItemClickOrCallback(carousel.getIndex());
- return true;
- case KEY_LEFT:
- if (carousel.getFocusFlag())
- {
- currClickTime = System.currentTimeMillis();
- if (currClickTime - lastClickTime > 200)
- {
- lastClickTime = currClickTime;
- carousel.toNextLeftItem();
- Log.d("selectedItemIndex", carousel.getIndex() + "");
- return true;
- }
- else
- {
- return true;
- }
- }
- break;
- case KEY_RIGHT:
- if (carousel.getFocusFlag())
- {
- currClickTime = System.currentTimeMillis();
- if (currClickTime - lastClickTime > 200)
- {
- lastClickTime = currClickTime;
- carousel.toNextRightItem();
- Log.d("selectedItemIndex", carousel.getIndex() + "");
- return true;
- }
- else
- {
- return true;
- }
- }
- break;
- case KEY_UP:
- carousel.setFocusFlag(false);
- carousel.clearFocus();
- carousel.setFocusable(false);
- carousel.setSelected(false);
- return true;
- case KEY_DOWN:
- if (!carousel.getFocusFlag())
- {
- Log.e(TAG, "KEY_DOWN");
- carouselGetFocus();
- }
- return true;
- case KEY_EXIT:
- return true;
- case KEY_VOLDOWN:
- case KEY_VOLUP:
- case KEY_MUTE:
- case KEY_VOLUME_MUTE:
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
- private void carouselGetFocus()
- {
- carousel.setFocusFlag(true);
- carousel.requestFocus();
- carousel.setFocusable(true);
- }
- }
效果展示
http://v.youku.com/v_show/id_XMTcyMDY3ODUxMg==.html