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);
 }
}

效果展示

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

(0)

相关推荐

  • Android TV开发:使用RecycleView实现横向的Listview并响应点击事件的代码

    本文讲述了Android TV开发:使用RecycleView实现横向的Listview并响应点击事件的代码.分享给大家供大家参考,具体如下: 1.先贴出自己的效果图(可横向滚动,并响应item点击事件): 2.关于点击事件的实现细节 核心:使用接口回调 在adapter中自己定义了个接口,然后在onBindViewHolder中去为holder.itemView去设置相应的监听最后回调我们设置的监听. class HomeAdapter extends RecyclerView.Adapter

  • Android使用Rotate3dAnimation实现3D旋转动画效果的实例代码

    利用Android的ApiDemos的Rotate3dAnimation实现了个图片3D旋转的动画,围绕Y轴进行旋转,还可以实现Z轴的缩放.点击开始按钮开始旋转,点击结束按钮停止旋转. 代码如下:: Rotate3dAnimation.java public class Rotate3dAnimation extends Animation { private final float mFromDegrees; private final float mToDegrees; private fi

  • android开发之横向滚动/竖向滚动的ListView(固定列头)

    由于项目需要,我们需要一个可以横向滚动的,又可以竖向滚动的 表格.而且又要考虑大数据量(行)的展示视图.经过几天的研究终于搞定,做了一个演示.贴图如下:      好吧.让我们看思路是什么样的: 1. 上下滚动直接使用 listView来实现. 2. 左右滚动使用HorizontalScrollView,来处理滚动.我写一个类MyHScrollView继承 自它. 2.1 . ListView里的每行(row)分为 两部分,不滚动的和可滚动的区域.比如本demo的第一列,就是静态的.而后面的所有

  • Android TV开发:实现3D仿Gallery效果的实例代码

    本文讲述了Android TV开发:实现3D仿Gallery效果的实例代码.分享给大家供大家参考,具体如下: 1.实现效果: 滚动翻页+ 页面点击+页码指示器+焦点控制 2.实现这个效果之前必须要了解 Android高级图片滚动控件实现3D版图片轮播器这篇文章,我是基于他的代码进行修改的,主要为了移植到电视上做了按键事件和焦点控制. 3.具体代码: public class Image3DSwitchView extends LinearLayout { private int currentP

  • android开发教程之listview使用方法

    首先是布局文件,这里需要两个布局文件,一个是放置列表控件的Activity对应的布局文件 main.xml,另一个是ListView中每一行信息显示所对应的布局  list_item.xml    这一步需要注意的问题是ListView 控件的id要使用Android系统内置的 android:id="@android:id/list"   [注意形式] main.xml 复制代码 代码如下: <?xml version="1.0" encoding=&quo

  • Android RecycleView使用(CheckBox全选、反选、单选)

    本文实例为大家分享了CheckBox全选.反选.单选的具体代码,供大家参考,具体内容如下 MainActiivity package com.bwie.day06; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.Recyc

  • Android编程实现3D立体旋转效果的实例代码

    说明:之前在网上到处搜寻类似的旋转效果 但搜到的结果都不是十分满意 原因不多追述(如果有人找到过相关 比较好的效果 可以发一下连接 一起共同进步) 一 效果展示 : 如非您所需要的效果 也希望能给些微帮助 具体操作以及实现 效果 请看项目例子 二 使用方式 此空间继承与FrameLayout 子空间直接添加如同framelayout 相同 如要如图效果 唯一要求子空间必须位于父控件中心且宽高等大小 为了方便扩展而做 如有其他需求可自行更改 (注 所有子控件 最好添加上背景 由于绘制机制和动画原因

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

    在Android中,如果想要实现3D动画效果一般有两种选择:一是使用Open GL ES,二是使用Camera.Open GL ES使用起来太过复杂,一般是用于比较高级的3D特效或游戏,并且这个也不是开源的,像比较简单的一些3D效果,使用Camera就足够了. 一些熟知的Android 3D动画如对某个View进行旋转或翻转的 Rotate3dAnimation类,还有使用Gallery( Gallery目前已过时,现在都推荐使用 HorizontalScrollView或 RecyclerVi

  • 通过FancyView提供 Android 酷炫的开屏动画实例代码

    效果 使用 compile 'site.gemus:openingstartanimation:1.0.0' //在gradle中导入项目 OpeningStartAnimation openingStartAnimation = new OpeningStartAnimation.Builder(this) .setDrawStategy(new NormalDrawStrategy()) //设置动画效果 .create(); openingStartAnimation.show(this)

  • Android实现带动画效果的可点击展开TextView

    本文为大家分享了Android实现带动画效果的可点击展开TextView 制作代码,效果图: 收起(默认)效果: 点击展开后的效果: 源码: 布局: <?xml version="1.0" encoding="utf-8"?> <LinearLayout android:id="@+id/activity_main" xmlns:android="http://schemas.android.com/apk/res/a

  • Android GridView实现动画效果实现代码

     Android GridView实现动画效果 项目中用到的一些动画,GridView的Item依次从屏幕外飞入到相应位置,附上相关代码: MainActivity.Java package com.mundane.gridanimationdemo; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.view.an

  • Android仿Flipboard动画效果的实现代码

    1.上原图 前几天在 Hencoder 征稿看到的Filpboard 里的的动画效果: Filipboard.gif 先bb一句:在看本文的同时,如果觉得我写的模糊看不太懂的可以直接拉到文末,配合完整代码再一步一步看. 2.实现 整体思路: 用手机拍下来,逐帧观看了许久,恍然大悟,就是一张纸,折起一边之后,让其对折线绕中心点旋转. 关联自定义 View : Camera 来控制对折幅度, canves 控制旋转. 具体: 每当对折线旋转的时候,图标总是一边是折起来的,一边是平铺的,且中心对称,所

  • Android自定义带动画效果的圆形ProgressBar

    本文实例为大家分享了Android自定义带动画效果的圆形ProgressBar,供大家参考,具体内容如下 最近有个需求显示进度,尾部还要有一标示,像下边这样 使用自定义View的方式实现,代码如下,很简单注释的很清楚 文章最后我们拓展一下功能,实现一个带动画效果的进度条 package com.example.fwc.allexample.progressbar; import android.animation.ValueAnimator; import android.annotation.

  • Android共享元素动画效果显示问题解决

    目录 bug描述 官方文档 解决流程 总结 bug描述 设计同学想搞一个点击图片item,item内的图片移动到新页面的图片位置的效果,一想就是共享元素就能搞定啊. companion object { fun gotoDetail( context: Activity, dynamicId: String?, jumpComment: Boolean = false, shareElement: Boolean = false, imageView: ImageView? = null, na

  • 5分钟快速实现Android爆炸破碎酷炫动画特效的示例

    这个破碎动画,是一种类似小米系统删除应用时的爆炸破碎效果的动画. 效果图展示 先来看下是怎样的动效,要是感觉不是理想的学习目标,就跳过,避免浪费大家的时间.�� 源码在这里:point_right: https://github.com/ReadyShowShow/explosion 一行代码即可调用该动画 new ExplosionField(this).explode(view, null)) 下面开始我们酷炫的Android动画特效正式讲解:point_down: 先来个整体结构的把握 整

  • 原生JS实现酷炫分页效果

    本文实例为大家分享一个如下效果的JS分页特效,是不是很酷炫. 以下是代码实现: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>原生JS实现酷炫分页</title> <style> * { margin: 0; padd

  • Android 四种动画效果的调用实现代码

    (1) main.xml 代码如下:(声明四个按钮控件) XML代码: 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?> <AbsoluteLayout android:id="@+id/widget32" android:layout_width="fill_parent" android:layout_height="fill_parent&qu

随机推荐