您的位置:首页 > 移动开发 > Android开发

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

2017-01-02 15:54 477 查看




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();
}
}
}
};
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  opengl android