栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Android view绘制流程机制

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Android view绘制流程机制

摘要:     你是否经常被Android千奇百怪的view所困扰,是否被各种动画所折磨,天下武功分内功和外功,内功讲究的是心法,会驱动外功,各式各样的界面设计就是外功,单学外功很难有质的突破,还需从内功攻克,本文将从view的内功开始,以Android view的根本需求为起点,站在设计者的角度,讲述了Android 系统view部分如何完成整个视图的绘制,结合view、viewgroup的组织结构,阐述整个流程机制,以期掌握Androidview部分的核心设计理念。 一、基本诉求         学习一个事物,首先要找到它的首要问题,view顾名思义,视图的意思,如何显示视图是它的首要问题,从需求来看,view要满足任意的显示需求,各式各样的界面需求都要满足,如何设计一套方案来满足复杂的需求,变的十分重要,该问题的特点是复杂性,界面千变万化,如何应对复杂性,能更好的管理起来,将是解决方案的重中之重。        可以借鉴其他领域的复杂问题来处理,比如如何管理一个公司,公司面对的事情各种各样,但公司的金字塔结构或者说树形结构,将各种事情不断的委托处理,分而治之。因此,采用树形结构是能满足问题的处理要求。所以,问题转化为如何通过树形结构,将显示需求一步步传递至子节点,各个子节点各自对上负责。每个子节点负责本节点的显示,然后组合起来就可以组装成各式各样的界面。        见下面摘自官网的图,可以发现整个结构正是树形结构,viewgroup代表容器,负责布局子view,view代表具体的界面绘制。所以针对千变万化的展示需求,只需构造一颗view树,处理各个结点的布局绘制。绘制主要是确定位置和内容,结合组织结构图,核心就是从viewgroup到view的分解转化,该步骤是降低问题复杂度的关键,将问题一步步分解为下级的问题,一步步简化问题的处理,直到绘制一个图形,绘制背景颜色这种最简单的处理 二、绘制问题分解转化        结合前面的分析,结合Android源码(主要是viewgroup和view两个类),系统最初提供给view树的是一个以左上角为原点,向右为x轴正方向,向下为y轴正方向的屏幕大小的坐标系;可完成绘制的canvas。要想正确绘制,前提是将view树完成各部分大小和位置的确定,对应于源码中的measure和layout,draw是最终目的。         结合viewgroup和view中draw部分,输出了下图二者的转化原理,viewgroup中的canvas会完成translate,将坐标原点从父view的左上角,转化到子view的左上角,同时会结合子view的大小,完成cliprect,将绘制范围圈定在子view的范围,如此对子view来说,它的canvas原点在左上角,绘制范围在自身大小,如此子view面对的绘制坐标系和父view面临的情况一致,转化了数学中常说的同一类问题,如此可循环嵌套。值得注意的是,Android中引入了srcoll滑动的概念,会影响该转化,见图中说明,只是在计算坐标系偏移量时需要考虑到滑动的距离,网上经常可见对该部分的说明“Android引入scroll主要是为了滑动内容”。  

具体到源码中measure、layout、draw部分的作用简述如下: measure:最终结果是set measured width和height,onmeasure接受到当前view的measurespe,该measurespc是如何确定的呢?参见viewGroup的measureChildWithMargins和getChildMeasureSpec代码,通过parent的spec能够给予的自由(随便、至多,只能),自身大小期望wrap还是match、已经用去的大小,决定自身的spec。   其中最顶层的view由于没有parent,是自己确定的,见viewrootimpl中的getRootMeasureSpec。另外要注意的是,该过程只测量大小,不涉及位置,比如在线性布局文件,width和height两个属性才跟大小有关,经常使用的gravity只跟位置有关 protected void measureChildWithMargins(View child, 
        int parentWidthMeasureSpec, int widthUsed, 
        int parentHeightMeasureSpec, int heightUsed) { 
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, 
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin 
                    + widthUsed, lp.width); 
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, 
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin 
                    + heightUsed, lp.height); 

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 

public static int getChildMeasureSpec(int spec, int padding, int childDimension) { 
    int specMode = MeasureSpec.getMode(spec); 
    int specSize = MeasureSpec.getSize(spec); 

    int size = Math.max(0, specSize - padding); 

    int resultSize = 0; 
    int resultMode = 0; 

    switch (specMode) { 
    // Parent has imposed an exact size on us 
    case MeasureSpec.EXACTLY: 
        if (childDimension >= 0) { 
            resultSize = childDimension; 
            resultMode = MeasureSpec.EXACTLY; 
        } else if (childDimension == LayoutParams.MATCH_PARENT) { 
            // Child wants to be our size. So be it. 
            resultSize = size; 
            resultMode = MeasureSpec.EXACTLY; 
        } else if (childDimension == LayoutParams.WRAP_CONTENT) { 
            // Child wants to determine its own size. It can't be 
            // bigger than us.
            resultSize = size; 
            resultMode = MeasureSpec.AT_MOST; 
        } 
        break; 

    // Parent has imposed a maximum size on us 
    case MeasureSpec.AT_MOST: 
        if (childDimension >= 0) { 
            // Child wants a specific size... so be it 
            resultSize = childDimension; 
            resultMode = MeasureSpec.EXACTLY; 
        } else if (childDimension == LayoutParams.MATCH_PARENT) { 
            // Child wants to be our size, but our size is not fixed. 
            // Constrain child to not be bigger than us.
            resultSize = size; 
            resultMode = MeasureSpec.AT_MOST; 
        } else if (childDimension == LayoutParams.WRAP_CONTENT) { 
            // Child wants to determine its own size. It can't be 
            // bigger than us.
            resultSize = size; 
            resultMode = MeasureSpec.AT_MOST; 
        } 
        break; 

    // Parent asked to see how big we want to be 
    case MeasureSpec.UNSPECIFIED: 
        if (childDimension >= 0) { 
            // Child wants a specific size... let him have it 
            resultSize = childDimension; 
            resultMode = MeasureSpec.EXACTLY; 
        } else if (childDimension == LayoutParams.MATCH_PARENT) { 
            // Child wants to be our size... find out how big it should 
            // be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; 
            resultMode = MeasureSpec.UNSPECIFIED; 
        } else if (childDimension == LayoutParams.WRAP_CONTENT) { 
            // Child wants to determine its own size.... find out how 
            // big it should be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; 
            resultMode = MeasureSpec.UNSPECIFIED; 
        } 
        break; 
    } 
    //noinspection ResourceType 
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 

private static int getRootMeasureSpec(int windowSize, int rootDimension) { 
    int measureSpec; 
    switch (rootDimension) { 

    case ViewGroup.LayoutParams.MATCH_PARENT: 
        // Window can't resize. Force root view to be windowSize. 
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); 
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT: 
        // Window can resize. Set max size for root view. 
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); 
        break;
    default: 
        // Window wants to be an exact size. Force root view to be that size. 
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); 
        break;
    } 
    return measureSpec; 

layout:在测量完大小的基础上,结合各边距、布局方式,设置view在parent中的位置left、top、right、bottom。 draw:由parent到child层级深入时,执行translate、clip操作,将坐标面向范围从viewgroup转化为view,同时考虑滑动偏移scroll,然后判断子view位置是否与对应rect相交,不相交则不显示,相交才显示。源码部分可见viewgroup和view中的draw部分,view里面有两个draw方法,其中一个参数较多的是parent来调用,另一个则被此方法调用              三、滑动相关      为何将滑动单独拿出来说呢,主要是滑动在Android中经常会遇到,像banner、viewpager以及各种动画,其实不管是滑动也好,动画也好,这些均属于view绘制的部分,只不过是动态多次绘制变化的view。如此,变化的view绘制转化为了一系列静态图像的绘制,动态问题转化为静态问题,问题复杂度得以降低,而且前面的分析得以利用上。而不至于将静态view的绘制和动态view的绘制割裂为两个问题看待。 简单分析系统常见view控件的处理如下: 1、listview滑动处理是通过改变child的top、bottom来改变的。 2、view的scroll改变的是内部的内容,是通过translate坐标来改变的,canvas会改变rect top、left位置,但大小不变 3、drwaerlayout 左滑式布局,采用offsetleftandright来进行滑动 4、setTranslationX 是11引入的,主要通过矩阵来变换,并且经过测试验证,不会触发ondraw方法 5、viewpager、scrollview 是通过内容滑动,也就是2来实现的     结合前面的canvas转化原理图,核心是改变绘制的内容,一共两种方法,要么原点坐标系改变,这样间接的导致绘制的内容改变;一个是改变绘制内容的坐标,原点坐标系不动。两种方法分别对应srcoll和改变child的坐标offsetleftandright。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/270348.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号