轉載自:http://blog.csdn.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一些控件屬性
The Rotator Class
如果你去查看Scroller類方法,你會發現它定義了兩種操作模式:滑動模式和拋動作,用來計算當前相對於給出的起始位置的偏移量,我們需要移除一些不需要的成員變量,添加我們自己的成員,並且修改相應的計算方法
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;