Android Coverflow Gallery 的关键源码解析【Android】【OpenGL】




CoverFlowOpenGL.java


/*
 * Copyright 2013 - Android Coverflow Gallery. (Vladyslav Yarovyi)
 *
 * 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 com.masterofcode.android.coverflow_library;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.animation.AnimationUtils;
import com.masterofcode.android.coverflow_library.listeners.CoverFlowListener;
import com.masterofcode.android.coverflow_library.listeners.DataChangedListener;
import com.masterofcode.android.coverflow_library.render_objects.Background;
import com.masterofcode.android.coverflow_library.render_objects.CoverImage;
import com.masterofcode.android.coverflow_library.render_objects.EmptyImage;
import com.masterofcode.android.coverflow_library.utils.CoverflowQuery;
import com.masterofcode.android.coverflow_library.utils.DataCache;
import com.masterofcode.android.coverflow_library.utils.EQuality;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import java.util.ArrayList;
import java.util.List;

/**
 * Custom Cover Flow Gallery View. This core class is responsible for drawing
 * all images.
 * 
 * @author skynet67
 */
public class CoverFlowOpenGL extends GLSurfaceView implements
		GLSurfaceView.Renderer {

	public static final String TAG = "CoverFlowOpenGL";
	// 滑动的最小像素偏移量
	private static final int TOUCH_MINIMUM_MOVE = 5;
	// 动画阻力
	private static final float FRICTION = 20.0f;	// 10.f
	// 滑动的最大速度
	private static final float MAX_SPEED = 5.0f;	// 6.f
	
	// ------------------------------
	private static final int COEF = 5;	// 10	// 影响到滑动多少像素能切换到下一 tile
	private static final int OFFSET = 0; // 5
	private static final float RCOEF = 0.25f;
	// ------------------------------
	
	private int maxTiles = 21; // 缓存中总共可以容纳的 tiles
	private int visibleTiles = 4; // 除了中间,左侧/右侧最多可见 tiles 数

	private int imageSize = 512; // 统一的图像大小(外框)

	private float mOffset;	// 记录偏移了多少 tiles(当前中间tile的索引)
	//private int mLastOffset;
	//private RectF mTouchRect;	// click 显示 toast 的范围

	private int mWidth;
	private int mHeight;

	private boolean mTouchMoved;
	private float mTouchStartPos;
	private float mTouchStartX;	// 按下起始的坐标
	private float mTouchStartY;

	private float mStartOffset;	// 起始 tile 的索引
	private long mStartTime;

	private float mStartSpeed;
	private float mDuration;
	private Runnable mAnimationRunnable;
	private VelocityTracker mVelocity;

	private CoverFlowListener mListener;
	private DataCache<Integer, CoverImage> mCache;
	private CoverflowQuery aQuery;
	private List<String> imagesList;
	private List<CoverImage> images;
	private Activity mActivity;

	private EmptyImage emptyImage;
	private Background mBackground;
	// true:RGB_565  false:ARGB_8888
	private boolean showBlackBars;

	public CoverFlowOpenGL(Context context) {
		super(context);

		init();
	}

	public CoverFlowOpenGL(Context context, AttributeSet attrs) {
		super(context, attrs);

		init();
	}

	public void setActivity(Activity activity) {
		this.mActivity = activity;
		aQuery = new CoverflowQuery(mActivity);
	}

	public void init() {

		setEGLConfigChooser(8, 8, 8, 8, 16, 0);

		setRenderer(this);
		// 设置渲染模式
		setRenderMode(RENDERMODE_WHEN_DIRTY);

		getHolder().setFormat(PixelFormat.TRANSLUCENT);
		setZOrderMediaOverlay(true);
		setZOrderOnTop(true);

		// int cacheForVisibleTiles = (visibleTiles * 2 + 1) + 10; //
		// visible_left + center + visible_right + 10 additional
		mCache = new DataCache<Integer, CoverImage>(maxTiles); 
		// Math.min(maxTiles,cacheForVisibleTiles));
		// mLastOffset = 0;
		mOffset = 0;
	}

	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		gl.glEnable(GL10.GL_TEXTURE_2D); // Enable Texture Mapping ( NEW )
		gl.glShadeModel(GL10.GL_SMOOTH); // Enable Smooth Shading
		gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Black Background
		gl.glDisable(GL10.GL_DEPTH_TEST); // Enables Depth Testing

		// Really Nice Perspective Calculations
		gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
	}

	public void onSurfaceChanged(GL10 gl, int w, int h) {
		mCache.clear();

		mWidth = w;
		mHeight = h;

		if (mBackground != null) {
			mBackground.setGL(gl);
			mBackground.initBuffers(w, h);
			mBackground.loadGLTexture();
		}

		if (emptyImage != null) {
			emptyImage.setGL(gl);
			emptyImage.setViewportData(w, h);
			emptyImage.setImageSize(imageSize);
			emptyImage.loadGLTexture();
		}

		if (images != null && images.size() > 0) {
			for (CoverImage cImg : images) {
				if (cImg != null) {
					cImg.setGL(gl);
					cImg.setViewportData(w, h);
					cImg.removeTexture();
				}
			}
		}

//		float imagew = w * RCOEF / 2.0f;
//		float imageh = h * RCOEF/ 2.0f;
		// 屏幕中心的一定范围,响应 click 事件
//		mTouchRect = new RectF(w / 2 - imagew, h / 2 - imageh, w / 2 + imagew,
//				h / 2 + imageh);

		gl.glViewport(0, 0, w, h); // Reset The Current Viewport

		// 选择投影矩阵
		gl.glMatrixMode(GL10.GL_PROJECTION);
		// 重置投影矩阵
		gl.glLoadIdentity();
		GLU.gluOrtho2D(gl, 0, w, 0, h);

		gl.glMatrixMode(GL10.GL_MODELVIEW);
		gl.glLoadIdentity();

		// updateCache();
	}

	@Override
	public 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;
	}

	// 对 offset 的范围进行截断
	private float checkValid(float off) {
		int max = imagesList.size() - 1;
		if (off < 0)
			return 0;
		else if (off > max)
			return max;

		return off;
	}

	private void touchBegan(MotionEvent event) {
		endAnimation();

		float x = event.getX();
		mTouchStartX = x;
		mTouchStartY = event.getY();
		mStartTime = System.currentTimeMillis();
		// 起始组件偏移
		mStartOffset = mOffset;
		Log.e(TAG, "touchBegan mStartOffset: " + mStartOffset);

		mTouchMoved = false;

		// -2.5 ~ 2.5
		mTouchStartPos = (x / mWidth) * COEF - OFFSET;
		mTouchStartPos /= 2;

		// ------- 统计滑动速度 ------
		mVelocity = VelocityTracker.obtain();
		mVelocity.addMovement(event);
		// ------- 统计滑动速度 ------
	}

	private void touchMoved(MotionEvent event) {
		// -2.5 ~ 2.5
		float pos = (event.getX() / mWidth) * COEF - OFFSET;
		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 = checkValid(mStartOffset + mTouchStartPos - pos);
		//Log.e(TAG, "touchMoved mOffset: " + mOffset);

		requestRender();

		// ------- 统计滑动速度 ------
		mVelocity.addMovement(event);
		// ------- 统计滑动速度 ------
	}

	private void touchEnded(MotionEvent event) {
		float pos = (event.getX() / mWidth) * COEF - OFFSET;
		pos /= 2;

		if (mTouchMoved) {
			mStartOffset += mTouchStartPos - pos;
			mStartOffset = checkValid(mStartOffset);
			// 更新组件的偏移(注意是浮点数)
			mOffset = mStartOffset;

			// ------- 统计滑动速度 ------
			mVelocity.addMovement(event);
			// 初始化速率单位
			// 1000表示 1秒内运动了多少像素
			mVelocity.computeCurrentVelocity(1000);
			double speed = mVelocity.getXVelocity();
			speed = (speed / mWidth) * COEF;
			if (speed > MAX_SPEED)
				speed = MAX_SPEED;
			else if (speed < -MAX_SPEED)
				speed = -MAX_SPEED;
			// ------- 统计滑动速度 ------

			// 开始一段时间的滑动动画
			startAnimation(-speed);
		} else {
			// MainActivity中实现接口
//			if (mTouchRect.contains(event.getX(), event.getY())) {
//				mListener.topTileClicked(this, (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);
		nearest = checkValid((float) nearest);

		// 计算动画速度
		mStartSpeed = (float) Math.sqrt(Math.abs(nearest - mStartOffset)
				* FRICTION * 2);
		// 反向动画速度
		if (nearest < mStartOffset)
			mStartSpeed = -mStartSpeed;

		Log.i(TAG, "startAnimation! mStartSpeed: " + mStartSpeed);

		// 计算动画总时长
		mDuration = Math.abs(mStartSpeed / FRICTION);
		// 记录动画起始时间
		mStartTime = AnimationUtils.currentAnimationTimeMillis();

		// 动画 Runnable对象
		mAnimationRunnable = new Runnable() {
			public void run() {
				driveAnimation();
			}
		};
		// △ 继续执行driveAnimation
		post(mAnimationRunnable);
	}

	private void driveAnimation() {
		//Log.i(TAG, "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);
			mOffset = checkValid(mOffset);
			Log.i(TAG, "endAnimation: mOffset = " + mOffset);

			// 刷新动画
			requestRender();
			// △ 停止执行driveAnimation()
			removeCallbacks(mAnimationRunnable);
			mAnimationRunnable = null;

			// updateCache();
		}
	}

	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 = checkValid(mStartOffset + delta);
		//Log.i(TAG, "Update Anim: mOffset = " + mOffset);

		requestRender();
	}

	// private void updateCache(){
	// int diff = VISIBLE_TILES * 2 + 5;
	// int diffLeft = Math.max(0,(int)mOffset - diff);
	// int diffRight = Math.min(images.size(),(int)mOffset + diff);
	//
	// for(int i = 0; i < images.size(); i++){
	// if(mCache.containsKey(i) && (i < diffLeft || i > diffRight)){
	// mCache.removeObjectForKey(i);
	// } else
	// if(!mCache.containsKey(i) && i >= diffLeft && i <= diffRight){
	// CoverImage img = images.get(i);
	// img.tryLoadTexture(dataChangedListener, i);
	// mCache.putObjectForKey(i, img);
	// }
	// }
	// }

	public int getMaxTiles() {
		return maxTiles;
	}

	public void setMaxTiles(int maxTiles) {
		this.maxTiles = maxTiles;
		mCache = new DataCache<Integer, CoverImage>(maxTiles);
	}

	public int getVisibleTiles() {
		return visibleTiles;
	}

	public void setVisibleTiles(int visibleTiles) {
		this.visibleTiles = visibleTiles;
	}

	public void setImageQuality(EQuality size) {
		imageSize = size.getValue();
	}

	public void setImageShowBlackBars(boolean value) {
		showBlackBars = value;
	}

	public void setImagesList(List<String> imagesList) {

		this.imagesList = imagesList;

		if (imagesList != null && imagesList.size() > 0) {
			images = new ArrayList<CoverImage>(imagesList.size());

			for (String imageUrl : imagesList) {
				CoverImage ci = new CoverImage(mActivity, aQuery)
						.setUrl(imageUrl).setImageSize(imageSize)
						.setShowBlackBars(showBlackBars);
				images.add(ci);
			}
		}
	}

	
	public void setCoverFlowListener(CoverFlowListener listener) {
		mListener = listener;
	}

	public void setSelection(int position) {
		endAnimation();

		if (images != null && images.size() > 0) {
			position = Math.min(position, images.size() - 1);
		}
		mOffset = position;
		Log.w(TAG, "setSelection: mOffset = " + mOffset);

		requestRender();
	}

	public void setBackgroundRes(int res) {
		mBackground = new Background(mActivity, res);
	}

	public void setEmptyRes(int res) {
		emptyImage = new EmptyImage(mActivity, res);
	}

	// public void setBackgroundUrl(String url){
	// mBackground = new Background(mActivity, url);
	// }

	// public void setEmtpyUrl(String url){
	// emptyImage = new EmptyImage(mActivity, url);
	// }
	
	public void onDrawFrame(GL10 gl) {
		gl.glMatrixMode(GL10.GL_MODELVIEW);
		gl.glLoadIdentity();

		// clear Screen and Depth Buffer
		gl.glDisable(GL10.GL_DEPTH_TEST);
		gl.glClearColor(0, 0, 0, 0);
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

		// Drawing
		gl.glTranslatef(0.0f, 0.0f, 0.0f); 

		if (mBackground != null) {
			mBackground.draw(gl);
		}

		final float offset = mOffset;
		//Log.e(TAG, "onDrawFrame: offset = " + offset);

		int i;

		int max = imagesList != null ? imagesList.size() - 1 : 0;
		// 中间的最大 tile 的索引(取整)
		int mid = (int) Math.floor(offset + 0.5);
		// 可见的最左侧  tile 的索引(取整)
		int iStartPos = mid - visibleTiles;
		//Log.e(TAG, "onDrawFrame: iStartPos = " + iStartPos);

		if (iStartPos < 0)
			iStartPos = 0;
		// 绘制左侧 tiles
		for (i = iStartPos; i < mid; ++i) {
			drawTile(i, i - offset, gl);	// 如果用  mid 就没有过渡特效
		}

		// 可见的最右侧的  tile 的索引
		int iEndPos = mid + visibleTiles;
		//Log.e(TAG, "onDrawFrame: iEndPos = " + iEndPos);
		// 绘制右侧和中间的 tiles
		if (iEndPos > max)
			iEndPos = max;
		for (i = iEndPos; i >= mid; --i) {
			drawTile(i, i - offset, gl);	// 如果用  mid 就没有过渡特效
		}

		// MainActivity中实现接口
//		if (mLastOffset != (int) offset) {
//			mListener.tileOnTop(this, (int) offset);
//			mLastOffset = (int) offset;
//			//Log.e(TAG, "mLastOffset: " + offset);
//		}
	}

	private void drawTile(
			int position, 	// 当前的 tile
			float off,		// 当前 tile 离 中间的 tile 的偏移
			GL10 gl) {
		
		// ☆ 从键值对缓存中取出对应的 CoverImage ☆
		// 不用每次都去下载
		CoverImage cacheImg = mCache.objectForKey(position);

		boolean canDraw = false;

		if (cacheImg == null) {
			// 当前的 tile 所对应的图片
			cacheImg = images.get(position);
			cacheImg.tryLoadTexture(dataChangedListener, position);
			// 添加到 键值对 缓存中
			mCache.putObjectForKey(position, cacheImg);

			if (cacheImg.getTexture() != 0) {
				canDraw = true;
			}
		} else if (cacheImg.getTexture() != 0) {
			canDraw = true;
		}
		
		// 图像等比例缩放(还有稍稍偏移)后的大小
		float desiredSize = canDraw ? cacheImg.getDesiredSize() : emptyImage
				.getDesiredSize();
		// 一半宽度/一侧可见的tiles数
		float spread = (mWidth - desiredSize) * 0.5f / visibleTiles;
		// 水平偏移
		float trans = off * spread;
		// 根据离中心的距离,控制缩放比例
		float sc = 1.0f - (Math.abs(off) / (visibleTiles + 1));

		if (canDraw) {
			cacheImg.draw(gl, trans, sc);
		} else {
			emptyImage.draw(gl, trans, sc);
		}
	}

	// 定义监听器
	private DataChangedListener dataChangedListener = new DataChangedListener() {

		public void imageUpdated(int position) {
			synchronized (this) {
				// 显示范围:(mOffset - visibleTiles, mOffset + visibleTiles)
				if (mOffset - visibleTiles < position
						|| position < mOffset + visibleTiles) {
					requestRender();
				}
			}
		}
	};
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShaderJoy

您的打赏是我继续写博客的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值