首页 > 系统 > Android > 正文

Android酷炫动画效果之3D星体旋转效果

2019-10-22 18:11:29
字体:
来源:转载
供稿:网友

在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类并自定义一些方法,具体实现和使用请参照Android实现CoverFlow效果控件的实例代码。

本文要实现的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/240021.html">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)  {  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);  }  } } 

Demo测试类AndroidActivity.java

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);  if (getIntent() != null) {  initSelection = 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);  } } 

效果展示

android,3D星体旋转,3D旋转,android旋转,android旋转动画

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对VEVB武林网的支持。


注:相关教程知识阅读请移步到Android开发频道。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表