Android Canvas 与 View之间的关系

  大家都知道,Canvas是画布,开发者通过onDraw(Canvas canvas)方法,将View的具体content 画到 Canvas中,最后显示在屏幕上,但是有这么几个问题,一直困扰我好久: 1 什么是View的视图区域,其位置和大小是怎么决定的?  2  为什么View的Content可以无限大,是什么样的机制,保证将正确的内容显示屏幕上? 3 Canvas 是怎么来保存 View的具体内容的? 4 Canvas 的translate,rolate等变化是如何实现的?  接下来,将对上述几个问题,进行一一解答。

1)  什么是View的视图区域,其位置和大小是怎么决定的?

同一个窗口的视图层次树中的View节点,其onDraw中的Canvas参数都是同一个,是由该窗口的ViewRootImpl通过surface.lockCanvas获得,并把该Canvas传给根视图, 对于应用窗口来说,其根视图的Canvas大小一般为整个屏幕;

对于视图层次树中的View,通过 Measure 和 layout 两个过程之后,便可确定其大小和位置,视图在绘制的过程中,根据其mLeft, mRight, mTop, mButtom, mScrollX和mScrollY等参数,将父节点传过来的Canvas进行剪切操作,得到新的Canvas,该剪切区便是父节点分配给子节点的视图区域,其源代码如下:

   
         if ((flags & ViewGroup.FLAG_CLIP_CHILDREN) == ViewGroup.FLAG_CLIP_CHILDREN &&
                !useDisplayListProperties && cache == null) {
            if (offsetForScroll) {
                canvas.clipRect(sx, sy, sx + (mRight - mLeft), sy + (mBottom - mTop));
            } else {
                if (!scalingRequired || cache == null) {
                    canvas.clipRect(0, 0, mRight - mLeft, mBottom - mTop);
                } else {
                    canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
                }
            }
        }

Canvas之所以要这么设计的主要原因是,为了View的绘制,通过这种方式,使得View每次绘制是,都可以该视图的原点坐标作为绘制的原点坐标,而独立于其他View,使得绘制逻辑大大简化。


2)  View的Content可以无限大,是什么样的机制,保证将正确位置的内容显示屏幕上?

   在许多资料和博客上,都有提到,View的Content可以无限大,但是通过什么 样的机制,将正确位置的内容显示到屏幕中?

   从问题1中,咱们可知,每个View都在屏幕存在一个由父节点分配的可视区域,当View绘制的内容,超过了可视区域的内容,就不会显示,此时咱们可以通过scrollTo或者scrollBy方法,将不显示的内容,滚动到可视区域,下面将详细的解释下其原理:



首先来看图中,那黑色和红色的坐标系,其中黑色的为ViewGroup的坐标轴,即父节点的坐标轴;红色的为子节点的坐标轴,通过父节点对子节点的measure和layou的过程之后,并可以确定子节点坐标轴的原点坐标为<mLeft, mTop>(相对于父节点来说),然后子节点便可相对于其自身的坐标系来进行绘画,并将绘画在可视区域的内容显示在屏幕上;

当绘制的内容超过其可视区域是,便通过scrollTo或者scrollBy方法,将超过可视区域的内容,滚动到可视区域(mScrollX > 0时 向左滚动,mScrollY > 0时 向上滚动) ,其具体实现过程是:

   1) 记录具体的滚动位置(mScrollX mScrollY),并调用invalidate()方法,scrollTo和scrollBy作用存在差别,但其本质都是调用scrollTo;

   2) 由invalidate方法向上传递,并最终又触发了该View的这个方法

            boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) 

    在这个方法中,作了这么几个特殊处理:

1 将该坐标系移动到  <mLeft-mScrollX, mTop-mScrollY>,即图中的橙色坐标轴,其源代码如下:

if (offsetForScroll) {
            canvas.translate(mLeft - sx, mTop - sy);
        } else {
            if (!useDisplayListProperties) {
                canvas.translate(mLeft, mTop);
            }
            if (scalingRequired) {
                if (useDisplayListProperties) {
                    // TODO: Might not need this if we put everything inside the DL
                    restoreTo = canvas.save();
                }
                // mAttachInfo cannot be null, otherwise scalingRequired == false
                final float scale = 1.0f / mAttachInfo.mApplicationScale;
                canvas.scale(scale, scale);
            }
        }

      从代码中可知,是通过canvas.translate的方式,来移动坐标系,translate方法这个将在第4个问题来详细描述;

      2   在对父节点传过来的Canvas进行可视区域进行截取时,作了一些处理,其源代码如下:

  if (offsetForScroll) {
                canvas.clipRect(sx, sy, sx + (mRight - mLeft), sy + (mBottom - mTop));
            } else {
                if (!scalingRequired || cache == null) {
                    canvas.clipRect(0, 0, mRight - mLeft, mBottom - mTop);
                } else {
                    canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
                }
            }

从代码中可知,即使有滚动,但是其View的可视区域的位置和大小都没有更改,如图中,那个灰色的布局窗口;

     通过处理之后,最终又会调用View.onDraw方法,此时绘制相对的坐标系,并且更新后的坐标系,即图中的橙色坐标系,通过这种方式,将显示在可视区域的内容的坐标滚动了<scrollX, scrollY>;

   3 Canvas 是怎么来保存 View的具体内容的:

      Canvas本身只是一个工具类,它可以调用底层绘制驱动来绘制相应的图形,但真正保存图像的是Canvas中的Bitmap对象,Bitmap中有一个byte数组,用来存放[0~255]的像素值;

  4 Canvas 的translate,rolate等变化是如何实现的?

      Canvas中存在四种变换,及translate rolate Scale和Skew,本文将以rolate(旋转来进行讲解)。许多博客中,都把canvas rolate当作是 Canvas本身的旋转,其实这样的理解是有偏差的,真正旋转的并不是画布本身,而是将其坐标系(通过Matrix矩阵的变化)来进行旋转,下面将以图形结合代码来进行说明:

      

当调用Canvas.rolate(-45)时,是把绘制坐标逆时针旋转45°,但View的可视区域并不会更改。此时绘制的时候,会根据新的坐标系来进行绘制,所以最后显示在屏幕上的,就是两者的交集区域,下面是运行的真实效果,与预期一致:

其余几个变换也都是通过Matrix矩阵,来进行坐标系的调整,但是Canvas本身并没有更改,而且这种调整是不可逆的,但能通过canvas.save和canvas.restore来进行保存和恢复;


  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值