[转载]Android OpenGL教程-第五课 - onicewalk的专栏 - 博客频道 - CSDN.NET

mikel阅读(767)

来源: [转载]Android OpenGL教程-第五课 – onicewalk的专栏 – 博客频道 – CSDN.NET

Android OpenGL 教程

第五课

3D空间:

 

我们使用多边形和四边形创建3D物体,在这一课里,我们把三角形变为立体的金子塔形状,把四边形变为立方体。

先看看三角形的顶点变成啥了

private float[] mTriangleArray = {

0.0f,1.0f,0.0f,

-1.0f,-1.0f,1.0f,

1.0f,-1.0f,1.0f,

 

0.0f,1.0f,0.0f,

1.0f,-1.0f,1.0f,

1.0f,-1.0f,-1.0f,

 

0.0f,1.0f,0.0f,

1.0f,-1.0f,-1.0f,

-1.0f,-1.0f,-1.0f,

 

0.0f,1.0f,0.0f,

-1.0f,-1.0f,-1.0f,

-1.0f,-1.0f,1.0f

};

private FloatBuffer mTriangleBuffer;

一个四个面,每个面三个点,地面没画。

你们仔细看看每个面,都是按逆时针方向画的。当然,如果你没有增加Cull剔除代码(上节课最后提到),至于顺时针,逆时针都无所谓,都画。但是我们还是推荐使用按照方向来写的array。

 

三角形的颜色数组来了

private float[] mColorArray={

1.0f,0.0f,0.0f,1.0f,

0.0f,1.0f,0.0f,1.0f,

0.0f,0.0f,1.0f,1.0f,

 

1.0f,0.0f,0.0f,1.0f,

0.0f,0.0f,1.0f,1.0f,

0.0f,1.0f,0.0f,1.0f,

 

1.0f,0.0f,0.0f,1.0f,

0.0f,1.0f,0.0f,1.0f,

0.0f,0.0f,1.0f,1.0f,

 

1.0f,0.0f,0.0f,1.0f,

0.0f,0.0f,1.0f,1.0f,

0.0f,1.0f,0.0f,1.0f,

};

private FloatBuffer mColorBuffer;

注意,同一个点是同一个颜色。

 

立方体的颜色数组来了

private float[] mQuadColorArray={

0.0f,1.0f,0.0f,1.0f,

0.0f,1.0f,0.0f,1.0f,

0.0f,1.0f,0.0f,1.0f,

0.0f,1.0f,0.0f,1.0f,

 

1.0f,0.5f,0.0f,1.0f,

1.0f,0.5f,0.0f,1.0f,

1.0f,0.5f,0.0f,1.0f,

1.0f,0.5f,0.0f,1.0f,

 

1.0f,0.0f,0.0f,1.0f,

1.0f,0.0f,0.0f,1.0f,

1.0f,0.0f,0.0f,1.0f,

1.0f,0.0f,0.0f,1.0f,

 

1.0f,1.0f,0.0f,1.0f,

1.0f,1.0f,0.0f,1.0f,

1.0f,1.0f,0.0f,1.0f,

1.0f,1.0f,0.0f,1.0f,

 

0.0f,0.0f,1.0f,1.0f,

0.0f,0.0f,1.0f,1.0f,

0.0f,0.0f,1.0f,1.0f,

0.0f,0.0f,1.0f,1.0f,

 

1.0f,0.0f,1.0f,1.0f,

1.0f,0.0f,1.0f,1.0f,

1.0f,0.0f,1.0f,1.0f,

1.0f,0.0f,1.0f,1.0f,

 

};

 

private FloatBuffer mQuadColorBuffer;

不要晕,后面有了纹理就好了,不过这是基础,一定要掌握。

 

画图的代码来了


gl.glTranslatef(-1.5f, 0.0f, -6.0f);
gl.glRotatef(rtri, 0.0f, 1.0f, 0.0f);                  //绕Y轴旋转
//     gl.glColor4f(1f, 1f, 1f, 1f);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);

gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mTriangleBuffer);
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 12);     //四棱锥有4个面,每个面3个点

gl.glLoadIdentity();
gl.glTranslatef(1.5f, 0.0f, -6.0f);
gl.glRotatef(rquad, 1.0f, 0.0f, 0.0f);

gl.glColorPointer(4, GL10.GL_FLOAT, 0, mQuadColorBuffer);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mQuadsBuffer);
//依次画6个面
for (int i=0; i<6; i++){
gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, i*4, 4);
}

rtri+=0.2f;
rquad-=0.15f;

 

[转载]Android OpenGL教程-第四课 - onicewalk的专栏 - 博客频道 - CSDN.NET

mikel阅读(617)

来源: [转载]Android OpenGL教程-第四课 – onicewalk的专栏 – 博客频道 – CSDN.NET

Android OpenGL 教程

第四课

旋转:

 

在这一课里,我将教会你如何旋转三角形和四边形。左图中的三角形沿Y轴旋转,四边形沿着X轴旋转。

我们增加两个变量来控制这两个对象的旋转。这两个变量加在程序的开始处其他变量的后面。它们是浮点类型的变量,使得我们能够非常精确地旋转对象。浮点数包含小数位置,这意味着我们无需使用1、2、3…的角度。你会发现浮点数是OpenGL编程的基础。新变量中叫做 rtri 的用来旋转三角形, rquad 旋转四边形。

private float rtri,rquad;

 

 

在OnDrawFrame里面增加代码如下:

gl.glRotatef(rtri, 0.0f, 1.0f, 0.0f);

三角形绕Y轴旋转;

gl.glRotatef(rquad, 1.0f, 0.0f, 0.0f);

四边形绕X轴旋转;

 

最后,增加旋转的角度

rtri+=0.2f;

rquad-=0.15f;

 

注:

一点测试代码

我们说了很久的顺时针,逆时针画图,到底有什么用呢

gl.glEnable(GL10.GL_CULL_FACE);

//设置openggl有剔除效果,就是看不到的面就不画,当然可以增加效率

gl.glFrontFace(GL10.GL_CCW);

//设置逆时针方向为正面

gl.glCullFace(GL10.GL_BACK);

//设置背面被剔除,不画

Cull就是剔除的意思

版权声明:本文为博主原创文章,未经博主允许不得转载。

[转载]Android OpenGL教程-第三课 - onicewalk的专栏 - 博客频道 - CSDN.NET

mikel阅读(658)

来源: [转载]Android OpenGL教程-第三课 – onicewalk的专栏 – 博客频道 – CSDN.NET

Android OpenGL 教程

第三课 给多边形上色:

作为第二课的扩展,我将叫你如何使用颜色。你将理解两种着色模式,在左图中,三角形用的是光滑着色,四边形用的是平面着色。

 

这次增加的代码不算多。

增加一个color的buffer

view plaincopy

  1. private float[] mColorArray={
  2.            1f,0f,0f,1f,     //红
  3.            0f,1f,0f,1f,     //绿
  4.            0f,0f,1f,1f      //蓝
  5.     };
  6. private FloatBuffer mColorBuffer;
  7. gl.glTranslatef(-1.5f, 0.0f, -6.0f);
  8. //     gl.glColor4f(1f, 1f, 1f, 1f); 注释掉原来的三角形的红色
  9. gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
  10. gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
  11. //使用数组作为颜色
  12. gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);
  13. //数组指向一个buffer
  14. gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mTriangleBuffer);
  15. gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);

 

 

这样话出一个颜色渐变的三角形

 

画四边形加上这一句,就出来了蓝色的四边形

gl.glColor4f(0.5f, 0.5f, 1f, 1f);

 

 

 

 

好了,我今天无意中看到了一个IPHONE的opengles教程,和我的很类似,所以学学openggl是必要的,上层的什么平台不重要,用什么语言不重要,底层的opengl的代码和API都是一样的。

 

版权声明:本文为博主原创文章,未经博主允许不得转载。

[转载]Android OpenGL教程-第二课 - onicewalk的专栏 - 博客频道 - CSDN.NET

mikel阅读(781)

来源: [转载]Android OpenGL教程-第二课 – onicewalk的专栏 – 博客频道 – CSDN.NET

Android OpenGL 教程

第二课 你的第一个多边形:

在第一个教程的基础上,我们添加了一个三角形和一个四边形。也许你认为这很简单,但你已经迈出了一大步,要知道任何在OpenGL中绘制的模型都会被分解为这两种简单的图形。

读完了这一课,你会学到如何在空间放置模型,并且会知道深度缓存的概念。

 

其他类不变,只更改OpenGLRenderer类。

首先,我们画一个三角形

主要是在OnDrawFrame里面画,使用的函数是

gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);

或者

gl.glDrawElements(GL10.GL_TRIANGLES,3,GL10.GL_FLOAT, mIndexBuffer);

我们先使用drawArray,drawElement里面要多用一个indexBuffer。

第一步,定义个array

 

view plaincopy

  1. private float[] mTriangleArray = {
  2.           0f,1f,0f,
  3.           -1f,-1f,0f,
  4.           1f,-1f,0f
  5.    };

 

这里实际上是定义了三角形的三个顶点,三个数分别是x,y,z的坐标,和数学里直角坐标系相同

0f,1f,0f 是上顶点

-1f,-1f,0f 是左下顶点

1f,-1f,0f 是右下顶点

定义个FloatBuffer,这是Android画三角形必须的结构

private FloatBuffer mTriangleBuffer;

来一个函数转换array到Buffer

我们直接上一个工具类

view plaincopy

  1. package com.xinli;
  2. import java.nio.ByteBuffer;
  3. import java.nio.ByteOrder;
  4. import java.nio.FloatBuffer;
  5. public class BufferUtil {
  6.     public static FloatBuffer mBuffer;
  7.     public static FloatBuffer floatToBuffer(float[] a){
  8.     //先初始化buffer,数组的长度*4,因为一个float占4个字节
  9.        ByteBuffer mbb = ByteBuffer.allocateDirect(a.length*4);
  10.     //数组排序用nativeOrder
  11.        mbb.order(ByteOrder.nativeOrder());
  12.        mBuffer = mbb.asFloatBuffer();
  13.        mBuffer.put(a);
  14.        mBuffer.position(0);
  15.        return mBuffer;
  16.     }
  17. }

 

注意:这里有个排序的问题,是使用大端(BIG_ENDIAN)还是用小端(LITTLE_ENDIAN),在android里面,opengl画图must use native order direct buffer,否则报错为ERROR/AndroidRuntime(6897): java.lang.IllegalArgumentException: Must use a native order direct Buffer这个错误在android1.6以上会出现,在1.5上不会出现。

这里我们直接使用allocateDirect和nativeOrder,就能满足android的要求

第二步:

在SurfaceCreate里面构建这个Buffer

mTriangleBuffer = BufferUtil.floatToBuffer(mTriangleArray);

第三步:

在OndrawFrame里面画三角形

gl.glColor4f(1.0f, 0.0f, 0.0f, 1.0f);

//三角形的颜色为红色,透明度为不透明

gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mTriangleBuffer);

//设置顶点,第一个参数是坐标的维数,这里是3维,第二个参数,表示buffer里面放的是float,第三个参数是0,是因为我们的坐标在数组中是紧凑的排列的,没有使用offset,最后的参数放的是存放顶点坐标的buffer

gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);

//画数组,第一个参数表示画三角形,第二个参数是first,第三个参数是count,表示在buffer里面的坐标的开始和个数

运行,会看到一个大三角形

 

 

下面,我们来变换坐标轴,画个小三角形,原理就是把坐标轴向远拉,意思就是让镜头向后拉,这样三角形就小了。

第一步:

在OnSurfaceCreate里面加入下面几行代码

view plaincopy

  1. float ratio = (float) width / height;
  2.        gl.glMatrixMode(GL10.GL_PROJECTION);
  3.        gl.glLoadIdentity();
  4.        gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);

 

这几行为透视图设置屏幕。意味着越远的东西看起来越小。这么做创建了一个现实外观的场景。此处透视按照基于窗口宽度和高度的45度视角来计算。0.1f,100.0f是我们在场景中所能绘制深度的起点和终点。

gl.glMatrixMode(GL10.GL_PROJECTION);指明接下来的两行代码将影响projection matrix(投影矩阵)。投影矩阵负责为我们的场景增加透视。 glLoadIdentity()近似于重置。它将所选的矩阵状态恢复成其原始状态。调用 glLoadIdentity()之后我们为场景设置透视图。

glMatrixMode(GL_MODELVIEW)指明任何新的变换将会影响 modelview matrix(模型观察矩阵)。模型观察矩阵中存放了我们的物体讯息。最后我们重置模型观察矩阵。如果您还不能理解这些术语的含义,请别着急。在以后的教程里,我会向大家解释。只要知道如果您想获得一个精彩的透视场景的话,必须这么做。

 

在OndrawFrame里面增加代码如下:

view plaincopy

  1. gl.glMatrixMode(GL10.GL_MODELVIEW);
  2. gl.glLoadIdentity();
  3. gl.glTranslatef(-1.5f, 0.0f, -6.0f);

 

当您调用glLoadIdentity()之后,您实际上将当前点移到了屏幕中心,X坐标轴从左至右,Y坐标轴从下至上,Z坐标轴从里至外。OpenGL屏幕中心的坐标值是X和Y轴上的0.0f点。中心左面的坐标值是负值,右面是正值。移向屏幕顶端是正值,移向屏幕底端是负值。移入屏幕深处是负值,移出屏幕则是正值。

glTranslatef(x, y, z)沿着 X, Y 和 Z 轴移动。根据前面的次序,下面的代码沿着X轴左移1.5个单位,Y轴不动(0.0f),最后移入屏幕6.0f个单位。注意在glTranslatef(x, y, z)中当您移动的时候,您并不是相对屏幕中心移动,而是相对与当前所在的屏幕位置。

这时候就能画出个小三角形了。

 

 

下面,我们来画一个四边形

四边形的顶点数组为:

view plaincopy

  1. private float[] mQuadsArray = {
  2.            1f,1f,0f,                           //右上
  3.            -1f,1f,0f,                          //左上
  4.            -1f,-1f,0f,                         //左下
  5.            1f,-1f,0f                           //右下
  6.     };
  7. //从这里可以看出,我们按照逆时针的方向画图
  8. private FloatBuffer mQuadsBuffer;

 

 

在OnDrawFrame里面添加代码

gl.glLoadIdentity();

//坐标向右移1.5个单位

gl.glTranslatef(1.5f, 0.0f, -6.0f);

gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mQuadsBuffer);

gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, 0, 4);

//画四边形

 

第一个参数是绘图模式,而且你现在看到两种可能的OpenGL绘图方式。 我想花时间来讨论现在不同的绘图模式。

它们是:

GL_POINTS
GL_LINES
GL_LINE_LOOP
GL_LINE_STRIP
GL_TRIANGLES
GL_TRIANGLE_STRIP
GL_TRIANGLE_FAN

我们还没有讨论点或线,所以我只介绍最后的三个 。在我开始之前,我要提醒你,顶点数组可能包含不止一个三角形,以便当您只看到一个物体顶点数组,你要知道,不仅限于这一点。

GL_TRIANGLES – 这个参数意味着OpenGL使用三个顶点来组成图形。所以,在开始的三个顶点,将用顶点1,顶点2,顶点3来组成一个三角形。完成后,在用下一组的三个顶点来组成三角形,直到数组结束。

GL_TRIANGLE_STRIP – OpenGL的使用将最开始的两个顶点出发,然后遍历每个顶点,这些顶点将使用前2个顶点一起组成一个三角形。所以第三个点与第一个,第二个生成一个三角形。第四个点将于第二个,第三个生成三角形。

也就是说,0,1,2这三个点组成一个三角形,1,2,3这三个点也组成一个三角形。

GL_TRIANGLE_FAN – 在跳过开始的2个顶点,然后遍历每个顶点,让OpenGL将这些顶点于它们前一个,以及数组的第一个顶点一起组成一个三角形。 第3个点将与 第二个点(前一个)和第一个点(第一个).生成一个三角形。

也就是说,同样是0,1,2,3这4个顶点。
在STRIP状态下是,0,1,2;1,2,3这2个三角形。
在FAN状态下是,0,1,2;0,2,3这2个三角形。

这次我们将使用 GL_TRIANGLE_FAN ,我们将在显示区域获得一个矩形。

 

 

 

版权声明:本文为博主原创文章,未经博主允许不得转载。

[转载]Android OpenGL教程-第一课 - onicewalk的专栏 - 博客频道 - CSDN.NET

mikel阅读(752)

来源: [转载]Android OpenGL教程-第一课 – onicewalk的专栏 – 博客频道 – CSDN.NET

Android OpenGL 教程

第一课

快速的开始一个Android OpenGL项目

首先,读懂我们的教程,需要有Android的初步基础,我们这里只是通过android提供的SDK,来进行OpenGL的学习,所以你必须先学习如何建立一个android的项目,同时了解activity的生命周期和android下的屏幕或键盘响应机制。

好的,开始建立一个android的项目Lesson1,Activity的名字的名字我们叫Lesson

 

package com.xinli;

 

import android.app.Activity;

import android.os.Bundle;

import android.view.Window;

import android.view.WindowManager;

 

public class Lesson extends Activity {

private OpenGLView mOpenGLView;

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

//去标题栏

requestWindowFeature(Window.FEATURE_NO_TITLE);

//设置全屏

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,

WindowManager.LayoutParams.FLAG_FULLSCREEN);

 

mOpenGLView = new OpenGLView(this);

setContentView(mOpenGLView);

}

}

 

我们在这里用了一个OpenGLView类,把这个类直接设为contentView。

package com.xinli;

 

import android.content.Context;

import android.opengl.GLSurfaceView;

 

public class OpenGLView extends GLSurfaceView {

private OpenGLRenderer mRenderer;

 

public OpenGLView(Context context) {

super(context);

mRenderer = new OpenGLRenderer();

setRenderer(mRenderer);

// TODO Auto-generated constructor stub

}

 

}

OpenGLView类继承自GLSurfaceView,这个类是android提供的用opengl画图的类。这里用了OpenGLRenderer类,Render是渲染的意思,真正画图的操作在这个类里面。

package com.xinli;

 

import javax.microedition.khronos.egl.EGLConfig;

import javax.microedition.khronos.opengles.GL10;

 

import android.opengl.GLSurfaceView.Renderer;

 

public class OpenGLRenderer implements Renderer {

 

@Override

public void onDrawFrame(GL10 gl) {

gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);

gl.glLoadIdentity();

}

 

@Override

public void onSurfaceChanged(GL10 gl, int width, int height) {

// 设置输出屏幕大小

gl.glViewport(0, 0, width, height);

}

 

@Override

public void onSurfaceCreated(GL10 gl, EGLConfig config) {

// TODO Auto-generated method stub

gl.glShadeModel(GL10.GL_SMOOTH);

gl.glClearColor(0f, 0f, 0f, 0f);

gl.glClearDepthf(1.0f);

gl.glEnable(GL10.GL_DEPTH_TEST);

gl.glDepthFunc(GL10.GL_LEQUAL);

gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);

 

}

 

}

这个类实现了GLSurfaceView.Renderer接口,实现这个接口,需要实现3个方法:OnSurfaceCreated(),OnSurfaceChanged(),OnDrawFrame()。第一个方法在Surface创建的时候调用,我们一般在这里做一个初始化openggl的操作,第二个方法在Surface发生改变的时候调用,例如从竖屏切换到横屏的时候;第三个方法是画图方法,类似于View类的OnDraw(),一般所有的画图操作都在这里实现。

注:现在我们大多是在JAVA层操作,后来学习深入后,这里画图的一些操作大多需要很多的计算,我们可以用NDK在C++层进行。

我们看OnSurfaceCreate里面的代码,这里都是在做对OpengGL的初始化。

 

gl.glShadeModel(GL10.GL_SMOOTH);

启用smooth shading(阴影平滑)。阴影平滑通过多边形精细的混合色彩,并对外部光进行平滑,在以后的课程中会看到他的效果。

 

gl.glClearColor(0f, 0f, 0f, 0f);

设置清除屏幕时所用的颜色。如果您对色彩的工作原理不清楚的话,我快速解释一下。色彩值的范围从0.0f到1.0f。0.0f代表最黑的情况,1.0f就是最亮的情况。glClearColor 后的第一个参数是Red Intensity(红色分量),第二个是绿色,第三个是蓝色。最大值也是1.0f,代表特定颜色分量的最亮情况。最后一个参数是Alpha值。当它用来清除屏幕的时候,我们不用关心第四个数字。现在让它为0.0f。我会用另一个教程来解释这个参数。

通过混合三种原色(红、绿、蓝),您可以得到不同的色彩。希望您在学校里学过这些。因此,当您使用glClearColor(0.0f,0.0f,1.0f,0.0f),您将用亮蓝色来清除屏幕。如果您用 glClearColor(0.5f,0.0f,0.0f,0.0f)的话,您将使用中红色来清除屏幕。不是最亮(1.0f),也不是最暗 (0.0f)。要得到白色背景,您应该将所有的颜色设成最亮(1.0f)。要黑色背景的话,您该将所有的颜色设为最暗(0.0f)。我们在这里设置屏幕为黑色。

 

gl.glClearDepthf(1.0f);

gl.glEnable(GL10.GL_DEPTH_TEST);

gl.glDepthFunc(GL10.GL_LEQUAL);

这三行是关于depth buffer(深度缓存)的。将深度缓存设想为屏幕后面的层。深度缓存不断的对物体进入屏幕内部有多深进行跟踪。我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。它的排序决定那个物体先画。这样您就不会将一个圆形后面的正方形画到圆形上来。深度缓存是OpenGL十分重要的部分。

gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);

这里告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。

 

在OnDrawFrame

gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);

清除屏幕和深度缓存。

gl.glLoadIdentity();

重置当前的模型观察矩阵。

 

本课的效果只是生成了一个黑色的屏幕,很多代码并没有发生应有的效果,后面的课程中,我们会使用到这些代码,到时候大家就能看到这些代码所产生的效果。

 

 

附:

  1. 我们来改变背景的颜色

将gl.glClearColor(1.0f, 0.0f, 0.0f, 0.0f);从SurfaceCreate放到OnDrawFrame里面,这里设置为红色了,运行下看看,是不是背景成红色了。

  1. 加上点击屏幕来改变背景的颜色

第一步,在render里面加3个float

private float cr,cg,cb;

第二步,加个设置r,g,b的函数

public void setColor(float r,float g,float b){

cr = r;

cg = g;

cb = b;

}

第三步在glclearColor里面用cr,cg,cb来设置颜色。

gl.glClearColor(cr, cg, cb, 0.0f);

第四步,在view里面override onTouchEvent函数,在这里响应点击屏幕的事件。

@Override

public boolean onTouchEvent(final MotionEvent event) {

queueEvent(new Runnable(){

 

@Override

public void run() {

mRenderer.setColor(event.getX()/getWidth(), event.getY()/getHeight(), 1.0f);

}

 

});

 

return super.onTouchEvent(event);

}

 

至于这里为什么用final,大家去网上查查,刚好也复习一下内部类调用参数,需要final这个知识点。

版权声明:本文为博主原创文章,未经博主允许不得转载。

OpenGL读取Obj模型文件

mikel阅读(1322)

昨天,帮助别人写了一个程序,读取obj文件中的3D模型,就学习了下使用OpenGL如何读取这种文件。

Obj文件格式

想要顺利读取obj模型文件,先要了解这种文件的格式,OBJ文件格式是非常简单的。这种文件以纯文本的形式存储了模型的顶点、法线和纹理坐标和材质使用信息。OBJ文件的每一行,都有极其相似的格式。在OBJ文件中,每行的格式如下:

前缀 参数1 参数2 参数3 ...

其中,前缀标识了这一行所存储的信息类型。参数则是具体的数据。OBJ文件常见的的前缀有

  • v 表示本行指定一个顶点。 前缀后跟着3个单精度浮点数,分别表示该定点的X、Y、Z坐标值
  • vt 表示本行指定一个纹理坐标。此前缀后跟着两个单精度浮点数。分别表示此纹理坐标的U、V值
  • vn 表示本行指定一个法线向量。此前缀后跟着3个单精度浮点数,分别表示该法向量的X、Y、Z坐标值
  • f 表示本行指定一个表面(Face)。一个表面实际上就是一个三角形图元。此前缀行的参数格式后面将详细介绍。
  • usemtl 此前缀后只跟着一个参数。该参数指定了从此行之后到下一个以usemtl开头的行之间的所有表面所使用的材质名称。该材质可以在此OBJ文件所附属的MTL文件中找到具体信息。
  • mtllib 此前缀后只跟着一个参数。该参数指定了此OBJ文件所使用的材质库文件(*.mtl)的文件路径

现 在,我们再来看一下OBJ文件的结构。在一个OBJ文件中,首先有一些以v、vt或vn前缀开头的行指定了所有的顶点、纹理坐标、法线的坐标。然后再由一 些以f开头的行指定每一个三角形所对应的顶点、纹理坐标和法线的索引。在顶点、纹理坐标和法线的索引之间,使用符号“/”隔开的。一个f行可以以下面几种 格式出现:

  • f  1  2  3 这样的行表示以第1、2、3号顶点组成一个三角形。
  • f  1/3  2/5  3/4 这样的行表示以第1、2、3号顶点组成一个三角形,其中第一个顶点的纹理坐标的索引值为3,第二个顶点的纹理坐标的索引值为5,第三个顶点的纹理坐标的索引值为4。
  • f  1/3/4  2/5/6  3/4/2 这样的行表示以第1、2、3号顶点组成一个三角形,其中第一个顶点的纹理坐标的索引值为3,其法线的索引值是4;第二个顶点的纹理坐标的索引值为5,其法 线的索引值是6;第三个顶点的纹理坐标的索引值为6,其法线的索引值是2。
  • f  1//4  2//6  3//2这样的行表示以第1、2、3号顶点组成一个三角形,且忽略纹理坐标。其中第一个顶点的法线的索引值是4;第二个顶点的法线的索引值是6;第三个顶点的法线的索引值是2。

值得注意的是文件中的索引值是以1作为起点的,这一点与C语言中以0作为起点有很大的不同。在渲染的时候应注意将从文件中读取的坐标值减去1。

obj文件在OpenGL中的读取

我拿到的Obj文件,内容如下:

# Max2Obj Version 4.0 Mar 10th, 2001 # # object Line01 to come … # v -9.574153 -2.220963 -2.000000 v -7.893424 -2.280989 -2.000000 …省略若干相同格式的行 v -7.195892 -1.380599 -0.980160 v -9.580536 -1.320573 -1.967912 # 160 vertices vn -0.071382 -1.998675 0.014198 vn -0.035691 -0.999338 0.007099 …同样省略若干相同格式的行 vn -0.825224 1.736366 -0.551397 vn 0.039418 1.999438 0.026341 # 160 vertex normals g Line01 s 1 f 1//1 12//12 2//2 f 1//1 11//11 12//12 s 4 f 2//2 13//13 3//3 f 2//2 12//12 13//13 …同样的省略若干相同格式的行 s 4 f 160//160 1//1 151//151 f 160//160 10//10 1//1 # 320 faces g

前面带有’#’的行是注释行,这个文件中包含的前缀有:v,表示顶点;vn,表示法线;g,表示组,行 “g Line01” 和行 “g” 之前的所有行表示一个名为”Line01″的组;f,表示一个面;s,表示光滑组。

由于文件中只出现顶点和法线数据,每个面存储顶点和法线索引,所以我们要声明如下几个全局函数:

int v_num=0; //记录点的数量 
int vn_num=0;//记录法线的数量
 int f_num=0; //记录面的数量 
GLfloat **vArr; //存放点的二维数组
 GLfloat **vnArr;//存放法线的二维数组 
int **fvArr; //存放面顶点的二维数组
 int **fnArr; //存放面法线的二维数组
string s1; GLfloat f2,f3,f4;

为了给存放顶点法线等二维数组分配存储空间,需要知道顶点和法线等的数量,使用下面的函数计算点、法线、面的数量:

int readfile(string addrstr) //将文件内容读到数组中去 

vArr=new GLfloat*[v_num];

for (int i=0;i<v_num;i++) 

{  

vArr[i]=new GLfloat[3]; 

vnArr=new GLfloat*[vn_num];

for (i=0;i<vn_num;i++) 

{ vnArr[i]=new GLfloat[3]; } 

fvArr=new int*[f_num];

fnArr=new int*[f_num]; 

for (i=0;i<f_num;i++)

{  

fvArr[i]=new int[3]; 

fnArr[i]=new int[3];

ifstream infile(addrstr.c_str()); 

string sline;//每一行

int ii=0,jj=0,kk=0;  

while(getline(infile,sline)) 

{ if(sline[0]==’v’) 

{ if(sline[1]==’n’)//vn 

{ istringstream sin(sline);  

sin>>s1>>f2>>f3>>f4;  

vnArr[ii][0]=f2;  

vnArr[ii][1]=f3;  

vnArr[ii][2]=f4; ii++; 

}  

else//v 

{  

istringstream sin(sline);

sin>>s1>>f2>>f3>>f4;  

vArr[jj][0]=f2;  

vArr[jj][1]=f3;  

vArr[jj][2]=f4; jj++;  

if (sline[0]==’f’) //读取面

{ istringstream in(sline);  

GLfloat a;  

in>>s1;//去掉前缀f  

int i,k;  

for(i=0;i<3;i++)  

{ in>>s1; cout<<s1<<endl; //取得顶点索引和法线索引  

a=0; for(k=0;s1[k]!=’/’;k++)  

{ a=a*10+(s1[k]-48); }  

fvArr[kk][i]=a; a=0;  

for(k=k+2;s1[k];k++)  

{ a=a*10+(s1[k]-48);;  

} fnArr[kk][i]=a;  

} kk++; 

} return 0; 

}

然后在绘制之前,初始化时,调用这两个函数读取模型即可:

getLineNum("wan.obj"); readfile("wan.obj");

相应的绘制代码:

for (int i=0;i<f_num;i++) 

{ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 

glBegin(GL_TRIANGLES); 

glNormal3f(vnArr[fnArr[i][0]-1][0], vnArr[fnArr[i][0]-1][1],  vnArr[fnArr[i][0]-1][2]);

glVertex3f(vArr[fvArr[i][0]-1][0], vArr[fvArr[i][0]-1][1],  vArr[fvArr[i][0]-1][2]);  

glNormal3f(vnArr[fnArr[i][1]-1][0], vnArr[fnArr[i][1]-1][1],  vnArr[fnArr[i][1]-1][2]); 

glVertex3f(vArr[fvArr[i][1]-1][0], vArr[fvArr[i][1]-1][1],  vArr[fvArr[i][1]-1][2]); 

glNormal3f(vnArr[fnArr[i][2]-1][0], vnArr[fnArr[i][2]-1][1],  vnArr[fnArr[i][2]-1][2]); 

glVertex3f(vArr[fvArr[i][2]-1][0], vArr[fvArr[i][2]-1][1],  vArr[fvArr[i][2]-1][2]);  

glEnd();

}

这样就完成了绘制,上面的代码仅仅针对我的wan.obj这个文件,对于想读取其他的obj文件,相应的分配一个存储空间,读取相应的数据,然后在绘制时使用这些数据就行了。

******************************

微信支付开发(APP)的各种坑,.net和iOS的各种陷阱,解决.net调用下单接口提示无权限,解决iOS跳转到微信支付页面中间只有一个确定按钮

mikel阅读(3875)

直入主题之前,请容我吐槽一下微*的官方东西:ASDFQ%#$%$#$%^FG@#$%DSFQ#$%…….;吐槽玩了!大家心照就好。

要完成手机APP跳转到微信的APP进行微信支付,需要进行如下操作:

1、先去微信的开放平台(http://open.weixin.qq.com)进行开发者账号的注册。

2、新建一个APP应用,然后填写必填信息提交审核。

3、进入APP应用,在接口信息中,进行申请“获得微信支付能力”的功能,期间会提交相关的公司营业信息证明等。

通过以上三点,然后经过漫长的等待之后,基本都可以申请下来的,这里不细说申请细节了,讲个大概,基本都能应付。

基本的对接流程在这里有细说(https://pay.weixin.qq.com/wiki/doc/api/app.php

在进行服务端对接之前,需要用到如下信息:

/* 微信app key 这两个就是我们上面申请的APP得到的*/
W_APP_ID
W_APP_SECRET
//商户号
W_MCH_ID–这个也是需要登录商户平台进行获得
//API密钥,在商户平台设置
W_API_KEY–这个需要登录商户平台进行获得

商户平台的登录地址(https://pay.weixin.qq.com/index.php

一、.net服务端的对接:

1、直接上微信的SDK列表下载.net的SDK(https://pay.weixin.qq.com/wiki/doc/api/download/WxPayAPI_CS_v3.zip

2、要先知道一点,下载回来的SDK是没有具体包含“统一下单API”的,只包含了这个“刷卡支付、微信内网页支付、扫码支付”

3、下载回来的SDK中,第一次运行是运行不了的,需要对代码进行设置

1)对上面四个参数进行配置,具体在lib\Config.cs文件中

2)屏蔽掉lib\HttpService.cs的代理访问:(这东西基本可以不用使用到)

 

4、通过以上的几步配置,基本可以运行了

5、还有一个点,我们点击Default.aspx页面上的按钮,链接过去的地址要注意一下,其实不是我们的测试工程,而是paysdk.weixin.qq.com的,这个需要改成是我们的测试功能才行(这个是比较吭的)。

6、如何对接“统一下单API”,最基本的做法就是打开business\JsApiPay.cs文件,然后拷贝GetUnifiedOrderResult方法直接用,但是如果拷贝这个方法用的时候,会提示“无权限调用”。其实最根本的错误在于我们传递的trade_type为JSAPI导致的,所以拷贝过来使用的时候,需要做如下的更新:(为什么要这样了,原因很简单,这个方法的用途本来就是给JSAPI的,我们是要使用trade_type为APP进行调用,这些参数当然要变拉)

只要这些字段即可,其余的可有可无,都不影响,到了这点,你运行“统一下单”功能,就会返回prepay_id了,也就是支付ID。

至此,通过这些设置,基本都可能成功。

二、iOS的对接,最常用的致命弱点就是,我把官方的请求地址换成了自己之后,点击支付测试,跳转到微信支付的页面时,中间就只出现了一个“确定”按钮,点击之后返回到原APP,提示“支付失败,xxx -2”这样的提示。

1、如果你有这些提示了,我可以很肯定的告诉你,你的sign错了,而且是用错了,你不应该直接用服务端返回的sign,这个根本不是一回事。

2、切记,客户端的sign字段,是要自己生成的。

3、还要切记,服务端返回的那一串json,最有用而且最优价值的就唯一一个prepay_id,其余的基本不需要。

4、官方的iOS SDK中,包含了两个方法(sendpay_demo、sendpay),这也是最容易错的,我们在对接的时候,选择sendpay的方法,也就是这个:

这个方法的东西其实是不完整,最明显,我们把网络请求改了之后,再把所用到的参数赋值,然后就没有然后了,根本调不起来,压根就没反应,原因是缺少了package参数值,这个是固定的(Sign=WXPay)

5、改了第四点之后,肯定是能调用起来的了,但是确不能支付,sign错误导致的,至此,你应该把注意力集中到sendpay_demo的方法中去,不要再使用sendpay方法了

只需要把刚才获取的prepay_id替换掉,立马就能正常使用支付功能,如下位置的参数替换成我们网络请求返回的的就行了:

总结,希望此篇文章对正在做微信支付对接的哥们有帮助,也让大家少走一些弯路,如果觉得合适,可以顺手点个赞,以此鼓励鼓励。thanks
本文链接:微信支付开发(APP)的各种坑,.net和iOS的各种陷阱,解决.net调用下单接口提示无权限,解决iOS跳转到微信支付页面中间只有一个确定按钮,转载请注明。

[转载]大分享-hibernate,springmvc,easyui简要介绍 - Rick-Bao - 博客园

mikel阅读(1052)

来源: [转载]大分享-hibernate,springmvc,easyui简要介绍 – Rick-Bao – 博客园

  近期公司一直在做项目,主要用到了springMVC,eseayui,hibernate几大框架。近一个月的时间,个人就目前自我知识给予分享。

很多公司使用mybatis产品,综合所述其最大优点是全SQL操作,灵活方便。

个人认为hibernate框架其优势比较突出。其一是因为它作为纯ORM产品,使用注解或是配置文件做好与数据表映射之后,操作实体就相当于操作 数据表。而对于普通业务,多数是基于增删改操作的,较为复杂的可能就是查询操作了。实体映射关系太过于复杂容易引发牵一动百的效果,而业务逻辑中必须要这 么做。那么,SQL就是最好的选择了。

hibernate 中提供了org.hibernate.Query 接口。 由 org.springframework.orm.hibernate3.support.HibernateDaoSupport.getSession() 方法返回的Sesson创建一个

org.hibernate.Session.createSQLQuery(String arg0) throws HibernateException 方法,该方法完全以SQL方式操作数据库表,抛开了实体bean的映射。
完整代码如下:
复制代码
//查找ID
public String findTypeIdByTitle(String title) throws Exception {
        String sql = "select pk_id from decoration_type_info where title =:title";
        Query query = this.getSession().createSQLQuery(sql);
        query.setString("title", title);
        String pkId = (String) query.uniqueResult();
        return pkId;
    }
复制代码
复制代码
//更新方法
public void updateTypeById(DecorationTypeInfo typeInfo) throws Exception{
        String sql = "update decoration_type_info set title =:title,REMARK =:remark,UPDATE_BY =:updateBy,"
                + " UPDATE_DATE = to_timestamp(to_char(sysdate,'yyyy-mm-dd hh24:mi:ss'), 'yyyy-mm-dd hh24:mi:ss:ff') "
                + " where pk_id =:pkId";
        Query query = this.getSession().createSQLQuery(sql);
        query.setString("title", typeInfo.getTitle());
        query.setString("remark", typeInfo.getRemark());
        query.setString("updateBy", typeInfo.getUpdateBy());
        query.setString("pkId", typeInfo.getPkId());
        query.executeUpdate();
    }
复制代码
复制代码
//删除方法
public void delTypeById(String pkId)throws Exception {
        String sql = " delete from unit_decoration_type_info where fk_decoration_type_id=?";
        Query query = this.getSession().createSQLQuery(sql);
        query.setParameter(0, pkId);
        query.executeUpdate();
    }
复制代码
复制代码
//新增方法
public void addType(DecorationTypeInfo typeInfo,String themeId)throws Exception{
        if (StringUtils.isBlank(typeInfo.getPkId())) {
            String id = GeneratePKID.getPKID();
            typeInfo.setPkId(id);
        }

        String sql = "insert into decoration_type_info (PK_ID,TITLE,PRICE,REMARK,FK_THEME_ID,CREATE_BY,CREATE_DATE,UPDATE_BY,UPDATE_DATE) "
                + " values (?,?,?,?,?,?,to_timestamp(to_char(sysdate,'yyyy-mm-dd hh24:mi:ss'), 'yyyy-mm-dd hh24:mi:ss:ff'),"
                + " ?,NULL)";
        Query query = this.getSession().createSQLQuery(sql);
        query.setParameter(0, typeInfo.getPkId());
        query.setParameter(1, typeInfo.getTitle());
        query.setParameter(2, typeInfo.getPrice());
        query.setParameter(3, typeInfo.getRemark());
        query.setParameter(4, themeId);
        query.setParameter(5, typeInfo.getCreateBy());
        query.setParameter(6, typeInfo.getUpdateBy());
        
        query.executeUpdate();
    }
复制代码
复制代码
//所有导入的Hibernate包

import org.apache.commons.lang.StringUtils;
import org.hibernate.Query;
import org.hibernate.transform.Transformers;
import org.springframework.stereotype.Repository;
复制代码

注明:以上只作为个人学习观点,不涉及任何商业或法律。

再来说说springMVC。

个人目前理解为 :

controller中的@Controller()和@RequestMapping(“/xx/xx”)注解作为action请求的唯一标志,不容许一个项目中有重复出现。

  service中的@Service(“xxx1”)注解作为全局唯一的service标识。

  dao中的xxxImpl类中@Repository(“xx1”)注解作为全局某个DAO的唯一标识。

在service服务层注入引用DAO需要@Autowired和@Qualifier(“xx1”)配合使用,这样便于更准确的找到引用的DAO。

controller引用service同样使用@Autowired和@Qualifier(“xxx1”)作为查找的条件 。

其优势总结为:AOP切面编程可以随意中任何一个类中引用其它包的资源,只要加上@Autowired自动装配注解和@Qualifier(“”)筛选注解即可达到一次编写,到处使用的目的。

    这样一来,我们只要编写一个通用业务处理的方法,那么中项目的任何地方都可以调用。很大程度上减轻了工作量和代码冗余量 。

复制代码
//Controller段代码

@Controller()
@RequestMapping("/xx/xxx")
public class TypeController {

    @Autowired
    @Qualifier("typeservice")
    private TypeService typeService;
    
    private static Log log = LogFactory.getLog(TypeController.class);

    @RequestMapping("/list")
    public ModelAndView bookCycleInfoList(HttpServletRequest request,
            HttpServletResponse response) {
        ModelAndView view = new ModelAndView();
        try {
            view.setViewName("/bookCycleInfo/bookCycleInfo_list");
        } catch (Exception e) {
            log.error("TypeController.list : " + e.getMessage());
        }
        return view;
    }
}
复制代码
复制代码
//Service代码

@Service("typeservice")
public class TypeService {

    @Autowired
    @Qualifier("typedao")
    private TypeDao type;

    public List<TreeList> queryRoomTree(String estateId) throws Exception{
        try {
            return bookCycleInfoDao.queryRoomTree(estateId);
        } catch (Exception e) {
            throw e;
        }
    }
复制代码
复制代码
//DAO中只有接口定义,本代码不做演示

@Repository("typedao")
public class TypeDaoImpl  {

    
    @Override
    public List findTypeList() throws Exception{

        try {
                        //代码体
            return null;
        } catch (Exception e) {
            throw e;
        }
    }
}
复制代码

注明:作为初学者,该共享知识仅作为参考,不能保证完全准确性 。

下面说说easyUI框架 。首先,easyUI框架是完全基于JS的,个人认为它是JQuery和ajax的综合体 。

其中包含很多界面控件和JS代码控制。常用的控件有datagrid,treegrid,Dialog,Menu,TextBox,NumberBox,DateTimeBox等等,ajax也非常简单,如下代码所示

复制代码
<script type="text/javascript">
$(function() {
            var ids ="${editId}";
            $.ajax({
                        type : "post",
                        async : false,
                        url : "${pageContext.request.contextPath}/xx/xxx/findTypeById.action",
                        data : {
                            pkId : ids
                        },
                        dataType : "json",
                        success : function(obj) {
                            $("#pkId").textbox('setValue', obj[0].PKID);
                            $("#title").textbox('setValue', obj[0].TITLE);
                            $("#content").textbox('setValue', obj[0].CONTENT);
                        }
                    });
        });
</script>
复制代码

更多easyUI知识,请看官网 http://www.jeasyui.com/ 和中文网 http://www.jeasyui.net/ 。

 

 

个人感触:

个人虽然已经有一年多的开发经验,但做的项目和写的代码为之甚少。进新公司一个月了快,虽然累,但学到了很多可用实用的东西。

所以非常开心,工作之余分享下自己的成果,既是对自我的检验,也是对雏步难行阶段学者的帮助 。

 

在编程中,所有的行动都是为了数据的存取二展开的!一条数据的生命周期随着程序的开始和结束在不断运行着,举例说明:程序是轨道,数据时列车,数据库是车站,旅客是数据源。梦想着如何改变程序的生命琐事,让它变得简洁而又实用,没有那么多繁杂的文件,这才是真正的追求 !

[原创]ECmobile的应用首页显示商品数量修改

mikel阅读(1914)

项目发现ECmobile的应用首页的特价区只显示1个商品和分类区只显示2个商品,需要按要求显示

于是查找了接口文件/Controller/Home/Data.php

发现代码   function gz_get_promote_goods($cats = ”)中的如下代码

/* 取得促销lbi的数量限制 */
$num = get_library_number(“recommend_promotion”);

那么recommend_promotion在哪设置的?

于是又去找函数位置get_library_number()在哪

发现在\includes\lib_main.php中

代码如下:


/**
* 取得某模板某库设置的数量
* @param   string      $template   模板名,如index
* @param   string      $library    库名,如recommend_best
* @param   int         $def_num    默认数量:如果没有设置模板,显示的数量
* @return  int         数量
*/
function get_library_number($library, $template = null)
{
global $page_libs;

if (empty($template))
{
$template = basename(PHP_SELF);
$template = substr($template, 0, strrpos($template, '.'));
}
$template = addslashes($template);

static $lib_list = array();

/* 如果没有该模板的信息,取得该模板的信息 */
if (!isset($lib_list[$template]))
{
$lib_list[$template] = array();
$sql = "SELECT library, number FROM " . $GLOBALS['ecs']->table('template') .
" WHERE theme = '" . $GLOBALS['_CFG']['template'] . "'" .
" AND filename = '$template' AND remarks='' ";
$res = $GLOBALS['db']->query($sql);
while ($row = $GLOBALS['db']->fetchRow($res))
{
$lib = basename(strtolower(substr($row['library'], 0, strpos($row['library'], '.'))));
$lib_list[$template][$lib] = $row['number'];
}
}

$num = 0;
if (isset($lib_list[$template][$library]))
{
$num = intval($lib_list[$template][$library]);
}
else
{
/* 模板设置文件查找默认值 */
include_once(ROOT_PATH . ADMIN_PATH . '/includes/lib_template.php');
static $static_page_libs = null;
if ($static_page_libs == null)
{
$static_page_libs = $page_libs;
}
$lib = '/library/' . $library . '.lbi';

$num = isset($static_page_libs[$template][$lib]) ? $static_page_libs[$template][$lib] :  3;
}

return $num;
}

发现,它是在ecs_template中设置的,那么就简单了,就到数据库查询设置的显示数量,修改了即可了,开源没有文档真的很坑爹,

p

Android图形动画

mikel阅读(1030)

本质

每帧绘制不同的内容。

 

基本过程

开始动画后,调用View的invalidate触发重绘。重绘后检查动画是否停止,若未停止则继续调用invalidate触发下一帧(下一次重绘),直到动画结束。

重绘时View的draw方法会被调用,根据动画的进行绘制不同的内容,如某个被绘制元素的大小变化、角度旋转、透明度变化等,这样即会产生动画。

动画的推进过程一般都会有一个变化量,这个变量会被用到draw方法内元素的绘制。一般的变量都是时间,也可以是手指移动、传感器等任何其他的变量。

 

Android中的动画支持

Animation:早期实现的让View整体做动画的类。能让View做Matrix(移动、缩放、旋转、3D旋转)和Alpha(透明)的动画。

Animator:有硬件加速后为做动画实现的类。能方便的让View整体做动画;也可以只产生随时间变化的变量,用来在onDraw里做绘图级的动画。比Animation灵活很多。

AnimationDrawable:图片逐帧动画。主要用来播放提前制作好的动画。

 

在哪个级别做动画

让整个View做动画(比如整个View平移、旋转等)很简单方便,一般调用几行代码就行。我把它称作View级的动画。

在View的draw/onDraw里通过Canvas来绘制时做动画更灵活,更精细,能力更强大。我把它称作绘图级的动画。(View级的动画本质上也是这么做的,只是Android系统帮我们做了大部分工作)

 

绘图级的动画

这篇文章主要讲绘图级的动画。

下面来一段绘图级动画的典型实现:

<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java keyword">class</code> <code class="java plain">MyView </code><code class="java keyword">extends</code> <code class="java plain">View {</code></div>
<div class="line number2 index1 alt1"><code class="java spaces">    </code><code class="java keyword">void</code> <code class="java plain">startAnimator() {</code></div>
<div class="line number3 index2 alt2"><code class="java spaces">        </code><code class="java plain">ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);</code></div>
<div class="line number4 index3 alt1"><code class="java spaces">        </code><code class="java plain">animator.start();</code></div>
<div class="line number5 index4 alt2"><code class="java spaces">        </code><code class="java plain">invalidate();</code></div>
<div class="line number6 index5 alt1"><code class="java spaces">    </code><code class="java plain">}</code></div>
<div class="line number7 index6 alt2"><code class="java spaces">    </code><code class="java keyword">protected</code> <code class="java keyword">void</code> <code class="java plain">onDraw(Canvas canvas) {</code></div>
<div class="line number8 index7 alt1"><code class="java spaces">        </code><code class="java keyword">if</code> <code class="java plain">(animator.isRunning()) {</code></div>
<div class="line number9 index8 alt2"><code class="java spaces">            </code><code class="java keyword">float</code> <code class="java plain">ratio = (Float)animator.getAnimatedValue();</code></div>
<div class="line number10 index9 alt1"><code class="java spaces">            </code><code class="java plain">canvas.rotate(ratio*</code><code class="java value">360</code><code class="java plain">);</code></div>
<div class="line number11 index10 alt2"><code class="java spaces">            </code><code class="java plain">canvas.drawBitmap(bitmap, </code><code class="java value">0</code><code class="java plain">, </code><code class="java value">0</code><code class="java plain">, </code><code class="java keyword">null</code><code class="java plain">);</code></div>
<div class="line number12 index11 alt1"></div>
<div class="line number13 index12 alt2"><code class="java spaces">            </code><code class="java plain">invalidate();</code></div>
<div class="line number14 index13 alt1"><code class="java spaces">        </code><code class="java plain">}</code></div>
<div class="line number15 index14 alt2"><code class="java spaces">        </code><code class="java plain">...</code></div>
<div class="line number16 index15 alt1"><code class="java spaces">    </code><code class="java plain">}</code></div>
<div class="line number17 index16 alt2"><code class="java plain">}</code></div>
<div class="line number17 index16 alt2">

有了不断变化的ratio变量,绘图级动画就可以大展身手了。

绘图级动画的强大能力来自绘图API的强大能力,下面主要讲绘图API。

 

二、绘图API

Matrix

Canvas.[translate,scale,rotate,skew]方法

Matrix.set/pre/post[translate,scale,rotate,skew]方法

平移、缩放、旋转、斜切。

从使用API的角度来看,我们通过调用Canvas.translate等方法,可以使后续在此Canvas上绘制操作的绘制区域变化,如translate(5,0),则后续所有绘制操作的绘制区域都会向右移动5个像素。

原理:Canvas里有一个Matrix,Canvas上的这几个调用都会最终调用到Matrix.pre*。这个Matrix保存整个变换过程。 当有Canvas.draw时,要绘制的点都会经过Matrix.mapPoints方法做一个映射。于是产生我们期望的变换效果。(事实上映射的时候只 需要映射关键点,其他的是插值来的)

关于Matrix的更多信息

set/pre/post的区别:set是设置,冲掉以前的数据。pre是前乘,post是后乘,根本上讲就是生效顺序不同。具体表现效果可在网上搜索资料。

setPolyToPoly:与mapPoints方法相反,mapPoints是通过矩阵把原始点映射为目标点。setPolyToPoly是输入原始点和映射后的目标点,计算出这个矩阵。

Camera:有透视效果的3D旋转。Camera是一个生成Matrix的工具类。可用来生成有透视效果的3D旋转。

 

Canvas.draw*方法

Canvas.draw-Point/s

Canvas.draw-Line/s

Canvas.draw-Rect,RoundRect,Circle,Oval,Arc,Path

Canvas.draw-Text

Canvas.draw-Bitmap,BitmapMesh

Canvas.draw-Color,Paint

这些方法都表示绘制一个区域。绘制的区域中究竟填充什么颜色,由Paint决定。

Color,Paint,Bitmap,BitmapMesh这几个则除了指定绘制区域外,还指定了填充内容。

Path功能比较强大,可自行组织成任何形状,还可以用贝塞尔曲线。

 

这些方法基本上都很好理解,从名字上即可看出其功能。这里重点提一下drawBitmapMesh。

drawBitmapMesh是输入一个网格模型,绘制出的图片内容将依据这个网格来扭曲。可以想像成把图片画在一块有弹性的布上,当我们把布的某些区域扯动的时候,会形成画面扭曲效果。

示例:假设有个30×30大小的图片,我们建立这样的网格输入:

0,0, 15,0, 30,0,

0,15, 15,15, 30,15,

0,30, 15,30, 30,30

则图片会原样输出,没有任何扭曲。

如果我们建立这样的网格输入:

0,0, 15,12, 30,0,

0,15, 15,15, 30,15,

0,30, 15,30, 30,30

则原本[15,0]的点会被绘制到[15,12]的位置上去。图片绘制出来后,上面部分会缺一块,形成用手把图片从上边中间位置往下拉的扭曲效果。但很锐利,上面缺的一块是个三角形而不会是半圆型,通常我们希望的是半圆型,这就需要我们把这个网格建得密一些。

 

Alpha通道

每个Color里可以有四个通道ARGB,其中RGB是红绿蓝,A即Alpha通道,它通常的作用是用来作为此颜色的透明度。

因为我们的显示屏是没法透明的,因此最终显示在屏幕上的颜色里可以认为没有Alpha通道。Alpha通道主要在两个图像混合的时候生效。

默认情况下,当一个颜色绘制到Canvas上时的混合模式是这样计算的:(RGB通道) 最终颜色 = 绘制的颜色 + (1 – 绘制颜色的透明度) × Canvas上的原有颜色。

注意:

1.这里我们一般把每个通道的取值从0到255映射到0到1的浮点数表示。

2.这里等式右边的“绘制的颜色”、“Canvas上的原有颜色”都是经过预乘了自己的Alpha通道的值。如绘制颜色:0x88ffffff,那么参与运算时的每个颜色通道的值不是1.0,而是(1.0 * 0.53125 = 0.53125)。

使用这种方式的混合,就会造成后绘制的内容以半透明的方式叠在上面的视觉效果。

其实还可以有不同的混合模式供我们选择,用Paint.setXfermode,指定不同的PorterDuff.Mode

下表是各个PorterDuff模式的混合计算公式:(D指原本在Canvas上的内容dst,S指绘制输入的内容src,a指alpha通道,c指RGB各个通道)

ADD Saturate(S + D)
CLEAR [0, 0]
DARKEN [Sa + Da – Sa*Da, Sc*(1 – Da) + Dc*(1 – Sa) + min(Sc, Dc)]
DST [Da, Dc]
DST_ATOP [Sa, Sa * Dc + Sc * (1 – Da)]
DST_IN [Sa * Da, Sa * Dc]
DST_OUT [Da * (1 – Sa), Dc * (1 – Sa)]
DST_OVER [Sa + (1 – Sa)*Da, Rc = Dc + (1 – Da)*Sc]
LIGHTEN [Sa + Da – Sa*Da, Sc*(1 – Da) + Dc*(1 – Sa) + max(Sc, Dc)]
MULTIPLY [Sa * Da, Sc * Dc]
SCREEN [Sa + Da – Sa * Da, Sc + Dc – Sc * Dc]
SRC [Sa, Sc]
SRC_ATOP [Da, Sc * Da + (1 – Sa) * Dc]
SRC_IN [Sa * Da, Sc * Da]
SRC_OUT [Sa * (1 – Da), Sc * (1 – Da)]
SRC_OVER [Sa + (1 – Sa)*Da, Rc = Sc + (1 – Sa)*Dc]
XOR [Sa + Da – 2 * Sa * Da, Sc * (1 – Da) + (1 – Sa) * Dc]

可以发现,我们之前的默认混合模式其实就是SRC_OVER。

通过选择其他的PorterDuff模式,我们可以达到一些特殊的效果:

使用DST_OVER的话,相当于后绘制的内容作为背景在底下。

使用DST_IN/DST_OUT的话,可以裁剪Canvas里的内容,或用一张带alpha的图片mask指定哪些区域显示/不显示。

通过选择SRC_ATOP可以只在Canvas上有内容(不透明)的地方绘制。

用一张示例图来查看使用不同模式时的混合效果(src表示输入的图,dst表示原Canvas上的内容):

 

填充颜色

之前说过Canvas.draw*指定了绘制的区域。而区域里的填充颜色是由Paint来指定的。

Paint.setColor指定纯色。

Paint.setShader可指定:BitmapShaderLinearGradientRadialGradientSweepGradientComposeShader

BitmapShader:图片填充。

LinearGradient, RadialGradient, SweepGradient:渐变填充。

ComposeShader:叠加前面的某两种。可选择PorterDuff混合模式。

如果既调用了setColor,又调用了setShader,则setShader生效。如果同时用setColor或setAlpha设置了透明度,则透明度也会生效。(会和Shader的透明度叠加)

如果使用drawBitmap输入一个只有alpha的图片(可用Bitmap.extractAlpha方法获得),则会以alpha图片为mask,绘制出shader/color的颜色。

 

ColorFilter

通过ColorFilter可以对一次绘制的所有像素做一个通用处理。

Paint.setColorFilter: LightingColorFilterPorterDuffColorFilterColorMatrixColorFilter

这可以整体上改变这一次draw的内容,比如让颜色更暗、更亮等。

这里重点介绍下ColorMatrixColorFilter。

ColorMatrix是4×5矩阵,定义其每个元素如下:

{ a, b, c, d, e,

f, g, h, i, j,

k, l, m, n, o,

p, q, r, s, t }

则ColorMatrix的最终运算方式如下:

R’ = a*R + b*G + c*B + d*A + e;
G’ = f*R + g*G + h*B + i*A + j;
B’ = k*R + l*G + m*B + n*A + o;
A’ = p*R + q*G + r*B + s*A + t;

可在这里方便的试验flash在线版。

 

绘图API架构

整个绘制流水线大概如下:(我们能定义的部分用蓝色表示)

考虑动画实现的时候一般从两个角度来思考:

宏观角度:有几个变化量,分别是什么。动画从开始到结束的流程。

微观角度:从某一帧上去想,在变化量为某个数值时的图像,该怎么绘制。

把这两者分开去想,就会比较清晰。

 

PPT里有示例,可以参照DEMO来熟悉:

怎么做动画.ppt

Android图形系统简介.ppt

GraphicsDemo.apk

GraphicsDemo_src.zip