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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: Android Coverflow Gallery 的关键源码解析【Android】【OpenGL】

image.pngimage.png

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();
        }
      }
    }
  };
}
目录
相关文章
|
3天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
3天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
3天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
25天前
|
存储 Linux API
深入探索Android系统架构:从内核到应用层的全面解析
本文旨在为读者提供一份详尽的Android系统架构分析,从底层的Linux内核到顶层的应用程序框架。我们将探讨Android系统的模块化设计、各层之间的交互机制以及它们如何共同协作以支持丰富多样的应用生态。通过本篇文章,开发者和爱好者可以更深入理解Android平台的工作原理,从而优化开发流程和提升应用性能。
|
27天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
54 12
|
22天前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
25天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
4天前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。