用OpenGL实现无限循环的coverflow
网上找了很久,都是用Gallery实现的,效果不是很满意,结果发现这个用OpenGL实现的,稍微修改了一下源码,实现了无限循环功能
源码地址:https://github.com/jackfengji/glcoverflow
public class CoverFlowOpenGL extends GLSurfaceView implementsGLSurfaceView.Renderer {private static final int TOUCH_MINIMUM_MOVE = 5;private static final int IMAGE_SIZE = 128;private static final int MAX_TILES = 48;// the visble tiles left and rightprivate static final int VISIBLE_TILES = 1;private static final float SCALE = 0.7f;private static final float SPREAD_IMAGE = 0.14f;private static final float FLANK_SPREAD = 0.4f;private static final float FRICTION = 10.0f;private static final float MAX_SPEED = 6.0f;private static final float[] GVertices = new float[] { -1.0f, -1.0f, 0.0f,1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, };private static final float[] GTextures = new float[] { 0.0f, 1.0f, 1.0f,1.0f, 0.0f, 0.0f, 1.0f, 0.0f, };private GL10 mGLContext;private FloatBuffer mVerticesBuffer;private FloatBuffer mTexturesBuffer;private float[] mMatrix;private int mBgTexture;private FloatBuffer mBgVerticesBuffer;private FloatBuffer mBgTexturesBuffer;private int mBgBitmapId;private boolean mInitBackground;private float mOffset;private int mLastOffset;private RectF mTouchRect;private int mWidth;private boolean mTouchMoved;private float mTouchStartPos;private float mTouchStartX;private float mTouchStartY;private float mStartOffset;private long mStartTime;private float mStartSpeed;private float mDuration;private Runnable mAnimationRunnable;private VelocityTracker mVelocity;private CoverFlowListener mListener;private DataCache<Integer, CoverFlowRecord> mCache;public CoverFlowOpenGL(Context context) {super(context);setEGLConfigChooser(8, 8, 8, 8, 16, 0);setRenderer(this);setRenderMode(RENDERMODE_WHEN_DIRTY);getHolder().setFormat(PixelFormat.TRANSLUCENT);mCache = new DataCache<Integer, CoverFlowRecord>(MAX_TILES);mLastOffset = 0;mOffset = 0;mInitBackground = false;mBgBitmapId = 0;}public void setCoverFlowListener(CoverFlowListener listener) {mListener = listener;}public void setSelection(int position) {endAnimation();mOffset = position;requestRender();}private int getActuallyPosition(int position) {int max = mListener.getCount(this);while (position < 0 || position >= max) {if (position < 0)position += max;else if (position >= max)position -= max;}return position;}@Overridepublic boolean onTouchEvent(MotionEvent event) {int action = event.getAction();switch (action) {case MotionEvent.ACTION_DOWN:touchBegan(event);return true;case MotionEvent.ACTION_MOVE:touchMoved(event);return true;case MotionEvent.ACTION_UP:touchEnded(event);return true;}return false;}private void touchBegan(MotionEvent event) {endAnimation();float x = event.getX();mTouchStartX = x;mTouchStartY = event.getY();mStartTime = System.currentTimeMillis();mStartOffset = mOffset;mTouchMoved = false;mTouchStartPos = (x / mWidth) * 10 - 5;mTouchStartPos /= 2;mVelocity = VelocityTracker.obtain();mVelocity.addMovement(event);}private void touchMoved(MotionEvent event) {float pos = (event.getX() / mWidth) * 10 - 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;}mOffset = mStartOffset + mTouchStartPos - pos;requestRender();mVelocity.addMovement(event);}private void touchEnded(MotionEvent event) {float pos = (event.getX() / mWidth) * 10 - 5;pos /= 2;if (mTouchMoved) {mStartOffset += mTouchStartPos - pos;mOffset = mStartOffset;mVelocity.addMovement(event);mVelocity.computeCurrentVelocity(1000);double speed = mVelocity.getXVelocity();speed = (speed / mWidth) * 10;if (speed > MAX_SPEED)speed = MAX_SPEED;else if (speed < -MAX_SPEED)speed = -MAX_SPEED;startAnimation(-speed);} else {if (mTouchRect.contains(event.getX(), event.getY())) {mListener.topTileClicked(this,getActuallyPosition((int) (mOffset + 0.01)));}}}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() {@Overridepublic 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);requestRender();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;requestRender();}@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {mCache.clear();mGLContext = gl;mVerticesBuffer = makeFloatBuffer(GVertices);mTexturesBuffer = makeFloatBuffer(GTextures);}@Overridepublic void onSurfaceChanged(GL10 gl, int w, int h) {mWidth = w;float imagew = w * 0.45f / SCALE / 2.0f;float imageh = h * 0.45f / SCALE / 2.0f;mTouchRect = new RectF(w / 2 - imagew, h / 2 - imageh, w / 2 + imagew,h / 2 + imageh);gl.glViewport(0, 0, w, h);float ratio = ((float) w) / h;gl.glMatrixMode(GL10.GL_PROJECTION);gl.glLoadIdentity();gl.glOrthof(-ratio * SCALE, ratio * SCALE, -1 * SCALE, 1 * SCALE, 1, 3);float[] vertices = new float[] { -ratio * SCALE, -SCALE, 0,ratio * SCALE, -SCALE, 0, -ratio * SCALE, SCALE, 0,ratio * SCALE, SCALE, 0 };mBgVerticesBuffer = makeFloatBuffer(vertices);}public void setBackgroundTexture(int res) {mBgBitmapId = res;mInitBackground = true;}public void clearTileCache() {mCache.clear();}private void initBg() {mInitBackground = false;if (mBgBitmapId != 0) {Bitmap bitmap = BitmapFactory.decodeResource(getResources(),mBgBitmapId);mBgBitmapId = 0;int tmp = 1;int w = bitmap.getWidth();int h = bitmap.getHeight();while (w > tmp || h > tmp) {tmp <<= 1;}int width = tmp;int height = tmp;Bitmap bm = Bitmap.createBitmap(width, height,Bitmap.Config.RGB_565);Canvas cv = new Canvas(bm);int left = (width - w) / 2;int top = (height - h) / 2;cv.drawBitmap(bitmap, left, top, new Paint());GL10 gl = mGLContext;int[] tex = new int[1];gl.glGenTextures(1, tex, 0);mBgTexture = tex[0];gl.glBindTexture(GL10.GL_TEXTURE_2D, mBgTexture);GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bm, 0);bitmap.recycle();bm.recycle();gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_NEAREST);gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,GL10.GL_LINEAR);gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,GL10.GL_CLAMP_TO_EDGE);gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,GL10.GL_CLAMP_TO_EDGE);gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,GL10.GL_REPLACE);float[] textcoor = new float[] { (tmp - w) / 2.0f / tmp,(tmp - h) / 2.0f / tmp, (tmp + w) / 2.0f / tmp,(tmp - h) / 2.0f / tmp, (tmp - w) / 2.0f / tmp,(tmp + h) / 2.0f / tmp, (tmp + w) / 2.0f / tmp,(tmp + h) / 2.0f / tmp };mBgTexturesBuffer = makeFloatBuffer(textcoor);}}@Overridepublic void onDrawFrame(GL10 gl) {gl.glMatrixMode(GL10.GL_MODELVIEW);gl.glLoadIdentity();GLU.gluLookAt(gl, 0, 0, 2, 0f, 0f, 0f, 0f, 1.0f, 0.0f);gl.glDisable(GL10.GL_DEPTH_TEST);gl.glClearColor(0, 0, 0, 0);gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);drawBg(gl);draw(gl);}public void drawBg(GL10 gl) {if (mInitBackground)initBg();if (mBgTexture != 0) {gl.glPushMatrix();gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBgVerticesBuffer);gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mBgTexturesBuffer);gl.glEnable(GL10.GL_TEXTURE_2D);gl.glBindTexture(GL10.GL_TEXTURE_2D, mBgTexture);gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);gl.glPopMatrix();}}private void draw(GL10 gl) {gl.glPushMatrix();gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVerticesBuffer);gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTexturesBuffer);gl.glEnable(GL10.GL_TEXTURE_2D);gl.glEnable(GL10.GL_BLEND);gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);final float offset = mOffset;int i = 0;int mid = (int) Math.floor(offset + 0.5);// draw the left tilesint iStartPos = mid - VISIBLE_TILES;for (i = iStartPos; i < mid; ++i) {drawTile(i, i - offset, gl);}// draw the right tilesint iEndPos = mid + VISIBLE_TILES;for (i = iEndPos; i >= mid; --i) {drawTile(i, i - offset, gl);}if (mLastOffset != (int) offset) {mListener.tileOnTop(this, getActuallyPosition((int) offset));mLastOffset = (int) offset;}gl.glPopMatrix();}private void drawTile(int position, float off, GL10 gl) {final CoverFlowRecord fcr = getTileAtIndex(position, gl);if (fcr != null && fcr.mTexture != 0) {if (mMatrix == null) {mMatrix = new float[16];mMatrix[15] = 1;mMatrix[10] = 1;mMatrix[5] = 1;mMatrix[0] = 1;}float trans = off * SPREAD_IMAGE;float f = off * FLANK_SPREAD;if (f > FLANK_SPREAD)f = FLANK_SPREAD;else if (f < -FLANK_SPREAD)f = -FLANK_SPREAD;mMatrix[3] = -f;mMatrix[0] = 1 - Math.abs(f);float sc = 0.38f * mMatrix[0];trans += f * 1;gl.glPushMatrix();gl.glBindTexture(GL10.GL_TEXTURE_2D, fcr.mTexture);// draw bitmapgl.glTranslatef(trans, 0, 0);gl.glScalef(sc, sc, 1.0f);gl.glMultMatrixf(mMatrix, 0);gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);// draw the reflectiongl.glTranslatef(0, -2, 0);gl.glScalef(1, -1, 1);gl.glColor4f(1f, 1f, 1f, 0.5f);gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);gl.glColor4f(1, 1, 1, 1);gl.glPopMatrix();}}private CoverFlowRecord getTileAtIndex(int position, GL10 gl) {synchronized (this) {position = getActuallyPosition(position);CoverFlowRecord fcr = mCache.objectForKey(position);if (fcr == null) {Bitmap bm = mListener.getImage(this, position);if (bm == null)return null;int texture = imageToTexture(bm, gl);fcr = new CoverFlowRecord(texture, gl);mCache.putObjectForKey(position, fcr);}return fcr;}}private static Bitmap createTextureBitmap(Bitmap bitmap) {int width = bitmap.getWidth();int height = bitmap.getHeight();final Bitmap bm = Bitmap.createBitmap(IMAGE_SIZE, IMAGE_SIZE,Bitmap.Config.ARGB_8888);Canvas cv = new Canvas(bm);if (width > IMAGE_SIZE || height > IMAGE_SIZE) {// scale the bitmap, make the width or height to the IMAGE_SIZERect src = new Rect(0, 0, width, height);float scale = 1.0f;if (width > height)scale = ((float) IMAGE_SIZE) / width;elsescale = ((float) IMAGE_SIZE) / height;width = (int) (width * scale);height = (int) (height * scale);float left = (IMAGE_SIZE - width) / 2.0f;float top = (IMAGE_SIZE - height) / 2.0f;RectF dst = new RectF(left, top, left + width, top + height);cv.drawBitmap(bitmap, src, dst, new Paint());} else {float left = (IMAGE_SIZE - width) / 2.0f;float top = (IMAGE_SIZE - height) / 2.0f;cv.drawBitmap(bitmap, left, top, new Paint());}return bm;}private int imageToTexture(Bitmap bitmap, GL10 gl) {// generate textureint[] texture = new int[1];gl.glGenTextures(1, texture, 0);gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[0]);final Bitmap bm = createTextureBitmap(bitmap);bitmap.recycle();GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bm, 0); // draw the bitmap in// the texturebm.recycle();// some texture settingsgl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_NEAREST);gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,GL10.GL_LINEAR);gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,GL10.GL_CLAMP_TO_EDGE);gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,GL10.GL_CLAMP_TO_EDGE);gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,GL10.GL_MODULATE);return texture[0];}private static FloatBuffer makeFloatBuffer(final float[] arr) {ByteBuffer bb = ByteBuffer.allocateDirect(arr.length * 4);bb.order(ByteOrder.nativeOrder());FloatBuffer fb = bb.asFloatBuffer();fb.put(arr);fb.position(0);return fb;}public static class CoverFlowRecord {private int mTexture;private GL10 gl;public CoverFlowRecord(int texture, GL10 gl) {mTexture = texture;this.gl = gl;}@Overrideprotected void finalize() throws Throwable {if (mTexture != 0) {gl.glDeleteTextures(1, new int[] { mTexture }, 0);}super.finalize();}}public static interface CoverFlowListener {public int getCount(CoverFlowOpenGL view);public Bitmap getImage(CoverFlowOpenGL anotherCoverFlow, int position);public void tileOnTop(CoverFlowOpenGL view, int position);public void topTileClicked(CoverFlowOpenGL view, int position);}}
public class DataCache<K, E> {private int mCapacity;private LinkedHashMap<K, E> mCache;private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();public DataCache(int capacity) {mCapacity = capacity;mCache = new LinkedHashMap<K, E>(mCapacity) {private static final long serialVersionUID = -9165777183357349715L;@Overrideprotected boolean removeEldestEntry(Entry<K, E> eldest) {if (size() > mCapacity) {mReadWriteLock.writeLock().lock();remove(eldest.getKey());mReadWriteLock.writeLock().unlock();}return false;}};}public E objectForKey(K key) {mReadWriteLock.readLock().lock();final E result = mCache.get(key);mReadWriteLock.readLock().unlock();return result;}public void putObjectForKey(final K key, final E value) {if (key != null && value != null) {mReadWriteLock.writeLock().lock();mCache.put(key, value);mReadWriteLock.writeLock().unlock();}}public boolean containsKey(final K key) {mReadWriteLock.readLock().lock();final boolean result = mCache.containsKey(key);mReadWriteLock.readLock().unlock();return result;}public void clear() {mReadWriteLock.writeLock().lock();mCache.clear();mReadWriteLock.writeLock().unlock();}public void setCapacity(int capacity) {mCapacity = capacity;}}
private static int[] SAMPLE_IMAGES = new int[] {R.drawable.gallery_photo_1, R.drawable.gallery_photo_2,R.drawable.gallery_photo_3, R.drawable.gallery_photo_4,R.drawable.gallery_photo_5, R.drawable.gallery_photo_6,R.drawable.gallery_photo_7, R.drawable.gallery_photo_8 };mCoverFlow = new CoverFlowOpenGL(this);mCoverFlow.setCoverFlowListener(new CoverFlowOpenGL.CoverFlowListener() {@Overridepublic int getCount(CoverFlowOpenGL view) {return SAMPLE_IMAGES_2.length;}@Overridepublic View getView(CoverFlowOpenGL anotherCoverFlow,int position) {return BitmapFactory.decodeResource(getResources(),SAMPLE_IMAGES_2[position]);}@Overridepublic void tileOnTop(CoverFlowOpenGL view, int position) {// you can control what will happen when one image is in// middle}@Overridepublic void topTileClicked(CoverFlowOpenGL view,int position) {// you can control what will happen when the image in// middle is clicked}});