View界面刷新的方法

  Android程序中可以使用的界面刷新方法有两种,分别是利用Handler与Invalidate()的组合和利用postInvalidate()来实现在线程中刷新界面。

       每一次调用他们,他们就会自动去执行View中的OnDraw()一次,从而达到刷新界面的效果,所以如果你想不断的刷新界面则最好是把调用此两个方法的语句放在一个Thread中,判断这线程是否已经中断while (!Thread.currentThread().isInterrupted()) {},没有不断的执行调语句来刷新界面

下面是网络一篇关于介绍此问题的一文import android.app.Activity;

004import android.os.Bundle;
005import android.os.Handler;
006import android.os.Message;
007import android.view.KeyEvent;
008import android.view.MotionEvent;
009 
010public class Activity01 extends Activity
011{
012 private static final int REFRESH  = 0x000001;
013  
014 /** 声明GameView类对象 */
015 private GameView   mGameView = null;
016 
017 /** Called when the activity is first created. */
018 @Override
019 public void onCreate(Bundle savedInstanceState)
020 {
021  super.onCreate(savedInstanceState);
022 
023  /** 实例化GameView对象 */
024  this.mGameView = new GameView(this);
025 
026  // 设置显示为我们自定义的View(GameView)
027  setContentView(mGameView);
028 
029  // 开启线程
030  new Thread(new GameThread()).start();
031 }
032 
033// Handler myHandler = new Handler()
034// {
035//  //接收到消息后处理
036//  public void handleMessage(Message msg)
037//  { switch (msg.what)
038//   {
039//   case Activity01.REFRESH:
040//    mGameView.invalidate();
041//    break;   }
042//   super.handleMessage(msg);
043//  }  
044// };
045//
046// class GameThread implements Runnable
047// { public void run()
048//  {  while (!Thread.currentThread().isInterrupted())
049//   { Message message = new Message();
050//    message.what = Activity01.REFRESH;
051//    //发送消息
052//    Activity01.this.myHandler.sendMessage(message);
053//    try
054//    {Thread.sleep(100);
055//    }
056//    catch (InterruptedException e)
057//    {Thread.currentThread().interrupt();
058//    }
059//   }
060//  }
061// }
062// **
063//  * 当然可以将GameThread类这样写
064//  * 同样可以更新界面,并且不在需要
065//  * Handler在接受消息
066 class GameThread implements Runnable
067 {
068  public void run()
069  {
070   while (!Thread.currentThread().isInterrupted())
071   {
072    try
073    {
074     Thread.sleep(100);
075    }
076    catch (InterruptedException e)
077    {
078     Thread.currentThread().interrupt();
079    }
080    //使用postInvalidate可以直接在线程中更新界面
081    mGameView.postInvalidate();
082//    如果用invalidate()则会出错
083//    mGameView.invalidate();
084//    FATAL EXCEPTION: Thread-8
085//     android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
086//      at android.view.ViewRoot.checkThread(ViewRoot.java:2802)
087//     at android.view.ViewRoot.invalidateChild(ViewRoot.java:607)
088//      at android.view.ViewRoot.invalidateChildInParent(ViewRoot.java:633)
089//      at android.view.ViewGroup.invalidateChild(ViewGroup.java:2505)
090//      at android.view.View.invalidate(View.java:5139)
091//     at com.yarin.android.Examples_05_01.Activity01$GameThread.run(Activity01.java:81)
092//     at java.lang.Thread.run(Thread.java:1096)
093//               查看View源文件
094//    public void postInvalidate(int left, int top, int right, int bottom) {
095//           postInvalidateDelayed(0, left, top, right, bottom);
096//       }
097//
098//       public void postInvalidateDelayed(long delayMilliseconds) {
099//           // We try only with the AttachInfo because there's no point in invalidating
100//           // if we are not attached to our window
101//           if (mAttachInfo != null) {
102//               Message msg = Message.obtain();
103//               msg.what = AttachInfo.INVALIDATE_MSG;
104//               msg.obj = this;
105//               mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
106//           }
107 
108   }
109  }
110 }
111  
112  
113 //详细事件处理见第三章
114 //当然这些事件也可以写在GameView中
115 //触笔事件
116 public boolean onTouchEvent(MotionEvent event)
117 {
118  return true;
119 }
120  
121 //按键按下事件
122    public boolean onKeyDown(int keyCode, KeyEvent event)
123    {
124     return true;
125    }
126    
127 //按键弹起事件
128 public boolean onKeyUp(int keyCode, KeyEvent event)
129 {
130  switch (keyCode)
131  {
132  //上方向键
133  case KeyEvent.KEYCODE_DPAD_UP:
134   mGameView.y-=3;
135   break;
136  //下方向键
137  case KeyEvent.KEYCODE_DPAD_DOWN:
138   mGameView.y+=3;
139   break;
140  }
141  return false;
142 }
143  
144 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)
145 {
146  return true;
147 }
148}

为了弄清这问题我自己测试一些问题,下面是问题的总结,方便他日参考:


    class Threadss implements Runnable{

        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (!Thread.currentThread().isInterrupted()) {
            Message smg=new Message();
            smg.what=1;
            MainActivity.this.handle.sendMessage(smg);
            Log.i("Threadss->", "run");
            try{
                Thread.sleep(2000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            }
        }
        
    }

public class MyGraphics extends View implements Runnable{   //自定义View  
    private Paint paint=null;                               //声明画笔对象  
    public MyGraphics(Context context) {  
        super(context);  
        // TODO Auto-generated constructor stub  
        paint=new Paint();                              //构建对象  
      // new Thread(this).start();                           //开启线程  
    }  
    @Override  
    protected void onDraw(Canvas canvas) {                  //重载onDraw方法  
        // TODO Auto-generated method stub  
        super.onDraw(canvas);  
        paint.setColor(Color.RED);                          //设置画笔颜色  
        canvas.drawColor(Color.WHITE);  
        canvas.drawLine(50, 50, 450, 50, paint);            //绘制直线  
        canvas.drawRect(100, 100, 200, 600, paint);         //绘制矩形  
        canvas.drawRect(300, 100, 400, 600, paint);         //绘制矩形  
        Log.i("----------------", "+++++++++++");
    }  
    @Override  
    public void run() {                                 //重载run方法  
        // TODO Auto-generated method stub  
        while(!Thread.currentThread().isInterrupted())  
        {  
            try  
            {  
                Thread.sleep(100);  
            }  
            catch(InterruptedException e)  
            {  
                Thread.currentThread().interrupt();  
            }  
            postInvalidate();                               //更新界面  
        }  
    }  
    public void test(){
        postInvalidate();  
         Log.i("----------test-----", "+++++++++++");
    }
}

    public void onCreate(Bundle savedInstanceState) {   这是测试方案一
        super.onCreate(savedInstanceState);
        setContentView(new MyGraphics(this));
        new Thread(new Threadss()).start();
         handle=new Handler(){

            @Override
            public void handleMessage(Message msg) {
                // TODO Auto-generated method stub
                super.handleMessage(msg);
                new MyGraphics(MainActivity.this).invalidate();
            }
            
        };

我这样也是能过一个线程不断的发送信息(log信息显示是在不停的打印Threadss->", "run"),接收信息,来不断刷新界面,但是运行结果却没有达到这个效果,它只执行了一次onDraw()方法,并没有不断的执行此ondraw方法,而执行ondraw方法是在setContentView(new MyGraphics(this));这里执行的,那为什么没在执行下面 new MyGraphics(MainActivity.this).invalidate();来刷新界面了,刚开始我也是弄了很久,到弄了很久才想清楚出现此问题的原因,这原因是类的创建对象有关,这两句话的都是各自创建一个MyGraphics实例对象,他们并不是同一个对象,所以不会达到不断执行,如果要不断的刷新界面的正解写法是:

   private  MyGraphics mGameView;

    public void onCreate(Bundle savedInstanceState) {   这是测试方案一
        super.onCreate(savedInstanceState);

       mGameView=new mGameView(this);
        setContentView(mGameView);
        new Thread(new Threadss()).start();
         handle=new Handler(){

            @Override
            public void handleMessage(Message msg) {
                // TODO Auto-generated method stub
                super.handleMessage(msg);
               mGameView.invalidate();
            }
            
        };


方案二:

    private MyGraphics mGameView;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mGameView=new MyGraphics(this);
        setContentView(mGameView);
        mGameView.test();

}或是

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MyGraphics(this));
        new  MyGraphics(this).test();

}

通过调用类的另一个方法,在另一个方法中刷新界面(写postInvalidate();  ),它都会去执行OnDraw()一次,从而达到刷新界面的效果 ,打印的结果都是

07-25 02:26:51.123: I/----------test-----(477): +++++++++++
07-25 02:26:51.323: I/----------------(477): +++++++++++


方案三:

    @Override  
    protected void onDraw(Canvas canvas) {                  //重载onDraw方法  
        // TODO Auto-generated method stub  
        super.onDraw(canvas);  
        paint.setColor(Color.RED);                          //设置画笔颜色  
        canvas.drawColor(Color.WHITE);  
        canvas.drawLine(50, 50, 450, 50, paint);            //绘制直线  
        canvas.drawRect(100, 100, 200, 600, paint);         //绘制矩形  
        canvas.drawRect(300, 100, 400, 600, paint);         //绘制矩形  
        Log.i("----------------", "+++++++++++");
        postInvalidate();     在我这里类里面写成invalidate() 也可行,
    }
postInvalidate()或是invalidate() 方法写在OnDraw()方法内,也会不断的执行OnDraw()方法(递归效果),

下面的文章转自网络上的,在这里记录一下以便日后自己查询:
Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用。

Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。


  实例化一个Handler对象,并重写handleMessage方法调用invalidate()实现界面刷新;而在线程中通过sendMessage发送界面更新消息。

// 在onCreate()中开启线程


new Thread(new GameThread()).start();、

// 实例化一个handler

Handler myHandler = new Handler() {
// 接收到消息后处理
public void handleMessage(Message msg) {
switch (msg.what) {
case Activity01.REFRESH:
mGameView.invalidate(); // 刷新界面
break;
}

super.handleMessage(msg);
}
};

class GameThread implements Runnable {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
Message message = new Message();
message.what = Activity01.REFRESH;
// 发送消息
Activity01.this.myHandler.sendMessage(message);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

}

2,使用postInvalidate()刷新界面
使用postInvalidate则比较简单,不需要handler,直接在线程中调用postInvalidate即可。

class GameThread implements Runnable {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}

// 使用postInvalidate可以直接在线程中更新界面
mGameView.postInvalidate();
}
}
}

 

View 类中postInvalidate()方法源码如下,可见它也是用到了handler的:
  1. public void postInvalidate() {
  2.         postInvalidateDelayed(0);
  3. }

  4. public void postInvalidateDelayed(long delayMilliseconds) {
  5.         // We try only with the AttachInfo because there's no point in invalidating
  6.         // if we are not attached to our window
  7.         if (mAttachInfo != null) {
  8.             Message msg = Message.obtain();
  9.             msg.what = AttachInfo.INVALIDATE_MSG;
  10.             msg.obj = this;
  11.             mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
  12.         }
  13.     }



除了onCreate()是运行在UI线程上的,其实其他大部分方法都是运行在UI线程上的,其实其实只要你没有开启新的线程,你的代码基本上都运行在UI线程上。



android

Handler.post(action) 这是android提供的一种机制,handler对象将通过post方法,将里面的Runnable对象放到UI执行队列中,UI消费这个队列,调用Runnable的run方法。这里并不生成新的线程。

 

  Android中在绘图中的多线程中,invalidate和postInvalidate这两个方法是用来刷新界面的,调用这两个方法后,会调用onDraw事件,让界面重绘。

 

 

在一个没有使用线程的小游戏中想刷新一下时间进度,想到用Timer。于是写了一段代码:

 

        nStartRoundTime = System.currentTimeMillis();
        nT1 = new Timer();
        nT1.schedule(new TimerTask(){ //计划运行时间间隔
                public void run(){
                    refreshTimePaint(); //过3秒调用一下refreshTimePaint()
                }
              },0,3000);

 

 

 

    public void refreshTimePaint(){
        invalidate(); //使用invalidate();刷新
        System.out.println(System.currentTimeMillis());
        System.out.println(nGameState);
    }

 

    同时我也将System.currentTimeMillis()打印在View上。

 

运行一下,发现并不是预期那样, System.out.println的结果在Log里面都有变化,但是View却没有反应。 不但View上面没有被刷新,甚至连原来的触屏事件都没有反映了。

 

去网上查了一下,得到的一些解释有这些:

 

The best thing is to  use Handler with delayed messages.
And Timer works fine, the problem is that a Timer runs in a separate thread,   and so you are trying to modify a view owned by another thread (the main   thread that originally created it).

 

 

What I think is happening is you're falling off the UI thread. There is a single "looper" thread which handles all screen updates. If you attempt to call "invalidate()" and you're not on this thread nothing will happen.

Try using "postInvalidate()" on your view instead. It'll let you update a view when you're not in the current UI thread.

 

于是把refreshTimePaint()的代码改成:

 

public void refreshTimePaint(){
        this.postInvalidate(); //使用postInvalidate();刷新
        System.out.println(System.currentTimeMillis());
        System.out.println(nGameState);
    }

 

 

这样View就能自动刷新了~~~

 

这里有几个网页做参考:

http://stackoverflow.com/questions/522800/android-textview-timer

http://groups.google.com/group/android-developers/browse_thread/thread/5baf5a3eaa823b7b?pli=1

http://groups.google.com/group/android-developers/msg/f5765705b8c59d66

分享到:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值