您好,欢迎来到江西省计算机培训学院!
技术文摘当前位置:首页 >> 技术文摘
Android view
发布时间:2015年5月17日 浏览:1104 次
 

1.view

viewapi中的结构

java.lang.Object

android.view.View

直接子类:

AnalogClock, ImageView, KeyboardView, ProgressBar, SurfaceView, TextVie, ViewGroup, ViewStub

间接子类:

AbsListView, AbsSeekBar, AbsSpinner, AbsoluteLayout, AdapterView<T extends Adapter>, AppWidgetHostView, AutoCompleteTextView, Button, CheckBox, CheckedTextView, Chronometer, CompoundButton, DatePicker, DialerFilter, DigitalClock,EditView, ExpandableListView, ExtractEditText, FrameLayout, GLSurfaceView, Gallery, GestureOverlayView, GridView, HorizontalScrollView, ImageButton, ImageSwitcher, LinearLayout, ListView, MediaController, MultiAutoCompleteTextView, QuickContactBadge, RadioButton, RadioGroup, RatingBar, RelativeLayout, ScrollView, SeekBar, SlidingDrawer, Spinner, TabHost, TabWidget, TableLayout, TableRow, TextSwitcher, TimePicker, ToggleButton, TwoLineListItem, VideoView, ViewAnimator, ViewFlipper, ViewSwitcher, WebView, ZoomButton, ZoomControls

         由此可见View类属于Android开发绘制中的显示老大,任何与绘制有关系的控件都是它的子类。在这篇文章中我主要讲View SurFaceView 使用线程刷新屏幕绘制方面的知识。开发中如何去选择使用View还是SurFaceView。我相信读过我前几篇博客的朋友应该知道我在刷新屏幕的时候使用invalidate()方法来重绘,下面我详细的说明一下Andooid刷新屏幕的几种方法。

          第一种: onDraw方法最后调用invalidate()方法,它会通知UI线程重绘 这样 View会重新调用onDraw方法,实现刷新屏幕。 这样写看起来代码非常简洁漂亮,但是它也同时存在一个很大的问题,它和游戏主线程是分开的 它违背了单线程模式,这样操作绘制的话是很不安全的,举个例子 比如程序先进在Activity1 使用invalidate()方法来重绘, 然后我跳到了Activity2这时候Activity1已经finash() 可是Activity1 invalidate() 的线程还在程序中,Android的虚拟机不可能主动杀死正在运行中的线程所以这样操作是非常危险的。因为它是在UI线程中被动掉用的所以很不安全。

invalidate()  更新整个屏幕区域

invalidate(Rect rect) 更新Rect区域

invalidate(l, t, r, b) 更新指定矩形区域

view plaincopy to clipboard

public void onDraw(Canvas canvas){   

        DosomeThing();   

        invalidate();   

}   

[java] view plaincopy

public void onDraw(Canvas canvas){    

        DosomeThing();    

        invalidate();    

}    




第二种:使用postInvalidate();方法来刷新屏幕 ,调用后它会用handler通知UI线程重绘屏幕,我们可以 new  Thread(this).start(); 开启一个游戏的主线程 然后在主线程中通过调用postInvalidate();方法来刷新屏幕。postInvalidate();方法 调用后 系统会帮我们调用onDraw方法 ,它是在我们自己的线程中调用 通过调用它可以通知UI线程刷新屏幕 。由此可见它是主动调用UI线程的。所以建议使用postInvalidate()方法通知UI线程来刷新整个屏幕。

postInvalidate(left, top, right, bottom) 方法 通过UI线程来刷新规定矩形区域。

view plaincopy to clipboard

@Override 

public void run() { 

    while (mIsRunning) { 

    try { 

        Thread.sleep(100); 

                   postInvalidate(); 

    } catch (InterruptedException e) { 

        // TODO Auto-generated catch block 

        e.printStackTrace(); 

    } 

    } 

[java] view plaincopy

@Override  

public void run() {  

    while (mIsRunning) {  

    try {  

        Thread.sleep(100);  

                   postInvalidate();  

    } catch (InterruptedException e) {  

        // TODO Auto-generated catch block  

        e.printStackTrace();  

    }  

    }  

}  




View中用到的双缓冲技术

        重绘的原理是 程序根据时间来刷新屏幕 如果有一帧图形还没有完全绘制结束 程序就开始刷新屏幕这样就会造成瞬间屏幕闪烁 画面很不美观,所以双缓冲的技术就诞生了。它存在的目的就是解决屏幕闪烁的问题,下面我说说在自定义View中如何实现双缓冲。

首先我们需要创建一张屏幕大小的缓冲图片,我说一下第三个参数 ARGB 分别代表的是 透明度   红色   绿色     蓝色

Bitmap.Config  ARGB_4444              ARGB  分别占四位  
Bitmap.Config  ARGB_8888              ARGB 
分别占八位 
Bitmap.Config  RGB_565               
没有透明度(A   R5   G 6   B5   

一般情况下我们使用ARGB_8888 因为它的效果是最好了 当然它也是最占内存的。

view plaincopy to clipboard

mBufferBitmap = Bitmap.createBitmap(mScreenWidth,mScreenHeight,Config.ARGB_8888); 

[java] view plaincopy

mBufferBitmap = Bitmap.createBitmap(mScreenWidth,mScreenHeight,Config.ARGB_8888);  


创建一个缓冲的画布,将内容绘制在缓冲区mBufferBitmap

view plaincopy to clipboard

Canvas mCanvas = new Canvas(); 

mCanvas.setBitmap(mBufferBitmap); 

[java] view plaincopy

Canvas mCanvas = new Canvas();  

mCanvas.setBitmap(mBufferBitmap);  



最后一次性的把缓冲区mBufferBitmap绘制在屏幕上,怎么样 简单吧 呵呵。

view plaincopy to clipboard

@Override 

protected void onDraw(Canvas canvas) { 

    /**这里先把所有须要绘制的资源绘制到mBufferBitmap**/ 

    /**绘制地图**/ 

    DrawMap(mCanvas,mPaint,mBitmap); 

    /**绘制动画**/ 

    RenderAnimation(mCanvas); 

    /**更新动画**/ 

    UpdateAnimation(); 

     

     

    if(isBorderCollision) { 

    DrawCollision(mCanvas,"与边界发生碰撞"); 

    } 

     

    if(isAcotrCollision) { 

    DrawCollision(mCanvas,"与实体层发生碰撞"); 

    } 

    if(isPersonCollision) { 

    DrawCollision(mCanvas,"NPC发生碰撞"); 

    } 

     

    /**最后通过canvas一次性的把mBufferBitmap绘制到屏幕上**/ 

    canvas.drawBitmap(mBufferBitmap, 0,0, mPaint); 

    super.onDraw(canvas); 

[java] view plaincopy

@Override  

protected void onDraw(Canvas canvas) {  

    /**这里先把所有须要绘制的资源绘制到mBufferBitmap**/  

    /**绘制地图**/  

    DrawMap(mCanvas,mPaint,mBitmap);  

    /**绘制动画**/  

    RenderAnimation(mCanvas);  

    /**更新动画**/  

    UpdateAnimation();  

      

      

    if(isBorderCollision) {  

    DrawCollision(mCanvas,"与边界发生碰撞");  

    }  

      

    if(isAcotrCollision) {  

    DrawCollision(mCanvas,"与实体层发生碰撞");  

    }  

    if(isPersonCollision) {  

    DrawCollision(mCanvas,"NPC发生碰撞");  

    }  

      

    /**最后通过canvas一次性的把mBufferBitmap绘制到屏幕上**/  

    canvas.drawBitmap(mBufferBitmap, 0,0, mPaint);  

    super.onDraw(canvas);  

}  




         由此可见view属于被动刷新, 因为我们做的任何刷新的操作实际上都是通知UI线程去刷新。所以在做一些只有通过玩家操作以后才会刷新屏幕的游戏 并非自动刷新的游戏 可以使用view来操作。

2.SurfaceView

        API中可以看出SurfaceView属于View的子类 它是专门为制作游戏而产生的,它的功能非常强大,最重要的是它支持OpenGL ES库,2D3D的效果都可以实现。创建SurfaceView的时候需要实现SurfaceHolder.Callback接口,它可以用来监听SurfaceView的状态,SurfaceView的改变 SurfaceView的创建 SurfaceView 销毁  我们可以在相应的方法中做一些比如初始化的操作 或者 清空的操作等等。

       使用SurfaceView构建游戏框架它的绘制原理是绘制前先锁定画布 然后等都绘制结束以后 在对画布进行解锁 最后在把画布内容显示到屏幕上。

            

代码中是如何实现SurfaceView

首先需要实现 Callback 接口 Runnable接口

view plaincopy to clipboard

public class AnimView extends SurfaceView implements Callback,Runnable 

[java] view plaincopy

public class AnimView extends SurfaceView implements Callback,Runnable  

获取当前mSurfaceHolder 并且把它加到CallBack回调函数中

view plaincopy to clipboard

SurfaceHolder  mSurfaceHolder = getHolder(); 

mSurfaceHolder.addCallback(this); 

[java] view plaincopy

SurfaceHolder  mSurfaceHolder = getHolder();  

mSurfaceHolder.addCallback(this);  



        通过callBack接口监听SurfaceView的状态, 在它被创建的时候开启游戏的主线程,结束的时候销毁。这里说一下在View的构造函数中是拿不到view有关的任何信息的,因为它还没有构建好。 所以通过这个监听我们可以在surfaceCreated()中拿到当前view的属性 比如view的宽高 等等,所以callBack接口还是非常有用处的。

view plaincopy to clipboard

@Override 

public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, 

    int arg3) { 

    // surfaceView的大小发生改变的时候 

     

 

@Override 

public void surfaceCreated(SurfaceHolder arg0) { 

    /**启动游戏主线程**/ 

    mIsRunning = true; 

    mThread = new Thread(this); 

    mThread.start(); 

 

@Override 

public void surfaceDestroyed(SurfaceHolder arg0) { 

// surfaceView销毁的时候 

    mIsRunning = false; 

[java] view plaincopy

@Override  

public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,  

    int arg3) {  

    // surfaceView的大小发生改变的时候  

      

}  

  

@Override  

public void surfaceCreated(SurfaceHolder arg0) {  

    /**启动游戏主线程**/  

    mIsRunning = true;  

    mThread = new Thread(this);  

    mThread.start();  

}  

  

@Override  

public void surfaceDestroyed(SurfaceHolder arg0) {  

 // surfaceView销毁的时候  

    mIsRunning = false;  

}  

在游戏主线程循环中在绘制开始 先拿到画布canvas 并使用mSurfaceHolder.lockCanvas()锁定画布,等绘制结束以后 使用mSurfaceHolder.unlockCanvasAndPost(mCanvas)解锁画布,  解锁画布以后画布上的内容才会显示到屏幕上。

view plaincopy to clipboard

@Override 

public void run() { 

    while (mIsRunning) { 

    try { 

        Thread.sleep(100); 

    } catch (InterruptedException e) { 

        // TODO Auto-generated catch block 

        e.printStackTrace(); 

    } 

     

    //在这里加上线程安全锁 

    synchronized (mSurfaceHolder) { 

        /**拿到当前画布 然后锁定**/ 

        mCanvas =mSurfaceHolder.lockCanvas();   

        Draw(); 

        /**绘制结束后解锁显示在屏幕上**/ 

        mSurfaceHolder.unlockCanvasAndPost(mCanvas); 

    } 

    } 

[java] view plaincopy

@Override  

public void run() {  

    while (mIsRunning) {  

    try {  

        Thread.sleep(100);  

    } catch (InterruptedException e) {  

        // TODO Auto-generated catch block  

        e.printStackTrace();  

    }  

      

    //在这里加上线程安全锁  

    synchronized (mSurfaceHolder) {  

        /**拿到当前画布 然后锁定**/  

        mCanvas =mSurfaceHolder.lockCanvas();    

        Draw();  

        /**绘制结束后解锁显示在屏幕上**/  

        mSurfaceHolder.unlockCanvasAndPost(mCanvas);  

    }  

    }  

}  



由此可见SurfaceView 属于主动刷新 ,重绘过程完全是在我们自己的线程中完成 由于游戏中肯定会执行各种绚丽的动画效果如果使用被动刷新的View就有可能就会阻塞UI线程,所以SurfaceView 更适合做游戏。

效果图

        最近有朋友反映说运行起来有点卡 我解释一下,  卡的主要原因是我的地图文件太大了,当然还有模拟器不给力的原因。我每绘制一块地图就须要使用裁剪原图,频繁的切割如此大的图片肯定会造成卡顿的情况。同学们在制作的时候将没用的地图块去掉,保留只需要的地图块这样会流畅很多喔

优化游戏主线程循环

        同学们先看看这段代码,Draw()方法绘制结束让线程等待100毫秒在进入下一次循环。其实这样更新游戏循环是很不科学的,原因是Draw()方法每一次更新所耗费的时间是不确定的。举个例子 比如第一次循环Draw() 耗费了1000毫秒 加上线程等待100毫秒 整个循环耗时1100毫秒,第二次循环Draw() 耗时2000毫秒 加上线程等待时间100毫秒 整个循环时间就是2100毫秒。很明显这样就会造成游戏运行刷新时间时快时慢,所以说它是很不科学的。

view plaincopy to clipboard

public void run() { 

    while (mIsRunning) { 

    //在这里加上线程安全锁 

    synchronized (mSurfaceHolder) { 

        /**拿到当前画布 然后锁定**/ 

        mCanvas =mSurfaceHolder.lockCanvas();   

        Draw(); 

        /**绘制结束后解锁显示在屏幕上**/ 

        mSurfaceHolder.unlockCanvasAndPost(mCanvas); 

    } 

    try { 

        Thread.sleep(100); 

    } catch (InterruptedException e) { 

        e.printStackTrace(); 

    } 

    } 

[java] view plaincopy

public void run() {  

    while (mIsRunning) {  

    //在这里加上线程安全锁  

    synchronized (mSurfaceHolder) {  

        /**拿到当前画布 然后锁定**/  

        mCanvas =mSurfaceHolder.lockCanvas();    

        Draw();  

        /**绘制结束后解锁显示在屏幕上**/  

        mSurfaceHolder.unlockCanvasAndPost(mCanvas);  

    }  

    try {  

        Thread.sleep(100);  

    } catch (InterruptedException e) {  

        e.printStackTrace();  

    }  

    }  

}  




       在贴一段科学的控游戏制循环代码,每次循环游戏主线程 Draw()方法前后计算出Draw()方法所消耗的时间,然后在判断是否达到我们规定的刷新屏幕时间,下例是以30帧刷新一次屏幕,如果满足则继续下次循环如果不满足使用Thread.yield(); 让游戏主线程去等待 并计算当前等待时间直到等待时间满足30帧为止在继续下一次循环刷新游戏屏幕。

这里说一下Thread.yield(): Thread.sleep(long millis):的区别,Thread.yield():是暂停当前正在执行的线程对象 ,并去执行其他线程。Thread.sleep(long millis):则是使当前线程暂停参数中所指定的毫秒数然后在继续执行线程。

view plaincopy to clipboard

       /**30帧刷新一次屏幕**/ 

       public static final int TIME_IN_FRAME = 30; 

@Override 

public void run() { 

    while (mIsRunning) { 

     

    /**取得更新游戏之前的时间**/ 

    long startTime = System.currentTimeMillis(); 

     

    /**在这里加上线程安全锁**/ 

    synchronized (mSurfaceHolder) { 

        /**拿到当前画布 然后锁定**/ 

        mCanvas =mSurfaceHolder.lockCanvas();   

        Draw(); 

        /**绘制结束后解锁显示在屏幕上**/ 

        mSurfaceHolder.unlockCanvasAndPost(mCanvas); 

    } 

     

    /**取得更新游戏结束的时间**/ 

    long endTime = System.currentTimeMillis(); 

     

    /**计算出游戏一次更新的毫秒数**/ 

    int diffTime  = (int)(endTime - startTime); 

     

    /**确保每次更新时间为30**/ 

    while(diffTime <=TIME_IN_FRAME) { 

        diffTime = (int)(System.currentTimeMillis() - startTime); 

        /**线程等待**/ 

        Thread.yield(); 

    } 

     

    } 

[java] view plaincopy

       /**30帧刷新一次屏幕**/  

       public static final int TIME_IN_FRAME = 30;  

@Override  

public void run() {  

    while (mIsRunning) {  

      

    /**取得更新游戏之前的时间**/  

    long startTime = System.currentTimeMillis();  

      

    /**在这里加上线程安全锁**/  

    synchronized (mSurfaceHolder) {  

        /**拿到当前画布 然后锁定**/  

        mCanvas =mSurfaceHolder.lockCanvas();    

        Draw();  

        /**绘制结束后解锁显示在屏幕上**/  

        mSurfaceHolder.unlockCanvasAndPost(mCanvas);  

    }  

      

    /**取得更新游戏结束的时间**/  

    long endTime = System.currentTimeMillis();  

      

    /**计算出游戏一次更新的毫秒数**/  

    int diffTime  = (int)(endTime - startTime);  

      

    /**确保每次更新时间为30**/  

    while(diffTime <=TIME_IN_FRAME) {  

        diffTime = (int)(System.currentTimeMillis() - startTime);  

        /**线程等待**/  

        Thread.yield();  

    }  

      

    }  

}  




最后由于代码较多我就不贴在博客中了 下面给出Demo源码的下载地址欢迎大家下载阅读互相学习,互相研究,互相讨论 雨松MOMO希望可以和大家一起进步。

下载地址:http://download.csdn.net/source/3467906

Jk

将文章分享到:
上一篇: java递归的经典例子
下一篇: 数据库分页查询
学院动态
联系方式
报名咨询热线:
13576968766
15070010500(毛老师)
15870000729(王老师)
官方邮箱:kxy@qdlhrhjz.com
招办热线:张老师
传  真:0791-88176246
   
热门资讯
全国免费热线:400-0791-361
电话:13576968766 张老师
传真:0791-88176246
邮箱:kxy@qdlhrhjz.com
地址:江西省南昌市上坊路382号(江西省科学院内)
软件工程师高级培训
Java软件工程师
Android工程师
企业应用软件测试工程师
思科/华为网络高级培训
思科网络工程师
华为网络工程师
Linux网络工程师
版权所有澳门巴黎人娱乐app © 2014 江西省计算机培训学院 赣ICP备05000706号