且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-08-13 23:39:40

Android Coverflow Gallery 的关键源码解析【Android】【OpenGL】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();
                }
            }
        }
    };
}