Java tutorial
/* * Copyright (C) 2013 Roy Wang * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tw.medfirst.com.project.baseview; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PaintFlagsDrawFilter; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.util.LruCache; import android.util.SparseArray; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AnimationUtils; import android.widget.Scroller; import java.util.ArrayList; import java.util.List; //import android.support.v4.util.LruCache; import tw.medfirst.com.project.R; import tw.medfirst.com.project.adapter.BaseCoverFlowAdapter; import tw.medfirst.com.project.adapter.CustomCoverFlowAdapter; import tw.medfirst.com.project.baseunit.BitmapUtils; /** * * @author dolphinWang * @time 2013-11-29 * */ public class CoverFlowView<T extends BaseCoverFlowAdapter> extends View { public enum CoverFlowGravity { TOP, BOTTOM, CENTER_VERTICAL; }; public enum CoverFlowLayoutMode { MATCH_PARENT, WRAP_CONTENT; } /**** static field ****/ private static final int DURATION = 200; protected final int INVALID_POSITION = -1; // Used to indicate a no preference for a position type. static final int NO_POSITION = -1; // the visible views left and right protected int VISIBLE_VIEWS = 3; // space between each two of children protected final int CHILD_SPACING = -200; // alpha private final int ALPHA_DATUM = 76; private int STANDARD_ALPHA; // private static final float CARD_SCALE = 0.15f; private static float MOVE_POS_MULTIPLE = 3.0f; private static final int TOUCH_MINIMUM_MOVE = 5; private static final float MOVE_SPEED_MULTIPLE = 1; private static final float MAX_SPEED = 6.0f; private static final float FRICTION = 10.0f; private static final int LONG_CLICK_DELAY = ViewConfiguration.getLongPressTimeout(); /**** static field ****/ private RecycleBin mRecycler; protected int mCoverFlowCenter; private T mAdapter; private int mVisibleChildCount; private int mItemCount; /** * True if the data has changed since the last layout */ boolean mDataChanged; protected CoverFlowGravity mGravity; protected CoverFlowLayoutMode mLayoutMode; private Rect mCoverFlowPadding; private PaintFlagsDrawFilter mDrawFilter; private Matrix mChildTransfromer; private Matrix mReflectionTransfromer; private Paint mDrawChildPaint; private RectF mTouchRect; private List<RectF> mTouchRect2; private int mWidth; private boolean mTouchMoved; private float mTouchStartPos; private float mTouchStartX; private float mTouchStartY; private float mOffset; private int mLastOffset; private float mStartOffset; private long mStartTime; private float mStartSpeed; private float mDuration; private Runnable mAnimationRunnable; private VelocityTracker mVelocity; private int mChildHeight; private int mChildTranslateY; private int mReflectionTranslateY; private float reflectHeightFraction; private int reflectGap; private boolean topImageClickEnable = true; protected CoverFlowListener<T> mCoverFlowListener; private TopImageLongClickListener mLongClickListener; private LongClickRunnable mLongClickRunnable; private boolean mLongClickPosted; private boolean mLongClickTriggled; private int mTopImageIndex; private Scroller mScroller; /** * Indicator whether view is drawing */ private boolean mDrawing; /** * To store reflections need to remove */ private ArrayList<Integer> mRemoveReflectionPendingArray; /** * Record origin width and height of images */ private SparseArray<int[]> mImageRecorder; public CoverFlowView(Context context) { super(context); init(); } public CoverFlowView(Context context, AttributeSet attrs) { super(context, attrs); initAttributes(context, attrs); init(); } public CoverFlowView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initAttributes(context, attrs); init(); } private void initAttributes(Context context, AttributeSet attrs) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ImageCoverFlowView); int totalVisibleChildren = a.getInt(R.styleable.ImageCoverFlowView_visibleImage, 2); if (totalVisibleChildren % 2 == 0) { throw new IllegalArgumentException("visible image must be an odd number"); } VISIBLE_VIEWS = totalVisibleChildren >> 1; reflectHeightFraction = a.getFraction(R.styleable.ImageCoverFlowView_reflectionHeight, 100, 0, 0.0f); if (reflectHeightFraction > 100) { reflectHeightFraction = 100; } reflectHeightFraction /= 100; reflectGap = a.getDimensionPixelSize(R.styleable.ImageCoverFlowView_reflectionGap, 0); mGravity = CoverFlowGravity.values()[a.getInt(R.styleable.ImageCoverFlowView_coverflowGravity, CoverFlowGravity.CENTER_VERTICAL.ordinal())]; mLayoutMode = CoverFlowLayoutMode.values()[a.getInt(R.styleable.ImageCoverFlowView_coverflowLayoutMode, CoverFlowLayoutMode.WRAP_CONTENT.ordinal())]; a.recycle(); } private void init() { setWillNotDraw(false); setClickable(true); mChildTransfromer = new Matrix(); mReflectionTransfromer = new Matrix(); mTouchRect = new RectF(); mTouchRect2 = new ArrayList<RectF>(); for (int i = 0; i < VISIBLE_VIEWS * 2 + 1; i++) { RectF r = new RectF(); mTouchRect2.add(r); } mImageRecorder = new SparseArray<int[]>(); mDrawChildPaint = new Paint(); mDrawChildPaint.setAntiAlias(true); mDrawChildPaint.setFlags(Paint.ANTI_ALIAS_FLAG); mCoverFlowPadding = new Rect(); mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); mScroller = new Scroller(getContext(), new AccelerateDecelerateInterpolator()); mRemoveReflectionPendingArray = new ArrayList<Integer>(); } /** * if subclass override this method, should call super method. * * @param adapter * extends CoverFlowAdapter */ public void setAdapter(T adapter) { mAdapter = adapter; if (mAdapter != null) { mItemCount = mAdapter.getCount(); if (mItemCount < (VISIBLE_VIEWS << 1) + 1) { throw new IllegalArgumentException("total count in adapter must larger than visible images!"); } mRecycler = new RecycleBin(); } resetList(); requestLayout(); } public T getAdapter() { return mAdapter; } public void setCoverFlowListener(CoverFlowListener<T> l) { mCoverFlowListener = l; } private void resetList() { if (mRecycler != null) { mRecycler.clear(); } mChildHeight = 0; mOffset = 0; mLastOffset = -1; STANDARD_ALPHA = (255 - ALPHA_DATUM) / VISIBLE_VIEWS; if (mGravity == null) { mGravity = CoverFlowGravity.CENTER_VERTICAL; } if (mLayoutMode == null) { mLayoutMode = CoverFlowLayoutMode.WRAP_CONTENT; } mImageRecorder.clear(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); } /** * ??? */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mAdapter == null) { return; } mCoverFlowPadding.left = getPaddingLeft(); mCoverFlowPadding.right = getPaddingRight(); mCoverFlowPadding.top = getPaddingTop(); mCoverFlowPadding.bottom = getPaddingBottom(); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int visibleCount = (VISIBLE_VIEWS << 1) + 1; int avaiblableHeight = heightSize - mCoverFlowPadding.top - mCoverFlowPadding.bottom; int maxChildTotalHeight = 0; for (int i = 0; i < visibleCount; ++i) { Bitmap child = mAdapter.getImage(i); final int childHeight = child.getHeight(); final int childTotalHeight = (int) (childHeight + childHeight * reflectHeightFraction + reflectGap); maxChildTotalHeight = (maxChildTotalHeight < childTotalHeight) ? childTotalHeight : maxChildTotalHeight; } if (heightMode == MeasureSpec.EXACTLY || heightMode == MeasureSpec.AT_MOST) { // if height which parent provided is less than child need, scale // child height to parent provide if (avaiblableHeight < maxChildTotalHeight) { mChildHeight = avaiblableHeight; } else { // if larger than, depends on layout mode // if layout mode is match_parent, scale child height to parent // provide if (mLayoutMode == CoverFlowLayoutMode.MATCH_PARENT) { mChildHeight = avaiblableHeight; // if layout mode is wrap_content, keep child's original // height } else if (mLayoutMode == CoverFlowLayoutMode.WRAP_CONTENT) { mChildHeight = maxChildTotalHeight; // adjust parent's height if (heightMode == MeasureSpec.AT_MOST) { heightSize = mChildHeight + mCoverFlowPadding.top + mCoverFlowPadding.bottom; } } } } else { // height mode is unspecified if (mLayoutMode == CoverFlowLayoutMode.MATCH_PARENT) { mChildHeight = avaiblableHeight; } else if (mLayoutMode == CoverFlowLayoutMode.WRAP_CONTENT) { mChildHeight = maxChildTotalHeight; // adjust parent's height heightSize = mChildHeight + mCoverFlowPadding.top + mCoverFlowPadding.bottom; } } // Adjust movement in y-axis according to gravity if (mGravity == CoverFlowGravity.CENTER_VERTICAL) { mChildTranslateY = (heightSize >> 1) - (mChildHeight >> 1); } else if (mGravity == CoverFlowGravity.TOP) { mChildTranslateY = mCoverFlowPadding.top; } else if (mGravity == CoverFlowGravity.BOTTOM) { mChildTranslateY = heightSize - mCoverFlowPadding.bottom - mChildHeight; } mReflectionTranslateY = (int) (mChildTranslateY + mChildHeight - mChildHeight * reflectHeightFraction); setMeasuredDimension(widthSize, heightSize); mVisibleChildCount = visibleCount; mWidth = widthSize; } /** * subclass should never override this method, because all of child will * draw on the canvas directly */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { } @Override protected void onDraw(Canvas canvas) { if (mAdapter == null) { super.onDraw(canvas); return; } mDrawing = true; canvas.setDrawFilter(mDrawFilter); final float offset = mOffset; int i = 0; int mid = (int) Math.floor(offset + 0.5); int rightChild = (mVisibleChildCount % 2 == 0) ? (mVisibleChildCount >> 1) - 1 : mVisibleChildCount >> 1; int leftChild = mVisibleChildCount >> 1; // draw the left children int startPos = mid - leftChild; for (i = startPos; i < mid; ++i) { drawChild(canvas, i, i - offset); } // draw the right children int endPos = mid + rightChild; for (i = endPos; i >= mid; --i) { drawChild(canvas, i, i - offset); } if (mLastOffset != (int) offset) { imageOnTop(getActuallyPosition((int) offset)); mLastOffset = (int) offset; } mDrawing = false; final int removeCount = mRemoveReflectionPendingArray.size(); for (i = 0; i < removeCount; i++) { final int removePosition = mRemoveReflectionPendingArray.get(i); mRecycler.removeCachedBitmap(removePosition); } mRemoveReflectionPendingArray.clear(); super.onDraw(canvas); mCoverFlowListener.invalidationCompleted(); } protected final void drawChild(Canvas canvas, int position, float offset) { int actuallyPosition = getActuallyPosition(position); final Bitmap child = mAdapter.getImage(actuallyPosition); final Bitmap reflection = obtainReflection(actuallyPosition, child); int[] wAndh = mImageRecorder.get(actuallyPosition); if (wAndh == null) { wAndh = new int[] { child.getWidth(), child.getHeight() }; mImageRecorder.put(actuallyPosition, wAndh); } else { wAndh[0] = child.getWidth(); wAndh[1] = child.getHeight(); } if (child != null && !child.isRecycled() && canvas != null) { makeChildTransfromer(child, position, offset); canvas.drawBitmap(child, mChildTransfromer, mDrawChildPaint); if (reflection != null) { canvas.drawBitmap(reflection, mReflectionTransfromer, mDrawChildPaint); } } } /** * <ul> * <li>bitmap3d??</li> * </ul> * // * @param childTransfromMatrix // * @param mDrawChildPaint * @param child * @param position * @param offset */ private void makeChildTransfromer(Bitmap child, int position, float offset) { mChildTransfromer.reset(); mReflectionTransfromer.reset(); float scale = 0; //this scale make sure that each image will be smaller than the //previous one if (position != 0) { scale = 1 - Math.abs(offset) * 0.15f; } else { scale = 1 - Math.abs(offset) * CARD_SCALE; } //float scale = 1 - Math.abs(offset) * CARD_SCALE; // x??center float translateX = 0; final int originalChildHeight = (int) (mChildHeight - mChildHeight * reflectHeightFraction - reflectGap); final int childTotalHeight = (int) (child.getHeight() + child.getHeight() * reflectHeightFraction + reflectGap); final float originalChildHeightScale = (float) originalChildHeight / child.getHeight(); final float childHeightScale = originalChildHeightScale * scale; final int childWidth = (int) (child.getWidth() * childHeightScale); final int centerChildWidth = (int) (child.getWidth() * originalChildHeightScale); // final int scaleWidth = mWidth * (int)childHeightScale; int leftSpace = ((mWidth >> 1) - mCoverFlowPadding.left) - (centerChildWidth >> 1); int rightSpace = (((mWidth >> 1) - mCoverFlowPadding.right) - (centerChildWidth >> 1)); //icon? if (offset <= 0) { translateX = ((float) leftSpace / VISIBLE_VIEWS) * (VISIBLE_VIEWS + offset) + mCoverFlowPadding.left; translateX += Math.abs(offset) * 100; } else { translateX = mWidth - ((float) rightSpace / VISIBLE_VIEWS) * (VISIBLE_VIEWS - offset) - childWidth - mCoverFlowPadding.right; translateX -= Math.abs(offset) * 100; } float alpha = (float) 254 - Math.abs(offset) * STANDARD_ALPHA; if (alpha < 0) { alpha = 0; } else if (alpha > 254) { alpha = 254; } //? // mDrawChildPaint.setAlpha((int) alpha); mChildTransfromer.preTranslate(0, -(childTotalHeight >> 1)); // matrixpostxxx???prexxx? mChildTransfromer.postScale(childHeightScale, childHeightScale, 0, 0); // if actually child height is larger or smaller than original child // height // need to change translate distance of y-axis float adjustedChildTranslateY = 0; if (childHeightScale != 1) { adjustedChildTranslateY = (mChildHeight - childTotalHeight) >> 1; } mChildTransfromer.postTranslate(translateX, mChildTranslateY + adjustedChildTranslateY); // Log.d(VIEW_LOG_TAG, "position= " + position + " mChildTranslateY= " // + mChildTranslateY + adjustedChildTranslateY); getCustomTransformMatrix(mChildTransfromer, mDrawChildPaint, child, position, offset); mChildTransfromer.postTranslate(0, (childTotalHeight >> 1)); recfAllViews(position, child); mReflectionTransfromer.preTranslate(0, -(childTotalHeight >> 1)); mReflectionTransfromer.postScale(childHeightScale, childHeightScale, 0, 0); ; mReflectionTransfromer.postTranslate(translateX, mReflectionTranslateY * scale + adjustedChildTranslateY); getCustomTransformMatrix(mReflectionTransfromer, mDrawChildPaint, child, position, offset); mReflectionTransfromer.postTranslate(0, (childTotalHeight >> 1)); } private void recfAllViews(int position, Bitmap child) { int actuallyPosition = getActuallyPosition(position); float[] values = new float[9]; mChildTransfromer.getValues(values); // Log.e("BBB", Integer.toString(actuallyPosition)); // RectF a = new RectF(); mTouchRect2.get(actuallyPosition).left = values[2]; mTouchRect2.get(actuallyPosition).top = values[5]; mTouchRect2.get(actuallyPosition).right = mTouchRect2.get(getActuallyPosition(position)).left + child.getWidth() * values[0]; mTouchRect2.get(actuallyPosition).bottom = mTouchRect2.get(getActuallyPosition(position)).top + child.getHeight() * values[0]; } /** * <ul> * <li>This is an empty method.</li> * <li>Giving user a chance to make more transform base on standard.</li> * </ul> * // * @param childTransfromMatrix * matrix to make transform * @param mDrawChildPaint * paint, user can set alpha * @param child * bitmap to draw * @param position * @param offset * offset to center(zero) */ protected void getCustomTransformMatrix(Matrix transfromer, Paint mDrawChildPaint, Bitmap child, int position, float offset) { /** example code to make image y-axis rotation **/ // Camera c = new Camera(); // c.save(); // Matrix m = new Matrix(); // c.rotateY(10 * (-offset)); // c.getMatrix(m); // c.restore(); // m.preTranslate(-(child.getWidth() >> 1), -(child.getHeight() >> 1)); // m.postTranslate(child.getWidth() >> 1, child.getHeight() >> 1); // mChildTransfromMatrix.preConcat(m); } private void imageOnTop(int position) { mTopImageIndex = position; final int[] wAndh = mImageRecorder.get(position); int heightInView = (int) (mChildHeight - mChildHeight * reflectHeightFraction - reflectGap); final float scale = (float) heightInView / wAndh[1]; int widthInView = (int) (wAndh[0] * scale); //replace this with your view size in order to get correctly calculated rectangle heightInView = 652; widthInView = 1080 / 2; // Log.e(VIEW_LOG_TAG, "height ==>" + heightInView + " width ==>" // + widthInView); mTouchRect.left = (mWidth >> 1) - (widthInView >> 1); mTouchRect.top = mChildTranslateY; mTouchRect.right = mTouchRect.left + widthInView; mTouchRect.bottom = mTouchRect.top + heightInView; // Log.e(VIEW_LOG_TAG, "rect==>" + mTouchRect); if (mCoverFlowListener != null) { mCoverFlowListener.imageOnTop(this, position, mTouchRect.left, mTouchRect.top, mTouchRect.right, mTouchRect.bottom); } } @Override public boolean onTouchEvent(MotionEvent event) { if (getParent() != null) { getParent().requestDisallowInterceptTouchEvent(true); } int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: if (mScroller.computeScrollOffset()) { mScroller.abortAnimation(); invalidate(); } stopLongClick(); triggleLongClick(event.getX(), event.getY()); touchBegan(event); return true; case MotionEvent.ACTION_MOVE: touchMoved(event); return true; case MotionEvent.ACTION_UP: touchEnded(event); stopLongClick(); return true; } return false; } private void triggleLongClick(float x, float y) { if (mTouchRect.contains(x, y) && mLongClickListener != null && topImageClickEnable && !mLongClickPosted) { final int actuallyPosition = mTopImageIndex; mLongClickRunnable.setPosition(actuallyPosition); postDelayed(mLongClickRunnable, LONG_CLICK_DELAY); } } private void stopLongClick() { if (mLongClickRunnable != null) { removeCallbacks(mLongClickRunnable); mLongClickPosted = false; mLongClickTriggled = false; } } private void touchBegan(MotionEvent event) { endAnimation(); float x = event.getX(); mTouchStartX = x; mTouchStartY = event.getY(); mStartTime = AnimationUtils.currentAnimationTimeMillis(); mStartOffset = mOffset; mTouchMoved = false; mTouchStartPos = (x / mWidth) * MOVE_POS_MULTIPLE - 5; mTouchStartPos /= 2; mVelocity = VelocityTracker.obtain(); mVelocity.addMovement(event); } private void touchMoved(MotionEvent event) { float pos = (event.getX() / mWidth) * MOVE_POS_MULTIPLE - 5; pos /= 2; if (!mTouchMoved) { float dx = Math.abs(event.getX() - mTouchStartX); float dy = Math.abs(event.getY() - mTouchStartY); if (dx < TOUCH_MINIMUM_MOVE && dy < TOUCH_MINIMUM_MOVE) return; mTouchMoved = true; stopLongClick(); } mOffset = mStartOffset + mTouchStartPos - pos; invalidate(); mVelocity.addMovement(event); } private void touchEnded(MotionEvent event) { float pos = (event.getX() / mWidth) * MOVE_POS_MULTIPLE - 5; pos /= 2; if (mTouchMoved || (mOffset - Math.floor(mOffset)) != 0) { mStartOffset += mTouchStartPos - pos; mOffset = mStartOffset; mVelocity.addMovement(event); mVelocity.computeCurrentVelocity(1000); double speed = mVelocity.getXVelocity(); speed = (speed / mWidth) * MOVE_SPEED_MULTIPLE; if (speed > MAX_SPEED) speed = MAX_SPEED; else if (speed < -MAX_SPEED) speed = -MAX_SPEED; startAnimation(-speed); } else { // Log.e(VIEW_LOG_TAG, // " touch ==>" + event.getX() + " , " + event.getY()); if (mTouchRect != null && mCoverFlowListener != null && topImageClickEnable && !mLongClickTriggled) { final int actuallyPosition = mTopImageIndex; if (mTouchRect2.get(actuallyPosition).contains(event.getX(), event.getY())) { mCoverFlowListener.topImageClicked(this, actuallyPosition); } else { final int totalImage = VISIBLE_VIEWS * 2 + 1; for (int i = 1; i <= VISIBLE_VIEWS; i++) { int preNum = (actuallyPosition + (totalImage - i)) % totalImage; int postNum = (actuallyPosition + i) % totalImage; if (mTouchRect2.get(preNum).contains(event.getX(), event.getY())) { startAnimation(-(i + 1) * 2); break; } else if (mTouchRect2.get(postNum).contains(event.getX(), event.getY())) { startAnimation((i + 1) * 2); break; } } } // if(mTouchRect2.get(actuallyPosition).contains(event.getX(), event.getY())){ // mCoverFlowListener.topImageClicked(this, actuallyPosition); // }else if(mTouchRect2.get((actuallyPosition + 4) % 5).contains(event.getX(), event.getY())){ // startAnimation(-4); // Log.e("onclick: ", Integer.toString((actuallyPosition + 4) % 5)); // }else if(mTouchRect2.get((actuallyPosition + 1) % 5).contains(event.getX(), event.getY())){ // startAnimation(4); // Log.e("onclick: ", Integer.toString((actuallyPosition + 1) % 5)); // }else if(mTouchRect2.get((actuallyPosition + 3) % 5).contains(event.getX(), event.getY())){ // startAnimation(-6); // Log.e("onclick: ", Integer.toString((actuallyPosition + 3) % 5)); // }else if(mTouchRect2.get((actuallyPosition + 2) % 5).contains(event.getX(), event.getY())){ // startAnimation(6); // Log.e("onclick: ", Integer.toString((actuallyPosition + 2) % 5)); // } // Log.e(VIEW_LOG_TAG, // " touch ==>" + event.getX() + " , " + event.getY()); } } mVelocity.clear(); mVelocity.recycle(); } private void startAnimation(double speed) { if (mAnimationRunnable != null) return; double delta = speed * speed / (FRICTION * 2); if (speed < 0) delta = -delta; double nearest = mStartOffset + delta; nearest = Math.floor(nearest + 0.5); mStartSpeed = (float) Math.sqrt(Math.abs(nearest - mStartOffset) * FRICTION * 2); if (nearest < mStartOffset) mStartSpeed = -mStartSpeed; mDuration = Math.abs(mStartSpeed / FRICTION); mStartTime = AnimationUtils.currentAnimationTimeMillis(); mAnimationRunnable = new Runnable() { @Override public void run() { driveAnimation(); } }; post(mAnimationRunnable); } private void driveAnimation() { float elapsed = (AnimationUtils.currentAnimationTimeMillis() - mStartTime) / 1000.0f; if (elapsed >= mDuration) endAnimation(); else { updateAnimationAtElapsed(elapsed); post(mAnimationRunnable); } } private void endAnimation() { if (mAnimationRunnable != null) { mOffset = (float) Math.floor(mOffset + 0.5); invalidate(); removeCallbacks(mAnimationRunnable); mAnimationRunnable = null; } } private void updateAnimationAtElapsed(float elapsed) { if (elapsed > mDuration) elapsed = mDuration; float delta = Math.abs(mStartSpeed) * elapsed - FRICTION * elapsed * elapsed / 2; if (mStartSpeed < 0) delta = -delta; mOffset = mStartOffset + delta; invalidate(); } /** * Invalidate image on special position * * @param position */ public void invalidatePosition(int position) { if (mAdapter == null || position < 0) { return; } final int count = mAdapter.getCount(); if (position >= count) { return; } if (!mDrawing) { mRecycler.removeCachedBitmap(position); } else { if (!mRemoveReflectionPendingArray.contains(position)) { mRemoveReflectionPendingArray.add(position); } } // If bitmap want to invalidate is showing on screen // call view to redraw if (position >= mTopImageIndex - VISIBLE_VIEWS && position <= mTopImageIndex + VISIBLE_VIEWS) { invalidate(); } } /** * Convert draw-index to index in adapter * * @param position * position to draw * @return */ private int getActuallyPosition(int position) { if (mAdapter == null) { return INVALID_POSITION; } int max = mAdapter.getCount(); position += VISIBLE_VIEWS; while (position < 0 || position >= max) { if (position < 0) { position += max; } else if (position >= max) { position -= max; } } return position; } private Bitmap obtainReflection(int position, Bitmap src) { if (reflectHeightFraction <= 0) { return null; } Bitmap reflection = mRecycler.getCachedBitmap(position); if (reflection == null || reflection.isRecycled()) { mRecycler.removeCachedBitmap(position); reflection = BitmapUtils.createReflectedBitmap(src, reflectHeightFraction); if (reflection != null) { mRecycler.addBitmap2Cache(position, reflection); return reflection; } } return reflection; } public void setVisibleImage(int count) { if (count % 2 == 0) { throw new IllegalArgumentException("visible image must be an odd number"); } VISIBLE_VIEWS = count / 2; STANDARD_ALPHA = (255 - ALPHA_DATUM) / VISIBLE_VIEWS; } public void setCoverFlowGravity(CoverFlowGravity gravity) { mGravity = gravity; } public void setCoverFlowLayoutMode(CoverFlowLayoutMode mode) { mLayoutMode = mode; } public void setReflectionHeight(int fraction) { if (fraction < 0) fraction = 0; else if (fraction > 100) fraction = 100; reflectHeightFraction = fraction; } public void setReflectionGap(int gap) { if (gap < 0) gap = 0; reflectGap = gap; } public void disableTopImageClick() { topImageClickEnable = false; } public void enableTopImageClick() { topImageClickEnable = true; } public void setSelection(int position) { final int max = mAdapter.getCount(); if (position < 0 || position >= max) { throw new IllegalArgumentException( "Position want to select can not less than 0 or larger than max of adapter provide!"); } if (mTopImageIndex != position) { if (mScroller.computeScrollOffset()) { mScroller.abortAnimation(); } final int from = (int) (mOffset * 100); final int disX = (int) ((position - VISIBLE_VIEWS) * 100) - from; mScroller.startScroll(from, 0, disX, 0, DURATION * Math.min(Math.abs(position + max - mTopImageIndex), Math.abs(position - mTopImageIndex))); invalidate(); } } @Override public void computeScroll() { super.computeScroll(); if (mScroller.computeScrollOffset()) { final int currX = mScroller.getCurrX(); mOffset = (float) currX / 100; invalidate(); } } public void setTopImageLongClickListener(TopImageLongClickListener listener) { mLongClickListener = listener; if (listener == null) { mLongClickRunnable = null; } else { if (mLongClickRunnable == null) { mLongClickRunnable = new LongClickRunnable(); } } } private class LongClickRunnable implements Runnable { private int position; public void setPosition(int position) { this.position = position; } @Override public void run() { if (mLongClickListener != null) { mLongClickListener.onLongClick(position); mLongClickTriggled = true; } } } class RecycleBin { @SuppressLint("NewApi") final LruCache<Integer, Bitmap> bitmapCache = new LruCache<Integer, Bitmap>(getCacheSize(getContext())) { @Override protected int sizeOf(Integer key, Bitmap bitmap) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) { return bitmap.getRowBytes() * bitmap.getHeight(); } else { return bitmap.getByteCount(); } } @Override protected void entryRemoved(boolean evicted, Integer key, Bitmap oldValue, Bitmap newValue) { if (evicted && oldValue != null && !oldValue.isRecycled()) { oldValue.recycle(); oldValue = null; } } }; public Bitmap getCachedBitmap(int position) { return bitmapCache.get(position); } public void addBitmap2Cache(int position, Bitmap b) { bitmapCache.put(position, b); Runtime.getRuntime().gc(); } public Bitmap removeCachedBitmap(int position) { if (position < 0 || position >= bitmapCache.size()) { return null; } return bitmapCache.remove(position); } public void clear() { bitmapCache.evictAll(); } private int getCacheSize(Context context) { final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); final int memClass = am.getMemoryClass(); // Target ~5% of the available heap. int cacheSize = 1024 * 1024 * memClass / 21; Log.e(VIEW_LOG_TAG, "cacheSize == " + cacheSize); return cacheSize; } } public static interface TopImageLongClickListener { public void onLongClick(int position); } public static interface CoverFlowListener<V extends BaseCoverFlowAdapter> { public void imageOnTop(final CoverFlowView<V> coverFlowView, int position, float left, float top, float right, float bottom); public void topImageClicked(final CoverFlowView<V> coverFlowView, int position); public void invalidationCompleted(); } }