天天看點

android+酷炫動畫效果,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

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

{

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

{

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;

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;

}

public final void forceFinished(boolean finished)

{

mFinished = finished;

}

public final long getDuration()

{

return mDuration;

}

public final float getCurrAngle()

{

return mCurrAngle;

}

public final float getStartAngle()

{

return mStartAngle;

}

public int timePassed()

{

return (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);

}

public int getdirection()

{

return this.direction;

}

public float getCurrDeg()

{

return this.mCurrDeg;

}

public void extendDuration(int extend)

{

int passed = timePassed();

mDuration = passed + extend;

mFinished = false;

}

public void abortAnimation()

{

mFinished = true;

}

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

}

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;

}

}

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

}

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

}

void resetList()

{

mDataChanged = false;

mNeedSync = false;

removeAllViewsInLayout();

mOldSelectedPosition = INVALID_POSITION;

mOldSelectedRowId = INVALID_ROW_ID;

setSelectedPositionInt(INVALID_POSITION);

setNextSelectedPositionInt(INVALID_POSITION);

invalidate();

}

@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()

{

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

public void requestLayout()

{

if (!mBlockLayoutRequests)

{

super.requestLayout();

}

}

@Override

public int getCount()

{

return mItemCount;

}

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;

SavedState(Parcelable superState)

{

super(superState);

}

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 CREATOR = new Parcelable.Creator()

{

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 mScrapHeap = new SparseArray();

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

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

}

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

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;

}

@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

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

}

@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");

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

}

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

}

private void scrollIntoSlots(int direction)

{

Log.d("ORDER", "scrollIntoSlots");

float angle;

int position;

ArrayList arr = new ArrayList();

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()

{

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

}

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+酷炫動畫效果,Android酷炫動畫效果之3D星體旋轉效果

以上就是這篇文章的全部内容了,希望本文的内容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支援。