温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Android如何实现3D星体旋转效果

发布时间:2021-07-21 14:31:45 来源:亿速云 阅读:213 作者:小新 栏目:移动开发

这篇文章主要介绍Android如何实现3D星体旋转效果,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

在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: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星体旋转效果

以上是“Android如何实现3D星体旋转效果”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注亿速云行业资讯频道!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI