Android高级进阶十一 Android OpenGL建立3D空间
2012-02-01 13:01
225 查看
最新版本:Android高级进阶十一
Android OpenGL建立3D空间
这一篇文章继续写在Android上使用OpenGL,前阶段的文章一直是旨在建立一个3D的形状,这一篇文章我们就来建立一个3D的空间模型。
老规矩先上效果图:
这次我们使用一个文件来存储我们所有顶点及纹理定点数据,具体使用方式待会儿见分晓。
代码如下:
WordActivity.java
Java代码
package org.ourunix.android.world;
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.KeyEvent;
public class WordActivity extends Activity {
/** Called when the activity is first created. */
GLRenderer render;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GLSurfaceView glView = new GLSurfaceView(this);
render = new GLRenderer(this);
glView.setRenderer(render);
setContentView(glView);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
return render.onKeyUp(keyCode, event);
}
}
ScData.java
Java代码
package org.ourunix.android.world;
import java.util.ArrayList;
import java.util.List;
public class ScData {
public static class VERTEX{
float x, y, z; //三角形的顶点坐标
float u, v;//纹理坐标
public VERTEX(float x, float y , float z, float u, float v){
this.x = x;
this.y = y;
this.z = z;
this.u = u;
this.v = v;
}
}
//三角形结构
public static class TRIANGLE{
VERTEX[] vertex = new VERTEX[3];
}
//区段结构
public static class SECTOR{
int numTriangles;
List<TRIANGLE> triangle = new ArrayList<TRIANGLE>();
}
}
GLRenderer.java
Java代码
package org.ourunix.android.world;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import org.ourunix.android.world.ScData.SECTOR;
import org.ourunix.android.world.ScData.TRIANGLE;
import org.ourunix.android.world.ScData.VERTEX;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;
import android.opengl.GLSurfaceView.Renderer;
import android.view.KeyEvent;
public class GLRenderer implements Renderer {
private Context mContext;
SECTOR sector1 = new SECTOR();
public final static float piover180 = 0.0174532925f;
float heading;
float xpos;
float zpos;
float yrot; // Y Rotation
float walkbias = 0;
float walkbiasangle = 0;
float lookupdown = 0.0f;
float z=0.0f;
private int[] texturesID = new int[3];
public GLRenderer(Context ctx){
mContext = ctx;
}
@Override
public void onDrawFrame(GL10 gl) {
// TODO Auto-generated method stub
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
gl.glLoadIdentity(); // Reset The View
float xtrans = -xpos;
float ztrans = -zpos;
float ytrans = -walkbias-0.25f;
float sceneroty = 360.0f - yrot;
FloatBuffer vertexPointer = Utils.BufferUtil.fBuffer(new float[9]);
FloatBuffer texCoordPointer = Utils.BufferUtil.fBuffer(new float[6]);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexPointer);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texCoordPointer);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glLoadIdentity();
gl.glRotatef(lookupdown, 1.0f, 0.0f, 0.0f);
gl.glRotatef(sceneroty, 0.0f, 1.0f, 0.0f);
gl.glTranslatef(xtrans, ytrans, ztrans);
gl.glBindTexture(GL10.GL_TEXTURE_2D, texturesID[0]);
for(TRIANGLE triangle : sector1.triangle)
{
vertexPointer.clear();
texCoordPointer.clear();
gl.glNormal3f(0.0f, 0.0f, 1.0f);
for(int i=0; i<3; i++)
{
VERTEX vt = triangle.vertex[i];
vertexPointer.put(vt.x);
vertexPointer.put(vt.y);
vertexPointer.put(vt.z);
texCoordPointer.put(vt.u);
texCoordPointer.put(vt.v);
}
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 4);
}
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// TODO Auto-generated method stub
float ratio = (float)width/height;
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// TODO Auto-generated method stub
//加载纹理
gl.glEnable(GL10.GL_TEXTURE_2D);
LoadGLTextures(gl);
//开启混色透明
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
gl.glClearColor(0, 0, 0, 0);
gl.glShadeModel(GL10.GL_SMOOTH);
//开启深度测试
gl.glClearDepthf(1.0f);
gl.glDepthFunc(GL10.GL_LESS);
gl.glEnable(GL10.GL_DEPTH_TEST);
SetupWorld();
}
private void LoadGLTextures(GL10 gl) {
int[] imgArray = {R.drawable.img};
// TODO Auto-generated method stub
for (int i = 0; i < 3; i++){
IntBuffer textureBuffer = IntBuffer.allocate(1);
gl.glGenTextures(1, textureBuffer);
texturesID[i] = textureBuffer.get();
gl.glBindTexture(GL10.GL_TEXTURE_2D, texturesID[i]);
Bitmap bmp = BitmapFactory.decodeResource(mContext.getResources(), imgArray[0]);
//生成纹理
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);
// 线形滤波
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
}
}
private void SetupWorld() {
// TODO Auto-generated method stub
GLFile glFile = new GLFile(mContext.getResources());
BufferedReader br = new BufferedReader(new InputStreamReader(glFile.getFile("data/world.txt")));
TRIANGLE triangle = new TRIANGLE();
int vertexIndex = 0;
try {
String line = null;
while((line = br.readLine()) != null){
if(line.trim().length() <= 0 || line.startsWith("/")){
continue;
}
String part[] = line.trim().split("\\s+");
float x = Float.valueOf(part[0]);
float y = Float.valueOf(part[1]);
float z = Float.valueOf(part[2]);
float u = Float.valueOf(part[3]);
float v = Float.valueOf(part[4]);
VERTEX vertex = new VERTEX(x, y, z, u, v);
triangle.vertex[vertexIndex] = vertex;
vertexIndex ++;
if(vertexIndex == 3){
vertexIndex = 0;
sector1.triangle.add(triangle);
triangle = new TRIANGLE();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean onKeyUp(int keyCode, KeyEvent event)
{
switch ( keyCode )
{
case KeyEvent.KEYCODE_DPAD_LEFT:
yrot -= 1.5f;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
yrot += 1.5f;
break;
case KeyEvent.KEYCODE_DPAD_UP:
// 沿游戏者所在的X平面移动
xpos -= (float)Math.sin(heading*piover180) * 0.05f;
// 沿游戏者所在的Z平面移动
zpos -= (float)Math.cos(heading*piover180) * 0.05f;
if (walkbiasangle >= 359.0f)// 如果walkbiasangle大于359度
{
walkbiasangle = 0.0f;// 将 walkbiasangle 设为0
}
else
{
walkbiasangle+= 10;// 如果 walkbiasangle < 359 ,则增加 10
}
// 使游戏者产生跳跃感
walkbias = (float)Math.sin(walkbiasangle * piover180)/20.0f;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
// 沿游戏者所在的X平面移动
xpos += (float)Math.sin(heading*piover180) * 0.05f;
// 沿游戏者所在的Z平面移动
zpos += (float)Math.cos(heading*piover180) * 0.05f;
// 如果walkbiasangle小于1度
if (walkbiasangle <= 1.0f)
{
walkbiasangle = 359.0f;// 使 walkbiasangle 等于 359
}
else
{
walkbiasangle-= 10;// 如果 walkbiasangle > 1 减去 10
}
// 使游戏者产生跳跃感
walkbias = (float)Math.sin(walkbiasangle * piover180)/20.0f;
break;
}
return false;
}
public class GLFile
{
public Resources resources;
public GLFile(Resources r)
{
resources = r;
}
public InputStream getFile(String name){
AssetManager am = resources.getAssets();
try {
return am.open(name);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
}
Utils.java
Java代码
package org.ourunix.android.world;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
public class Utils {
public static class BufferUtil {
public static IntBuffer intBuffer;
public static FloatBuffer floatBuffer;
public static ShortBuffer shortBuffer;
public static IntBuffer iBuffer(int[] a) {
// 先初始化buffer,数组的长度*4,因为一个float占4个字节
ByteBuffer mbb = ByteBuffer.allocateDirect(a.length * 4);
// 数组排列用nativeOrder
mbb.order(ByteOrder.nativeOrder());
intBuffer = mbb.asIntBuffer();
intBuffer.put(a);
intBuffer.position(0);
return intBuffer;
}
public static FloatBuffer fBuffer(float[] a) {
// 先初始化buffer,数组的长度*4,因为一个float占4个字节
ByteBuffer mbb = ByteBuffer.allocateDirect(a.length * 4);
// 数组排列用nativeOrder
mbb.order(ByteOrder.nativeOrder());
floatBuffer = mbb.asFloatBuffer();
floatBuffer.put(a);
floatBuffer.position(0);
return floatBuffer;
}
public static ShortBuffer sBuffer(short[] a) {
// 先初始化buffer,数组的长度*4,因为一个float占4个字节
ByteBuffer mbb = ByteBuffer.allocateDirect(a.length * 2);
// 数组排列用nativeOrder
mbb.order(ByteOrder.nativeOrder());
shortBuffer = mbb.asShortBuffer();
shortBuffer.put(a);
shortBuffer.position(0);
return shortBuffer;
}
}
}
源码下载:Android高级进阶十一
Android OpenGL建立3D空间源码
Android OpenGL建立3D空间
这一篇文章继续写在Android上使用OpenGL,前阶段的文章一直是旨在建立一个3D的形状,这一篇文章我们就来建立一个3D的空间模型。
老规矩先上效果图:
这次我们使用一个文件来存储我们所有顶点及纹理定点数据,具体使用方式待会儿见分晓。
代码如下:
WordActivity.java
Java代码
package org.ourunix.android.world;
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.KeyEvent;
public class WordActivity extends Activity {
/** Called when the activity is first created. */
GLRenderer render;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GLSurfaceView glView = new GLSurfaceView(this);
render = new GLRenderer(this);
glView.setRenderer(render);
setContentView(glView);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
return render.onKeyUp(keyCode, event);
}
}
ScData.java
Java代码
package org.ourunix.android.world;
import java.util.ArrayList;
import java.util.List;
public class ScData {
public static class VERTEX{
float x, y, z; //三角形的顶点坐标
float u, v;//纹理坐标
public VERTEX(float x, float y , float z, float u, float v){
this.x = x;
this.y = y;
this.z = z;
this.u = u;
this.v = v;
}
}
//三角形结构
public static class TRIANGLE{
VERTEX[] vertex = new VERTEX[3];
}
//区段结构
public static class SECTOR{
int numTriangles;
List<TRIANGLE> triangle = new ArrayList<TRIANGLE>();
}
}
GLRenderer.java
Java代码
package org.ourunix.android.world;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import org.ourunix.android.world.ScData.SECTOR;
import org.ourunix.android.world.ScData.TRIANGLE;
import org.ourunix.android.world.ScData.VERTEX;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;
import android.opengl.GLSurfaceView.Renderer;
import android.view.KeyEvent;
public class GLRenderer implements Renderer {
private Context mContext;
SECTOR sector1 = new SECTOR();
public final static float piover180 = 0.0174532925f;
float heading;
float xpos;
float zpos;
float yrot; // Y Rotation
float walkbias = 0;
float walkbiasangle = 0;
float lookupdown = 0.0f;
float z=0.0f;
private int[] texturesID = new int[3];
public GLRenderer(Context ctx){
mContext = ctx;
}
@Override
public void onDrawFrame(GL10 gl) {
// TODO Auto-generated method stub
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
gl.glLoadIdentity(); // Reset The View
float xtrans = -xpos;
float ztrans = -zpos;
float ytrans = -walkbias-0.25f;
float sceneroty = 360.0f - yrot;
FloatBuffer vertexPointer = Utils.BufferUtil.fBuffer(new float[9]);
FloatBuffer texCoordPointer = Utils.BufferUtil.fBuffer(new float[6]);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexPointer);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texCoordPointer);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glLoadIdentity();
gl.glRotatef(lookupdown, 1.0f, 0.0f, 0.0f);
gl.glRotatef(sceneroty, 0.0f, 1.0f, 0.0f);
gl.glTranslatef(xtrans, ytrans, ztrans);
gl.glBindTexture(GL10.GL_TEXTURE_2D, texturesID[0]);
for(TRIANGLE triangle : sector1.triangle)
{
vertexPointer.clear();
texCoordPointer.clear();
gl.glNormal3f(0.0f, 0.0f, 1.0f);
for(int i=0; i<3; i++)
{
VERTEX vt = triangle.vertex[i];
vertexPointer.put(vt.x);
vertexPointer.put(vt.y);
vertexPointer.put(vt.z);
texCoordPointer.put(vt.u);
texCoordPointer.put(vt.v);
}
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 4);
}
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// TODO Auto-generated method stub
float ratio = (float)width/height;
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// TODO Auto-generated method stub
//加载纹理
gl.glEnable(GL10.GL_TEXTURE_2D);
LoadGLTextures(gl);
//开启混色透明
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
gl.glClearColor(0, 0, 0, 0);
gl.glShadeModel(GL10.GL_SMOOTH);
//开启深度测试
gl.glClearDepthf(1.0f);
gl.glDepthFunc(GL10.GL_LESS);
gl.glEnable(GL10.GL_DEPTH_TEST);
SetupWorld();
}
private void LoadGLTextures(GL10 gl) {
int[] imgArray = {R.drawable.img};
// TODO Auto-generated method stub
for (int i = 0; i < 3; i++){
IntBuffer textureBuffer = IntBuffer.allocate(1);
gl.glGenTextures(1, textureBuffer);
texturesID[i] = textureBuffer.get();
gl.glBindTexture(GL10.GL_TEXTURE_2D, texturesID[i]);
Bitmap bmp = BitmapFactory.decodeResource(mContext.getResources(), imgArray[0]);
//生成纹理
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);
// 线形滤波
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
}
}
private void SetupWorld() {
// TODO Auto-generated method stub
GLFile glFile = new GLFile(mContext.getResources());
BufferedReader br = new BufferedReader(new InputStreamReader(glFile.getFile("data/world.txt")));
TRIANGLE triangle = new TRIANGLE();
int vertexIndex = 0;
try {
String line = null;
while((line = br.readLine()) != null){
if(line.trim().length() <= 0 || line.startsWith("/")){
continue;
}
String part[] = line.trim().split("\\s+");
float x = Float.valueOf(part[0]);
float y = Float.valueOf(part[1]);
float z = Float.valueOf(part[2]);
float u = Float.valueOf(part[3]);
float v = Float.valueOf(part[4]);
VERTEX vertex = new VERTEX(x, y, z, u, v);
triangle.vertex[vertexIndex] = vertex;
vertexIndex ++;
if(vertexIndex == 3){
vertexIndex = 0;
sector1.triangle.add(triangle);
triangle = new TRIANGLE();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean onKeyUp(int keyCode, KeyEvent event)
{
switch ( keyCode )
{
case KeyEvent.KEYCODE_DPAD_LEFT:
yrot -= 1.5f;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
yrot += 1.5f;
break;
case KeyEvent.KEYCODE_DPAD_UP:
// 沿游戏者所在的X平面移动
xpos -= (float)Math.sin(heading*piover180) * 0.05f;
// 沿游戏者所在的Z平面移动
zpos -= (float)Math.cos(heading*piover180) * 0.05f;
if (walkbiasangle >= 359.0f)// 如果walkbiasangle大于359度
{
walkbiasangle = 0.0f;// 将 walkbiasangle 设为0
}
else
{
walkbiasangle+= 10;// 如果 walkbiasangle < 359 ,则增加 10
}
// 使游戏者产生跳跃感
walkbias = (float)Math.sin(walkbiasangle * piover180)/20.0f;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
// 沿游戏者所在的X平面移动
xpos += (float)Math.sin(heading*piover180) * 0.05f;
// 沿游戏者所在的Z平面移动
zpos += (float)Math.cos(heading*piover180) * 0.05f;
// 如果walkbiasangle小于1度
if (walkbiasangle <= 1.0f)
{
walkbiasangle = 359.0f;// 使 walkbiasangle 等于 359
}
else
{
walkbiasangle-= 10;// 如果 walkbiasangle > 1 减去 10
}
// 使游戏者产生跳跃感
walkbias = (float)Math.sin(walkbiasangle * piover180)/20.0f;
break;
}
return false;
}
public class GLFile
{
public Resources resources;
public GLFile(Resources r)
{
resources = r;
}
public InputStream getFile(String name){
AssetManager am = resources.getAssets();
try {
return am.open(name);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
}
Utils.java
Java代码
package org.ourunix.android.world;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
public class Utils {
public static class BufferUtil {
public static IntBuffer intBuffer;
public static FloatBuffer floatBuffer;
public static ShortBuffer shortBuffer;
public static IntBuffer iBuffer(int[] a) {
// 先初始化buffer,数组的长度*4,因为一个float占4个字节
ByteBuffer mbb = ByteBuffer.allocateDirect(a.length * 4);
// 数组排列用nativeOrder
mbb.order(ByteOrder.nativeOrder());
intBuffer = mbb.asIntBuffer();
intBuffer.put(a);
intBuffer.position(0);
return intBuffer;
}
public static FloatBuffer fBuffer(float[] a) {
// 先初始化buffer,数组的长度*4,因为一个float占4个字节
ByteBuffer mbb = ByteBuffer.allocateDirect(a.length * 4);
// 数组排列用nativeOrder
mbb.order(ByteOrder.nativeOrder());
floatBuffer = mbb.asFloatBuffer();
floatBuffer.put(a);
floatBuffer.position(0);
return floatBuffer;
}
public static ShortBuffer sBuffer(short[] a) {
// 先初始化buffer,数组的长度*4,因为一个float占4个字节
ByteBuffer mbb = ByteBuffer.allocateDirect(a.length * 2);
// 数组排列用nativeOrder
mbb.order(ByteOrder.nativeOrder());
shortBuffer = mbb.asShortBuffer();
shortBuffer.put(a);
shortBuffer.position(0);
return shortBuffer;
}
}
}
源码下载:Android高级进阶十一
Android OpenGL建立3D空间源码
相关文章推荐
- Android高级进阶一 OpenGL介绍
- Android高级进阶二 Android OpenGL | ES介绍
- Android高级进阶三 Android OpenGL | ES开发框架
- Android高级进阶四 Android OpenGL开发多边形
- Android高级进阶五 Android OpenGL给多边形着色
- Android高级进阶九 Android OpenGL对立方体进行光照处理
- Android高级进阶六 Android OpenGL旋转多边形
- Android高级进阶七 Android OpenGL开发四棱锥和立方体
- Android高级进阶八 Android OpenGL给立方体进行纹理映射
- Android高级进阶十 Android OpenGL开启混合色
- Android探索之旅(第二十四篇)进阶安卓高级开发工程师需要掌握的知识体系
- Android开发高级进阶——多进程间通信
- Android高级进阶-换肤
- OpenGL进阶(十一) - GLSL4.x中的数据传递
- 05:Tensorflow高级API的进阶--利用tf.contrib.learn建立输入函数
- Android 高级进阶 - 动画之补间动画(Tween Animation)
- Android高手进阶教程(十一)之----Android 通用获取Ip的方法(判断手机是否联网的方法)!
- 《Android 高级进阶》 --> APP 整体框架
- Android 高级进阶 - 动画之属性动画(Property Animation)
- Android进阶之Gradle的高级用法