Android TV

OpenGL ES实现TV端焦点选中动画

2022-10-06  本文已影响0人  放羊娃华振

一、概述

在TV上,控件的选中状态是十分重要的,标示当前可以操作的位置。借助于本次学习OpenGL的时间,使用OpenGL实现了一个比较炫酷的焦点选中动画。Demo截图如下:


image.png

由于图片不能表达动画的效果,建议自己实现下功能在查看下具体效果。

二、基本工作内容

1.控件定位
2.呼吸效果
3.气泡动画
4.可视控件
5.性能优化
详细介绍之前先看下该效果在屏幕上绘制的层图。最底下是我们实现的UI控件,在控件的上层绘制了动画层。动画层有分为呼吸动画、气泡动画、可视控件。


image.png
控件定位:
  这个不是本期OpenGL的重点,每个人都有自己的方式,就不做赘述。
呼吸动画:
 呼吸动画就是蓝色的缩放动画,实现该动画就是修改OpenGL顶点着色器的显示范围。
  核心代码:
package com.stormdzh.openglanimation.customview.boder;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import com.stormdzh.openglanimation.R;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
* @Description: 背景
* @Author: dzh
* @CreateDate: 2020-06-23 18:00
*/
public class BgRender {
private int backguardTexture = GLESTools.NO_TEXTURE;
private ShortBuffer drawIndecesBuffer;
private FloatBuffer shapeBuffer;
private FloatBuffer textrueBuffer;
//计算 物体实际的渲染位置
private float squareVertices[] = {
-0.8f, 0.8f,
-0.8f, -0.8f,
0.8f, -0.8f,
0.8f, 0.8f
};
private int glProgram;
private int glBgTextureLoc;
private int glBgPostionLoc;
private int glBgTextureCoordLoc;
private String vertexShader_filter = "" +
"attribute vec4 aBgPosition;\n" +
"attribute vec2 aBgTextureCoord;\n" +
"varying vec2 vBgTextureCoord;\n" +
"void main(){\n" +
" gl_Position= aBgPosition;\n" +
" vBgTextureCoord = aBgTextureCoord;\n" +
"}";
private String fragmentShader_filter = "" +
"precision mediump float;\n" +
"varying mediump vec2 vBgTextureCoord;\n" +
"uniform sampler2D uBgTexture;\n" +
"void main(){\n" +
" lowp vec4 c1 = texture2D(uBgTexture,vec2(vBgTextureCoord.x,1.0-vBgTextureCoord.y));\n" +
" lowp vec4 outputColor = c1;\n" +
" gl_FragColor = outputColor;\n" +
"}";
private Bitmap bitmap;
private Context mContext;
private float setp = 1.02f;
private boolean isSurfaceChanged = false;
public BgRender(Context context) {
mContext = context;
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
initBuffer();
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
if (!isSurfaceChanged) {
isSurfaceChanged = true;
bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.focus_border_bg);
// bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.img_opgl_test);
backguardTexture = GLESTools.loadTexture(bitmap, GLESTools.NO_TEXTURE);
//设置环绕方式
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
//过滤方式
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
glProgram = GLESTools.createProgram(vertexShader_filter, fragmentShader_filter);
GLES20.glUseProgram(glProgram);
glBgTextureLoc = GLES20.glGetUniformLocation(glProgram, "uBgTexture");
glBgPostionLoc = GLES20.glGetAttribLocation(glProgram, "aBgPosition");
glBgTextureCoordLoc = GLES20.glGetAttribLocation(glProgram, "aBgTextureCoord");
if (bitmap != null) {
bitmap.recycle();
bitmap = null;
}
}
}
public void onDrawFrame(GL10 gl) {
updateBgSize();
//draw bg
GLES20.glUseProgram(glProgram);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, backguardTexture);
GLES20.glUniform1i(glBgTextureLoc, 0);
GLHelper.enableVertex(glBgPostionLoc, glBgTextureCoordLoc, shapeBuffer, textrueBuffer);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawIndecesBuffer.limit(), GLES20.GL_UNSIGNED_SHORT, drawIndecesBuffer);
GLES20.glFinish();
GLHelper.disableVertex(glBgPostionLoc, glBgTextureCoordLoc);
}
public void updateBgSize() {
float v = shapeBuffer.get(0);
if (Math.abs(v) >= 2.2) {
for (int i = 0; i < 8; i++) {
shapeBuffer.put(i, squareVertices[i]);
}
} else {
for (int i = 0; i < 8; i++) {
shapeBuffer.put(i, shapeBuffer.get(i) * setp);
}
}
shapeBuffer.position(0);
}
private void initBuffer() { //初始化坐标顶点 与 纹理顶点
drawIndecesBuffer = GLHelper.getDrawIndecesBuffer();
shapeBuffer = GLHelper.getShapeVerticesBuffer();
textrueBuffer = GLHelper.getScreenTextureVerticesBuffer();
}

public void destroy() {
if (drawIndecesBuffer != null) {
drawIndecesBuffer.clear();
drawIndecesBuffer = null;
}
if (shapeBuffer != null) {
shapeBuffer.clear();
shapeBuffer = null;
}
if (textrueBuffer != null) {
textrueBuffer.clear();
textrueBuffer = null;
}
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
}
}
}
气泡动画:

气泡动画就是控制四周飘动的气泡。
核心代码:

package com.stormdzh.openglanimation.customview.boder;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import com.stormdzh.openglanimation.R;
import com.stormdzh.openglanimation.util.DeviceUtil;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
* @Description: 气泡
* @Author: dzh
* @CreateDate: 2020-06-23 20:38
*/
public class BubbleRenderer {
private Context mContext;
private int ppTexture = GLESTools.NO_TEXTURE;
private ShortBuffer drawIndecesBuffer;
private FloatBuffer textrueBuffer;
//气泡的宽高 x,y
private float bubbleW, bubbleH;
private float bublePosition = 0.8f;
//泡泡 大小范围
private final float minScale = 0.5f;
private final float maxScale = 1.8f;

//气泡方向
private final int OrientationLeft = 1;
private final int OrientationRight = 2;
private final int OrientationTop = 3;
private final int OrientationBottom = 4;
//泡泡 随机飘动偏移范围 每帧
private final int Scope = 50;// dp
private int pixScope;
//相隔 多少像素 生成一个 气泡
private final int addPPUnit = 40;//dp
private int pixPPUnit;
//随机
private Random random = new Random();
//气泡列表
private List<BubbleItem> mBubbleList = new ArrayList<>();
private int mWidth, mHeight;
private Bitmap bitmap;
//变换矩阵
private int glProgram;
private int glPPTextureLoc;
private int glPPPostionLoc;
private int glPPTextureCoordLoc;
private String vertexShader_filter = "" +
"attribute vec4 aPPPosition;\n" +
"attribute vec2 aPPTextureCoord;\n" +
"varying vec2 vPPTextureCoord;\n" +
"void main(){\n" +
" gl_Position= aPPPosition;\n" +
" vPPTextureCoord = aPPTextureCoord;\n" +
"}";
private String fragmentShader_filter = "" +
"precision mediump float;\n" +
"varying mediump vec2 vPPTextureCoord;\n" +
"uniform sampler2D uPPTexture;\n" +
"void main(){\n" +
" lowp vec4 c1 = texture2D(uPPTexture,vec2(vPPTextureCoord.x,1.0-vPPTextureCoord.y));\n" +
" lowp vec4 outputColor = c1;\n" +
" gl_FragColor = outputColor;\n" +
"}";
public BubbleRenderer(Context context) {
this.mContext = context;
}
private void initBuffer() {
drawIndecesBuffer = GLHelper.getDrawIndecesBuffer();
textrueBuffer = GLHelper.getScreenTextureVerticesBuffer();
}
//生成一个气泡对象
public BubbleItem getBubbleItem(int orientation, int index) {
BubbleItem item = new BubbleItem();
item.orientation = orientation;
item.scaleSize = random.nextFloat() * (maxScale - minScale) + minScale;
item.index = index;
item.setPPLocation();
return item;
}
//设置气泡数组
private void initBubbleData() {
mBubbleList.clear();
int xNum = (int) Math.ceil((float) mWidth / (float) pixPPUnit) + 1;
int yNum = (int) Math.ceil((float) mHeight / (float) pixPPUnit) + 1;
for (int i = 0; i < yNum; i++) {
mBubbleList.add(getBubbleItem(OrientationLeft, i));
}
for (int i = 0; i < yNum; i++) {
mBubbleList.add(getBubbleItem(OrientationRight, i));
}
for (int i = 0; i < xNum; i++) {
mBubbleList.add(getBubbleItem(OrientationTop, i));
}
for (int i = 0; i < xNum; i++) {
mBubbleList.add(getBubbleItem(OrientationBottom, i));
}
}
private boolean isInit=false;
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
if(isInit) return;
isInit=true;
pixScope = DeviceUtil.dip2px(mContext, Scope);
pixPPUnit = DeviceUtil.dip2px(mContext, addPPUnit);
initBuffer();
// Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.focus_border_pp);
bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.focus_bubble_red);
ppTexture = GLESTools.loadTexture(bitmap, GLESTools.NO_TEXTURE);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
glProgram = GLESTools.createProgram(vertexShader_filter, fragmentShader_filter);
GLES20.glUseProgram(glProgram);
glPPTextureLoc = GLES20.glGetUniformLocation(glProgram, "uPPTexture");
glPPPostionLoc = GLES20.glGetAttribLocation(glProgram, "aPPPosition");
glPPTextureCoordLoc = GLES20.glGetAttribLocation(glProgram, "aPPTextureCoord");
if (bitmap != null) {
bitmap.recycle();
bitmap = null;
}
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
// bubbleW = 0.02f;
bubbleH = 0.02f;
if (mWidth != width) {
this.mWidth = width;
this.mHeight = height;
bubbleW = bubbleH * height / width;
if (mBubbleList != null) {
for (int i = 0; i < mBubbleList.size(); i++) {
mBubbleList.get(i).destroy();
}
mBubbleList.clear();
}
initBubbleData();
for (BubbleItem ppItem : mBubbleList) {
ppItem.setPPLocation();
}
}
}
public void onDrawFrame(GL10 gl) {
GLES20.glClearColor(1.0f, 1.0f, 1f, 1f);
GLES20.glUseProgram(glProgram);
GLES20.glEnableVertexAttribArray(glPPPostionLoc);
GLES20.glEnableVertexAttribArray(glPPTextureCoordLoc);
for (BubbleItem ppItem : mBubbleList) {
ppItem.drawPP();
}
GLES20.glFinish();
GLHelper.disableVertex(glPPPostionLoc, glPPTextureCoordLoc);
}
public void destroy() {
if (drawIndecesBuffer != null) {
drawIndecesBuffer.clear();
drawIndecesBuffer = null;
}
if (textrueBuffer != null) {
textrueBuffer.clear();
textrueBuffer = null;
}
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
}
if (random != null) {
random = null;
}
if (mBubbleList != null) {
destroyBubble();
mBubbleList.clear();
mBubbleList = null;
}
}
private void destroyBubble() {
for (int i = 0; i < mBubbleList.size(); i++) {
mBubbleList.get(i).destroy();
}
}
public class BubbleItem {
public int index;
public float scaleSize = 1.0f;
public int orientation = OrientationLeft;
private float ppVertices[];
private FloatBuffer shapeBuffer = GLHelper.getShapeVerticesBuffer();
private float x, y;
public void setPPLocation() {
//气泡顶点坐标
ppVertices = Arrays.copyOf(GLHelper.SquareVertices, GLHelper.SquareVertices.length);
ppVertices[0] = -bubbleW;
ppVertices[1] = bubbleH;
ppVertices[2] = -bubbleW;
ppVertices[3] = -bubbleH;
ppVertices[4] = bubbleW;
ppVertices[5] = -bubbleH;
ppVertices[6] = bubbleW;
ppVertices[7] = bubbleH;
bubbleScale(scaleSize);
int mideaHei = mHeight / 2;
int mideaWid = mWidth / 2;
if (orientation == OrientationLeft) {
float hy = index * pixPPUnit;
if (hy < mideaHei) {
x = -bublePosition;
y = hy / mideaHei;
if (y > bublePosition) {
y = bublePosition;
}
} else {
x = -bublePosition;
y = -(hy - mideaHei) / mideaHei;
if (y < -bublePosition) {
y = -bublePosition;
}
}
} else if (orientation == OrientationRight) {
float hy = index * pixPPUnit;
if (hy < mideaHei) {
x = bublePosition;
y = hy / mideaHei;
if (y > bublePosition) {
y = bublePosition;
}
} else {
x = bublePosition;
y = -(hy - mideaHei) / mideaHei;
if (y < -bublePosition) {
y = -bublePosition;
}
}
} else if (orientation == OrientationTop) {
float hx = index * pixPPUnit;
if (hx < mideaWid) {
x = -hx / mideaWid;
y = bublePosition;
if (x > -bublePosition) {
x = -bublePosition;
}
} else {
x = (hx - mideaWid) / mideaWid;
y = bublePosition;
if (x > bublePosition) {
x = bublePosition;
}
}
} else if (orientation == OrientationBottom) {
float hx = index * pixPPUnit;
if (hx < mideaWid) {
x = -hx / mideaWid;
y = -bublePosition;
if (x > -bublePosition) {
x = -bublePosition;
}
} else {
x = (hx - mideaWid) / mideaWid;
y = -bublePosition;
if (x > bublePosition) {
x = bublePosition;
}
}
}
bubblelocation(x, y);
shapeBuffer.clear();
shapeBuffer.put(ppVertices);
shapeBuffer.position(0);
}
private void bubbleScale(float scaleSize) {
for (int i = 0; i < ppVertices.length; i++) {
ppVertices[i] = ppVertices[i] * scaleSize;
}
}
private void bubblelocation(float x, float y) {
ppVertices[0] = ppVertices[0] + x;
ppVertices[1] = ppVertices[1] + y;
ppVertices[2] = ppVertices[2] + x;
ppVertices[3] = ppVertices[3] + y;
ppVertices[4] = ppVertices[4] + x;
ppVertices[5] = ppVertices[5] + y;
ppVertices[6] = ppVertices[6] + x;
ppVertices[7] = ppVertices[7] + y;
}
public void drawPP() {
float random = (float) (Math.random() / 500);
float randomD = (float) (Math.random() / 320);
int rd = Math.random() > 0.5 ? 1 : 0;
if (orientation == OrientationLeft) {
bubblelocation(-random, rd == 1 ? randomD : (0 - randomD));
} else if (orientation == OrientationRight) {
bubblelocation(random, rd == 1 ? randomD : (0 - randomD));
} else if (orientation == OrientationTop) {
bubblelocation(rd == 1 ? randomD : (0 - randomD), random);
} else if (orientation == OrientationBottom) {
bubblelocation(rd == 1 ? randomD : (0 - randomD), -random);
}
shapeBuffer.clear();
shapeBuffer.put(ppVertices);
shapeBuffer.position(0);
if (bubbleOut()) {
setPPLocation();
}

GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, ppTexture);
GLES20.glUniform1i(glPPTextureLoc, 0);
GLHelper.updateVertex(glPPPostionLoc, glPPTextureCoordLoc, shapeBuffer, textrueBuffer);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawIndecesBuffer.limit(), GLES20.GL_UNSIGNED_SHORT, drawIndecesBuffer);

}
private boolean bubbleOut() {
for (int i = 0; i < ppVertices.length; i++) {
if (Math.abs(ppVertices[i]) >= 1) {
return true;
}
}
return false;
}

public void destroy() {
if (shapeBuffer != null) {
shapeBuffer.clear();
shapeBuffer = null;
}
ppVertices = null;
}
}
}
可视控件:

可视控件就是真实控件在OpenGL上绘制的图形。
核心代码:

public synchronized void updateRunderViewTexture() {
//drawview to Texture
Canvas canvas = rootViewSuface.lockCanvas(null);
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
drawView.draw(canvas);
rootViewSuface.unlockCanvasAndPost(canvas);
rootViewSufaceTexture.updateTexImage();
}
性能优化:

就是优化cpu和内存 ,目前cpu使用率的优化是降低onDrawFrame方法的调用次数,在测试中我把频率改为30毫秒一次,效果还行。内存优化就是需要及时的释放各种图片已经内存数据资源,需要调整方法的触发顺序和逻辑,既能满足实现功能,又能释放资源。

上一篇下一篇

猜你喜欢

热点阅读