美文网首页
Android 使用SurfaceView实现动画

Android 使用SurfaceView实现动画

作者: gaookey | 来源:发表于2022-02-17 12:03 被阅读0次
    image.png

    View 的绘图机制存在如下缺陷

    • View 缺乏双缓冲机制。
    • 当程序需要更新 View 上的图片时,程序必须重绘 View上显示的整张图片。
    • 新线程无法直接更新 View 组件。

    SurfaceView 的绘图机制

    SurfaceView 一般会与 SurfaceHolder 结合使用,SurfaceHolder 用于向与之关联的 SurfaceView 上绘图,调用 SurfaceViewgetHolder() 方法即可获取 SurfaceView 关联的 SurfaceHolder

    SurfaceHolder 提供了如下方法来获取 Canvas 对象。

    • Canvas lockCanvasO 锁定整个 SurfaceView 对象,获取该 SurfaceView 上的 Canvas
    • Canvas lockCanvas(Rect dirty) 锁定 SurfaceViewRect 划分的区域,获取该区域上的 Canvas

    当对同一个 SurfaceView 调用上面两个方法时,两个方法所返回的是同一个 Canvas 对象。但当程序调用第二个方法获取指定区域的 Canvas 时,SurfaceView 将只对 Rect 所“圈”出来的区域进行更新,通过这种方式可以提高画面的更新速度。
    当通过 lockCanvas() 获取了指定 SurfaceView 上的 Canvas 之后,接下来程序就可以调用 Canvas 进行绘图了,Canvas 绘图完成后通过 unlockCanvasAndPost(canvas) 方法来释放绘图、提交所绘制的图形。
    需要指出的是,当调用 SurfaceHolderunlockCanvasAndPost() 方法之后,该方法之前所绘制的图形还处于缓冲区中,下一次 lockCanvas() 方法锁定的区域可能会“遮挡”它。

    小鱼游动
    public class FishView extends SurfaceView implements SurfaceHolder.Callback {
       private UpdateViewThread updateThread;
       private boolean hasSurface;
       private Bitmap back;
       private Bitmap[] fishs = new Bitmap[10];
       private int fishIndex; // 定义变量记录绘制第几张鱼的图片
       // 下面定义两个变量,记录鱼的初始位置
       private float initX;
       private float initY = 500f;
       // 下面定义两个变量,记录鱼的当前位置
       private float fishX;
       private float fishY = initY;
       private float fishSpeed = 12f; // 鱼的游动速度
       // 定义鱼游动的角度
       private int fishAngle = new Random().nextInt(60);
       Matrix matrix = new Matrix();
    
       public FishView(Context ctx, AttributeSet set) {
           super(ctx, set);
           // 获取该SurfaceView对应的SurfaceHolder,并将该类的实例作为其Callback
           getHolder().addCallback(this);
           back = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.fishbg);
           // 初始化鱼游动动画的10张图片
           for (int i = 0; i <= 9; i++) {
               try {
                   int fishId = (int) R.drawable.class.getField("fish" + i).get(null);
                   fishs[i] = BitmapFactory.decodeResource(ctx.getResources(), fishId);
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
       }
    
       private void resume() {
           // 创建和启动图像更新线程
           if (updateThread == null) {
               updateThread = new UpdateViewThread();
               if (hasSurface) updateThread.start();
           }
       }
    
       private void pause() {
           // 停止图像更新线程
           if (updateThread != null) {
               updateThread.requestExitAndWait();
               updateThread = null;
           }
       }
    
       // 当SurfaceView被创建时回调该方法
       public void surfaceCreated(SurfaceHolder holder) {
           initX = getWidth() + 50;
           fishX = initX;
           hasSurface = true;
           resume();
       }
    
       // 当SurfaceView将要被销毁时回调该方法
       public void surfaceDestroyed(SurfaceHolder holder) {
           hasSurface = false;
           pause();
       }
    
       // 当SurfaceView发生改变时回调该方法
       @Override
       public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
           if (updateThread != null) updateThread.onWindowResize(w, h);
       }
    
       class UpdateViewThread extends Thread {
           // 定义一个记录图像是否更新完成的旗标
           private boolean done;
    
           @Override
           public void run() {
               SurfaceHolder surfaceHolder = FishView.this.getHolder();
               // 重复绘图循环,直到线程停止
               while (!done) {
                   // 锁定SurfaceView,并返回到要绘图的Canvas
                   Canvas canvas = surfaceHolder.lockCanvas();  // ①
                   // 绘制背景图片
                   canvas.drawBitmap(back, 0f, 0f, null);
                   // 如果鱼“游出”屏幕之外,重新初始化鱼的位置
                   if (fishX < -100) {
                       fishX = initX;
                       fishY = initY;
                       fishAngle = new Random().nextInt(60);
                   }
                   if (fishY < -100) {
                       fishX = initX;
                       fishY = initY;
                       fishAngle = new Random().nextInt(60);
                   }
                   // 使用Matrix来控制鱼的旋转角度和位置
                   matrix.reset();
                   matrix.setRotate(fishAngle);
                   fishX -= fishSpeed * Math.cos(Math.toRadians(fishAngle));
                   fishY -= fishSpeed * Math.sin(Math.toRadians(fishAngle));
                   matrix.postTranslate(fishX, fishY);
                   canvas.drawBitmap(fishs[fishIndex++ % fishs.length], matrix, null);
                   // 解锁Canvas,并渲染当前图像
                   surfaceHolder.unlockCanvasAndPost(canvas);  // ②
                   try {
                       Thread.sleep(60);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
           }
    
           void requestExitAndWait() {
               // 把这个线程标记为完成,并合并到主程序线程中
               done = true;
               try {
                   join();
               } catch (InterruptedException ex) {
                   ex.printStackTrace();
               }
           }
    
           void onWindowResize(int w, int h) {
               // 处理SurfaceView的大小改变事件
               System.out.println("w:" + w + "===h:" + h);
           }
       }
    }
    

    实例:基于 SurfaceView 开发示波器

    对于 View 组件,如果程序需要花较长的时间来更新绘图,那么主 UI 线程将会被阻塞,无法响应用户的任何动作;而 SurfaceHlolder 则会启用新的线程去更新 SurfaceView 的绘制,因此不会阻塞主 UI 线程。

    MainActivity

    public class MainActivity extends AppCompatActivity {
        private SurfaceHolder holder;
        private Paint paint = new Paint();
        int HEIGHT = 400;
        int screenWidth;
        int X_OFFSET = 5;
        int cx = X_OFFSET;
        // 实际的Y轴的位置
        int centerY = HEIGHT / 2;
        Timer timer = new Timer();
        TimerTask task;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            DisplayMetrics metrics = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(metrics);
            screenWidth = metrics.widthPixels;
            SurfaceView surface = findViewById(R.id.show);
            // 初始化SurfaceHolder对象
            holder = surface.getHolder();
            paint.setColor(Color.GREEN);
            paint.setStrokeWidth(getResources().getDimension(R.dimen.stroke_width));
            Button sin = findViewById(R.id.sin);
            Button cos = findViewById(R.id.cos);
            View.OnClickListener listener = source -> {
                drawBack(holder);
                cx = X_OFFSET;
                if (task != null) {
                    task.cancel();
                }
                task = new TimerTask() {
                    @Override
                    public void run() {
                        paint.setColor(Color.GREEN);
                        int cy = source.getId() == R.id.sin ? centerY
                                - (int) (100 * Math.sin((cx - 5) * 2
                                * Math.PI / 150))
                                : centerY - (int) (100 * Math.cos((cx - 5)
                                * 2 * Math.PI / 150));
                        Canvas canvas = holder.lockCanvas(new Rect(cx, cy,
                                cx + (int) paint.getStrokeWidth(), cy + (int) paint.getStrokeWidth()));
                        canvas.drawPoint(cx, cy, paint);
                        cx += 2;
                        if (cx > screenWidth) {
                            task.cancel();
                            task = null;
                        }
                        holder.unlockCanvasAndPost(canvas);
                    }
                };
                timer.schedule(task, 0, 10);
            };
            sin.setOnClickListener(listener);
            cos.setOnClickListener(listener);
            holder.addCallback(new SurfaceHolder.Callback() {
                @Override
                public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                    drawBack(holder);
                }
    
                @Override
                public void surfaceCreated(SurfaceHolder myHolder) {
                }
    
                @Override
                public void surfaceDestroyed(SurfaceHolder holder) {
                    timer.cancel();
                }
            });
        }
    
        private void drawBack(SurfaceHolder holder) {
            Canvas canvas = holder.lockCanvas();
            // 绘制白色背景
            canvas.drawColor(Color.WHITE);
            paint.setColor(Color.BLACK);
            // 绘制坐标轴
            canvas.drawLine(X_OFFSET, centerY,
                    screenWidth, centerY, paint);
            canvas.drawLine(X_OFFSET, 40f, X_OFFSET, HEIGHT, paint);
            holder.unlockCanvasAndPost(canvas);
            holder.lockCanvas(new Rect(0, 0, 0, 0));
            holder.unlockCanvasAndPost(canvas);
        }
    }
    

    layout/activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="horizontal">
    
            <Button
                android:id="@+id/sin"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/sin" />
    
            <Button
                android:id="@+id/cos"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/cos" />
        </LinearLayout>
    
        <SurfaceView
            android:id="@+id/show"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center" />
    
    </LinearLayout>
    

    values/dimens.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <dimen name="stroke_width">3dp</dimen>
    </resources>
    
    image.png

    摘抄至《疯狂Android讲义(第4版)》

    相关文章

      网友评论

          本文标题:Android 使用SurfaceView实现动画

          本文链接:https://www.haomeiwen.com/subject/tlfylrtx.html