`
wo_deqing
  • 浏览: 60409 次
文章分类
社区版块
存档分类
最新评论

Android View SurfaceView使用详解

 
阅读更多
Android中的View就是我们眼睛看到的、屏幕上显示的东东,是Activty的具体内容的体现。每一个View都有一个Canvas(画布),我们可以对它进行扩展,使用画布绘制我们想要的图像。对View进行扩展十分简单,只需要继承View类,重载它的onDraw方法,在onDraw方法中利用画布画出各种图案,包括三角形、点、矩形、线、图片等。View必须在UI线程中刷新屏幕,因此一般用于被动更新画面的游戏,既不需要实时更新画面的游戏,需要玩家操作后才更新,如:棋牌类游戏。更新画面有两种方式:调用invalidate()或者postInvalidate()方法。对于一些对画面更新实时性更强的游戏,如:动作类和角色扮演类游戏,一般使用SurfaceView代替,下一篇再讲SurfaceView。

首先比较一下invalidate()和postInvalidate()的区别,两者都是用来实现view的更新,但是前者只能在UI线程中直接调用,后者可以在非UI线程中使用,两者没有参数时都是更新整个屏幕的,可以指定参数如:invalidate(Rect rect) 、invalidate(left, top, right, bottom)、postInvalidate(left, top, right, bottom)更新指定区域。下面通过一个简单的demo来实现在UI线程中和子线程中使用invalidate()更新画布以及使用postInvalidate函数更新画布,在子线程使用invalidate函数,需要借助于Handler来帮忙。这个demo的作用是在用户点击处绘制一个红色的实心圆。

(1)直接在UI线程中调用invalidate()

  1. publicclassGameViewextendsView{
  2. privateintcx;
  3. privateintcy;
  4. privatePaintp;
  5. publicGameView(Contextcontext){
  6. super(context);
  7. this.cx=20;
  8. this.cy=20;
  9. this.p=newPaint();
  10. p.setColor(Color.RED);
  11. }
  12. @Override
  13. protectedvoidonDraw(Canvascanvas){
  14. canvas.drawCircle(cx,cy,10,p);
  15. }
  16. @Override
  17. publicbooleanonTouchEvent(MotionEventevent){
  18. switch(event.getAction()){
  19. caseMotionEvent.ACTION_DOWN:
  20. //返回false,则该事件消失且接收不到下次事件
  21. returntrue;
  22. caseMotionEvent.ACTION_UP:
  23. intx=(int)event.getX();
  24. inty=(int)event.getY();
  25. changePosition(x,y);
  26. returntrue;
  27. }
  28. returnsuper.onTouchEvent(event);
  29. }
  30. privatevoidchangePosition(intx,inty){
  31. this.cx=x;
  32. this.cy=y;
  33. this.invalidate();
  34. }
  35. }

上面的代码很简单,就是响应up事件后执行changePosition函数,改变圆圈的坐标并重绘,调用invalidate函数后会重新执行onDraw函数。需要注意的是:监听UP事件时,一定得监听DOWN事件并且DOWN事件一定返回true,否则UP事件不会被监听到。因为如果不监听和处理DOWN事件,super.onTouchEvent(event)会返回false,如果onTouchEvent返回false则表示该事件已消失且不接收下次事件,这样就无法接收到UP事件了。Android中触屏事件和按键事件的分发处理,我们以后再详细讨论。
(2)在子线程中间接调用invalidate(),有些代码跟上面一样的,就不重复贴了
  1. publicclassGameViewextendsView{
  2. //.....
  3. @Override
  4. publicbooleanonTouchEvent(MotionEventevent){
  5. switch(event.getAction()){
  6. caseMotionEvent.ACTION_DOWN:
  7. returntrue;
  8. caseMotionEvent.ACTION_UP:
  9. intx=(int)event.getX();
  10. inty=(int)event.getY();
  11. GameThreadgameThread=newGameThread(x,y);
  12. gameThread.start();
  13. returntrue;
  14. }
  15. returnsuper.onTouchEvent(event);
  16. }
  17. privateHandlermHandler=newHandler(){
  18. publicvoidhandleMessage(Messagemsg){
  19. changePosition(msg.arg1,msg.arg2);
  20. }
  21. };
  22. privateclassGameThreadextendsThread{
  23. privateintx;
  24. privateinty;
  25. publicGameThread(intx,inty){
  26. this.x=x;
  27. this.y=y;
  28. }
  29. publicvoidrun(){
  30. Messagemsg=mHandler.obtainMessage();
  31. msg.arg1=x;
  32. msg.arg2=y;
  33. msg.sendToTarget();
  34. }
  35. }
  36. }

其实最终还是在UI线程中执行的invalidate函数,利用了handler来处理线程间的通信,这样有一个好处:就是把一些费事的操作放到子线程中处理,处理完了就通过handler通知ui线程更新画布。
(3)在子线程中使用postInvalidate()
  1. @Override
  2. publicbooleanonTouchEvent(MotionEventevent){
  3. switch(event.getAction()){
  4. caseMotionEvent.ACTION_DOWN:
  5. returntrue;
  6. caseMotionEvent.ACTION_UP:
  7. intx=(int)event.getX();
  8. inty=(int)event.getY();
  9. GameThreadgameThread=newGameThread(x,y);
  10. gameThread.start();
  11. returntrue;
  12. }
  13. returnsuper.onTouchEvent(event);
  14. }
  15. privatevoidchangePosition(intx,inty){
  16. this.cx=x;
  17. this.cy=y;
  18. }
  19. privateclassGameThreadextendsThread{
  20. privateintx;
  21. privateinty;
  22. publicGameThread(intx,inty){
  23. this.x=x;
  24. this.y=y;
  25. }
  26. publicvoidrun(){
  27. changePosition(x,y);
  28. postInvalidate();
  29. }
  30. }

使用postInvalidate方式跟invalidate+handler的方式原理是一样的,内部也是使用handler机制实现的,不过它更简单使用些,代码量更少。下面简单的跟踪一下sdk的源码实现。
  1. publicvoidpostInvalidate(){
  2. postInvalidateDelayed(0);
  3. }
  4. publicvoidpostInvalidateDelayed(longdelayMilliseconds){
  5. //WetryonlywiththeAttachInfobecausethere'snopointininvalidating
  6. //ifwearenotattachedtoourwindow
  7. finalAttachInfoattachInfo=mAttachInfo;
  8. if(attachInfo!=null){
  9. attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this,delayMilliseconds);
  10. }
  11. }
  12. publicvoiddispatchInvalidateDelayed(Viewview,longdelayMilliseconds){
  13. Messagemsg=mHandler.obtainMessage(MSG_INVALIDATE,view);
  14. mHandler.sendMessageDelayed(msg,delayMilliseconds);
  15. }

从上面的代码可以看到,使用了mHandler去更新UI,mHandler是ViewRootHandler的一个实例,它是在UI线程中创建的。

(4)view实现双缓冲技术
当数据量比较大,绘图时间比较长时,重复绘图会出现闪烁现象,引起闪烁现象的主要原因是视觉反差比较大。使用双缓冲技术可以解决这个问题,Surfaceview默认是使用双缓冲技术的。在Android上实现双缓冲技术的步骤是:创建一个屏幕大小(实际绘图区域)的缓冲区(Bitmap),创建一个画布(Canvas),然后设置画布的bitmap为创建好的缓冲区,把需要绘制的图像绘制到缓冲区上。最后把缓冲区中的图像绘制到屏幕上。具体实现代码如下:

  1. publicBitmapdecodeBitmapFromRes(Contextcontext,intresourseId){
  2. BitmapFactory.Optionsopt=newBitmapFactory.Options();
  3. opt.inPreferredConfig=Config.ARGB_8888;
  4. opt.inPurgeable=true;
  5. opt.inInputShareable=true;
  6. InputStreamis=context.getResources().openRawResource(resourseId);
  7. returnBitmapFactory.decodeStream(is,null,opt);
  8. }
  9. @Override
  10. protectedvoidonDraw(Canvascanvas){
  11. CanvasbufferCanvas=newCanvas();
  12. Bitmapbitmap=Bitmap.createBitmap(320,480,Config.ARGB_8888);
  13. Bitmapimg=decodeBitmapFromRes(mContext,R.drawable.sprite);
  14. bufferCanvas.setBitmap(bitmap);
  15. bufferCanvas.drawBitmap(img,0,0,null);
  16. canvas.drawBitmap(bitmap,0,0,null);
  17. }

上面代码中出现的Bitmap.Config.ARGB_8888是一个枚举值,它还有其他几个值。可以看看它的源码定义。
  1. publicenumConfig{
  2. ALPHA_8(2),
  3. RGB_565(4),
  4. @Deprecated
  5. ARGB_4444(5),
  6. ARGB_8888(6);
  7. finalintnativeInt;
  8. @SuppressWarnings({"deprecation"})
  9. privatestaticConfigsConfigs[]={
  10. null,null,ALPHA_8,null,RGB_565,ARGB_4444,ARGB_8888
  11. };
  12. Config(intni){
  13. this.nativeInt=ni;
  14. }
  15. staticConfignativeToConfig(intni){
  16. returnsConfigs[ni];
  17. }
  18. }

ARGB 分别代表的是:透明度,红色,绿色,蓝色。
ALPHA_8:存储占一个字节内存。
RGB_565:不含alpha通道(透明度),存储占两个字节内存,其中:R占5位,G占6位,B占5位
ARGB_4444:存储占两个字节内存,现在已经过时了,ARGB分别占4位。

ARGB_8888:存储占四个字节内存,ARGB分别占8位,存储的图片质量比较高,但是比较耗内存



1. SurfaceView的定义
前面已经介绍过View了,下面来简单介绍一下SurfaceView,参考SDK文档和网络资料:SurfaceView是View的子类,它内嵌了一个专门用于绘制的Surface,你可以控制这个Surface的格式和尺寸,Surfaceview控制这个Surface的绘制位置。surface是纵深排序(Z-ordered)的,说明它总在自己所在窗口的后面。SurfaceView提供了一个可见区域,只有在这个可见区域内的surface内容才可见。surface的排版显示受到视图层级关系的影响,它的兄弟视图结点会在顶端显示。这意味者 surface的内容会被它的兄弟视图遮挡,这一特性可以用来放置遮盖物(overlays)(例如,文本和按钮等控件)。注意,如果surface上面有透明控件,那么每次surface变化都会引起框架重新计算它和顶层控件的透明效果,这会影响性能。
SurfaceView默认使用双缓冲技术的,它支持在子线程中绘制图像,这样就不会阻塞主线程了,所以它更适合于游戏的开发。

2. SurfaceView的使用
首先继承SurfaceView,并实现SurfaceHolder.Callback接口,实现它的三个方法:surfaceCreated,surfaceChanged,surfaceDestroyed。
surfaceCreated(SurfaceHolder holder):surface创建的时候调用,一般在该方法中启动绘图的线程。
surfaceChanged(SurfaceHolder holder, int format, int width,int height):surface尺寸发生改变的时候调用,如横竖屏切换。
surfaceDestroyed(SurfaceHolder holder) :surface被销毁的时候调用,如退出游戏画面,一般在该方法中停止绘图线程。
还需要获得SurfaceHolder,并添加回调函数,这样这三个方法才会执行。

3. SurfaceView实战
下面通过一个小demo来学习SurfaceView的使用,绘制一个精灵,该精灵有四个方向的行走动画,让精灵沿着屏幕四周不停的行走。游戏中精灵素材和最终实现的效果图:

首先创建核心类GameView.java,源码如下:

  1. publicclassGameViewextendsSurfaceViewimplements
  2. SurfaceHolder.Callback{
  3. //屏幕宽高
  4. publicstaticintSCREEN_WIDTH;
  5. publicstaticintSCREEN_HEIGHT;
  6. privateContextmContext;
  7. privateSurfaceHoldermHolder;
  8. //最大帧数(1000/30)
  9. privatestaticfinalintDRAW_INTERVAL=30;
  10. privateDrawThreadmDrawThread;
  11. privateFrameAnimation[]spriteAnimations;
  12. privateSpritemSprite;
  13. privateintspriteWidth=0;
  14. privateintspriteHeight=0;
  15. privatefloatspriteSpeed=(float)((500*SCREEN_WIDTH/480)*0.001);
  16. privateintrow=4;
  17. privateintcol=4;
  18. publicGameSurfaceView(Contextcontext){
  19. super(context);
  20. this.mContext=context;
  21. mHolder=this.getHolder();
  22. mHolder.addCallback(this);
  23. initResources();
  24. mSprite=newSprite(spriteAnimations,0,0,spriteWidth,spriteHeight,spriteSpeed);
  25. }
  26. privatevoidinitResources(){
  27. Bitmap[][]spriteImgs=generateBitmapArray(mContext,R.drawable.sprite,row,col);
  28. spriteAnimations=newFrameAnimation[row];
  29. for(inti=0;i<row;i++){
  30. Bitmap[]spriteImg=spriteImgs[i];
  31. FrameAnimationspriteAnimation=newFrameAnimation(spriteImg,newint[]{150,150,150,150},true);
  32. spriteAnimations[i]=spriteAnimation;
  33. }
  34. }
  35. publicBitmapdecodeBitmapFromRes(Contextcontext,intresourseId){
  36. BitmapFactory.Optionsopt=newBitmapFactory.Options();
  37. opt.inPreferredConfig=Bitmap.Config.RGB_565;
  38. opt.inPurgeable=true;
  39. opt.inInputShareable=true;
  40. InputStreamis=context.getResources().openRawResource(resourseId);
  41. returnBitmapFactory.decodeStream(is,null,opt);
  42. }
  43. publicBitmapcreateBitmap(Contextcontext,Bitmapsource,introw,
  44. intcol,introwTotal,intcolTotal){
  45. Bitmapbitmap=Bitmap.createBitmap(source,
  46. (col-1)*source.getWidth()/colTotal,
  47. (row-1)*source.getHeight()/rowTotal,source.getWidth()
  48. /colTotal,source.getHeight()/rowTotal);
  49. returnbitmap;
  50. }
  51. publicBitmap[][]generateBitmapArray(Contextcontext,intresourseId,
  52. introw,intcol){
  53. Bitmapbitmaps[][]=newBitmap[row][col];
  54. Bitmapsource=decodeBitmapFromRes(context,resourseId);
  55. this.spriteWidth=source.getWidth()/col;
  56. this.spriteHeight=source.getHeight()/row;
  57. for(inti=1;i<=row;i++){
  58. for(intj=1;j<=col;j++){
  59. bitmaps[i-1][j-1]=createBitmap(context,source,i,j,
  60. row,col);
  61. }
  62. }
  63. if(source!=null&&!source.isRecycled()){
  64. source.recycle();
  65. source=null;
  66. }
  67. returnbitmaps;
  68. }
  69. publicvoidsurfaceChanged(SurfaceHolderholder,intformat,intwidth,
  70. intheight){
  71. }
  72. publicvoidsurfaceCreated(SurfaceHolderholder){
  73. if(null==mDrawThread){
  74. mDrawThread=newDrawThread();
  75. mDrawThread.start();
  76. }
  77. }
  78. publicvoidsurfaceDestroyed(SurfaceHolderholder){
  79. if(null!=mDrawThread){
  80. mDrawThread.stopThread();
  81. }
  82. }
  83. privateclassDrawThreadextendsThread{
  84. publicbooleanisRunning=false;
  85. publicDrawThread(){
  86. isRunning=true;
  87. }
  88. publicvoidstopThread(){
  89. isRunning=false;
  90. booleanworkIsNotFinish=true;
  91. while(workIsNotFinish){
  92. try{
  93. this.join();//保证run方法执行完毕
  94. }catch(InterruptedExceptione){
  95. //TODOAuto-generatedcatchblock
  96. e.printStackTrace();
  97. }
  98. workIsNotFinish=false;
  99. }
  100. }
  101. publicvoidrun(){
  102. longdeltaTime=0;
  103. longtickTime=0;
  104. tickTime=System.currentTimeMillis();
  105. while(isRunning){
  106. Canvascanvas=null;
  107. try{
  108. synchronized(mHolder){
  109. canvas=mHolder.lockCanvas();
  110. //设置方向
  111. mSprite.setDirection();
  112. //更新精灵位置
  113. mSprite.updatePosition(deltaTime);
  114. drawSprite(canvas);
  115. }
  116. }catch(Exceptione){
  117. e.printStackTrace();
  118. }finally{
  119. if(null!=mHolder){
  120. mHolder.unlockCanvasAndPost(canvas);
  121. }
  122. }
  123. deltaTime=System.currentTimeMillis()-tickTime;
  124. if(deltaTime<DRAW_INTERVAL){
  125. try{
  126. Thread.sleep(DRAW_INTERVAL-deltaTime);
  127. }catch(InterruptedExceptione){
  128. e.printStackTrace();
  129. }
  130. }
  131. tickTime=System.currentTimeMillis();
  132. }
  133. }
  134. }
  135. privatevoiddrawSprite(Canvascanvas){
  136. //清屏操作
  137. canvas.drawColor(Color.BLACK);
  138. mSprite.draw(canvas);
  139. }
  140. }

GameView.java中包含了一个绘图线程DrawThread,在线程的run方法中锁定Canvas、绘制精灵、更新精灵位置、释放Canvas等操作。因为精灵素材是一张大图,所以这里进行了裁剪生成一个二维数组。使用这个二维数组初始化了精灵四个方向的动画,下面看Sprite.java的源码:
  1. publicclassSprite{
  2. publicstaticfinalintDOWN=0;
  3. publicstaticfinalintLEFT=1;
  4. publicstaticfinalintRIGHT=2;
  5. publicstaticfinalintUP=3;
  6. publicfloatx;
  7. publicfloaty;
  8. publicintwidth;
  9. publicintheight;
  10. //精灵行走速度
  11. publicdoublespeed;
  12. //精灵当前行走方向
  13. publicintdirection;
  14. //精灵四个方向的动画
  15. publicFrameAnimation[]frameAnimations;
  16. publicSprite(FrameAnimation[]frameAnimations,intpositionX,
  17. intpositionY,intwidth,intheight,floatspeed){
  18. this.frameAnimations=frameAnimations;
  19. this.x=positionX;
  20. this.y=positionY;
  21. this.width=width;
  22. this.height=height;
  23. this.speed=speed;
  24. }
  25. publicvoidupdatePosition(longdeltaTime){
  26. switch(direction){
  27. caseLEFT:
  28. //让物体的移动速度不受机器性能的影响,每帧精灵需要移动的距离为:移动速度*时间间隔
  29. this.x=this.x-(float)(this.speed*deltaTime);
  30. break;
  31. caseDOWN:
  32. this.y=this.y+(float)(this.speed*deltaTime);
  33. break;
  34. caseRIGHT:
  35. this.x=this.x+(float)(this.speed*deltaTime);
  36. break;
  37. caseUP:
  38. this.y=this.y-(float)(this.speed*deltaTime);
  39. break;
  40. }
  41. }
  42. /**
  43. *根据精灵的当前位置判断是否改变行走方向
  44. */
  45. publicvoidsetDirection(){
  46. if(this.x<=0
  47. &&(this.y+this.height)<GameSurfaceView.SCREEN_HEIGHT){
  48. if(this.x<0)
  49. this.x=0;
  50. this.direction=Sprite.DOWN;
  51. }elseif((this.y+this.height)>=GameSurfaceView.SCREEN_HEIGHT
  52. &&(this.x+this.width)<GameSurfaceView.SCREEN_WIDTH){
  53. if((this.y+this.height)>GameSurfaceView.SCREEN_HEIGHT)
  54. this.y=GameSurfaceView.SCREEN_HEIGHT-this.height;
  55. this.direction=Sprite.RIGHT;
  56. }elseif((this.x+this.width)>=GameSurfaceView.SCREEN_WIDTH
  57. &&this.y>0){
  58. if((this.x+this.width)>GameSurfaceView.SCREEN_WIDTH)
  59. this.x=GameSurfaceView.SCREEN_WIDTH-this.width;
  60. this.direction=Sprite.UP;
  61. }else{
  62. if(this.y<0)
  63. this.y=0;
  64. this.direction=Sprite.LEFT;
  65. }
  66. }
  67. publicvoiddraw(Canvascanvas){
  68. FrameAnimationframeAnimation=frameAnimations[this.direction];
  69. Bitmapbitmap=frameAnimation.nextFrame();
  70. if(null!=bitmap){
  71. canvas.drawBitmap(bitmap,x,y,null);
  72. }
  73. }
  74. }

精灵类主要是根据当前位置判断行走的方向,然后根据行走的方向更新精灵的位置,再绘制自身的动画。由于精灵的动画是一帧一帧的播放图片,所以这里封装了FrameAnimation.java,源码如下:
  1. publicclassFrameAnimation{
  2. /**动画显示的需要的资源*/
  3. privateBitmap[]bitmaps;
  4. /**动画每帧显示的时间*/
  5. privateint[]duration;
  6. /**动画上一帧显示的时间*/
  7. protectedLonglastBitmapTime;
  8. /**动画显示的索引值,防止数组越界*/
  9. protectedintstep;
  10. /**动画是否重复播放*/
  11. protectedbooleanrepeat;
  12. /**动画重复播放的次数*/
  13. protectedintrepeatCount;
  14. /**
  15. *@parambitmap:显示的图片<br/>
  16. *@paramduration:图片显示的时间<br/>
  17. *@paramrepeat:是否重复动画过程<br/>
  18. */
  19. publicFrameAnimation(Bitmap[]bitmaps,intduration[],booleanrepeat){
  20. this.bitmaps=bitmaps;
  21. this.duration=duration;
  22. this.repeat=repeat;
  23. lastBitmapTime=null;
  24. step=0;
  25. }
  26. publicBitmapnextFrame(){
  27. //判断step是否越界
  28. if(step>=bitmaps.length){
  29. //如果不无限循环
  30. if(!repeat){
  31. returnnull;
  32. }else{
  33. lastBitmapTime=null;
  34. }
  35. }
  36. if(null==lastBitmapTime){
  37. //第一次执行
  38. lastBitmapTime=System.currentTimeMillis();
  39. returnbitmaps[step=0];
  40. }
  41. //第X次执行
  42. longnowTime=System.currentTimeMillis();
  43. if(nowTime-lastBitmapTime<=duration[step]){
  44. //如果还在duration的时间段内,则继续返回当前Bitmap
  45. //如果duration的值小于0,则表明永远不失效,一般用于背景
  46. returnbitmaps[step];
  47. }
  48. lastBitmapTime=nowTime;
  49. returnbitmaps[step++];//返回下一Bitmap
  50. }
  51. }

FrameAnimation根据每一帧的显示时间返回当前的图片帧,若没有超过指定的时间则继续返回当前帧,否则返回下一帧。
接下来需要做的是让Activty显示的View为我们之前创建的GameView,然后设置全屏显示。
  1. publicvoidonCreate(BundlesavedInstanceState){
  2. super.onCreate(savedInstanceState);
  3. getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
  4. WindowManager.LayoutParams.FLAG_FULLSCREEN);
  5. requestWindowFeature(Window.FEATURE_NO_TITLE);
  6. getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
  7. WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  8. DisplayMetricsoutMetrics=newDisplayMetrics();
  9. this.getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
  10. GameSurfaceView.SCREEN_WIDTH=outMetrics.widthPixels;
  11. GameSurfaceView.SCREEN_HEIGHT=outMetrics.heightPixels;
  12. GameSurfaceViewgameView=newGameSurfaceView(this);
  13. setContentView(gameView);
  14. }

现在运行Android工程,应该就可以看到一个手持宝剑的武士在沿着屏幕不停的走了。


分享到:
评论

相关推荐

    Android View类与SurfaceView类详解

    本文主要介绍Android View类与SurfaceView类,这里提供了详细的Android View类和SurfaceView类的使用方法,有兴趣的小伙伴可以参考下

    Android 自定义SurfaceView详解

    在Android游戏开发教程之二:View类与SurfaceView类中我们已经谈到,SurfaceView类是有很多优势的,所以在Android游戏开发中还是选择SurfaceView。  这里我们直接继承SurfaceView,实现SurfaceHolder.Callback接口...

    Android编程之SurfaceView实例详解

    本文实例讲述了Android编程之SurfaceView用法。分享给大家供大家参考,具体如下: 关于surfaceView相关知识: View和SurfaceView主要区别: 1. View只能在UI线程中刷新,而SurfaceView可以在子线程中刷新 2. ...

    Android游戏开发20回合

    Android游戏开发三 View类详解 Android游戏开发四 Canvas和Paint实例 Android游戏开发五Path和Typeface Android游戏开发六 自定义View Android游戏开发七 自定义SurfaceView Android游戏开发八 SurfaceView类实例 ...

    Android游戏开发之旅

    3.Android游戏开发之旅三 View类详解 4.Android游戏开发之旅四 Canvas和Paint实例 5.Android游戏开发之旅五 Path和Typeface 6.Android游戏开发之旅六 自定义View 7.Android游戏开发之旅七 自定义SurfaceView 8....

    android群雄传

    6.8 View之孪生兄弟——SurfaceView 155 6.8.1 Surface View与View的区别 155 6.8.2 Surface View的使用 156 6.8.3 Surface View实例 159 第7章 Android动画机制与使用技巧 162 7.1 Android View动画框架 163 ...

    android开发揭秘PDF

    2.3.2 运行HelloAndroid及模拟器的使用 2.3.3 调试HelloAndroid 2.4 小结 第二部分 基础篇 第3章 Android程序设计基础 3.1 Android程序框架 3.1.1 Android项目目录结构 3.1.2 Android应用解析 3.2 Android的生命...

    android双缓冲技术实例详解

    因此,在进行Android游戏开发时应尽量使用SurfaceView而不要使用View,这样的话效率较高,并且SurfaceView的功能也更加完善。为了更容易的了解双缓冲技术,下面介绍用View实现双缓冲的方法。 在此需要说明一下,双...

    详解Android 折叠屏适配攻略

    主要介绍了Android 折叠屏适配攻略,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

    android开发资料大全

    Android自定义View研究-- 一个小Demo Android调用相册拍照实现系统控件缩放切割图片 Android SQLite的实例汇总大全 两分钟彻底让你明白Android Activity生命周期(图文)! Android 图形系统剖析 Android 立体效果图片...

    《Android应用开发揭秘》附带光盘代码.

    《Android应用开发揭秘》全部实例源代码,配合《Android应用开发揭秘》使用 前言  第一部分 准备篇  第1章 Android开发简介  1.1 Android基本概念  1.1.1 Android简介  1.1.2 Android的系统构架  1.1.3 ...

    疯狂Android讲义源码

     1.3.2 使用Android模拟器  (Emulator) 14  1.3.3 使用DDMS进行调试 15  1.3.4 Android Debug Bridge(ADB)  的用法 16  1.3.5 使用DX编译Android应用 18  1.3.6 使用Android Asset Packaging  Tool...

    Android应用开发揭秘pdf高清版

    2.3.2 运行HelloAndroid及模拟器的使用 2.3.3 调试HelloAndroid 2.4 小结 第二部分 基础篇 第3章 Android程序设计基础 3.1 Android程序框架 3.1.1 Android项目目录结构 3.1.2 Android应用解析 3.2 Android的生命...

    《Android应用开发揭秘》源码

     2.3.2 运行HelloAndroid及模拟器的使用  2.3.3 调试HelloAndroid  2.4 小结  第二部分 基础篇  第3章 Android程序设计基础  3.1 Android程序框架  3.1.1 Android项目目录结构  3.1.2 Android应用解析  3.2...

    疯狂Android讲义.part2

    1.3.2 使用Android模拟器 (Emulator) 14 1.3.3 使用DDMS进行调试 15 1.3.4 Android Debug Bridge(ADB) 的用法 16 1.3.5 使用DX编译Android应用 18 1.3.6 使用Android Asset Packaging Tool(AAPT)打包资源 19 ...

Global site tag (gtag.js) - Google Analytics