应用层绘制初学1

简介

由于工作原因所以从头研究应用层的绘制,所以这篇笔记是从应用层着手探究绘制的基本使用以及原理还有涉及图表的粗略设计方法。

核心类&方法

Canvas.class

Canvas介绍

Canvas是一个工具类,这个工具类的目的是提供一系列的绘制基础图形的API,通过Canvas内部的drawXX()方法可以将一些如:线条,圆,点以及方块的图形绘制在用户显示的界面中。当然Canvas既然已经设计出来,那么肯定有对应的拓展功能,就不单纯是画一些东西,还会有save(),restore()方法等,利用这些方法可以将一种状态进行保存,然后最后清楚这种状态之后的处理过程。

发现问题

Canvas绘制的内容要用什么作为载体,答案是:通过内存...可能这个答案是个废话,但是仔细想,在应用层什么对于绘制过程中的内存具有代表性质,想想可能发现经常因为Bitmap而造成内存泄露,所以Bitmap就是这里的内存代表。所以Canvas要将一些图形绘制到Bitmap中最后经过Surface进行显示。我们之前查看过Surface的源码,里面对应有缓冲区,所以这里讲Bitmap对应的内存数据需要进行拷贝到缓冲区中,然后进行显示。

Canvas基本变换

canvas.save();//锁画布(为了保存之前的画布状态)  
canvas.translate(10, 10);//把当前画布的原点移到(10,10),后面的操作都以(10,10)作为参照点,默认原点为(0,0)  
drawScene(canvas);  
canvas.restore();//把当前画布返回(调整)到上一个save()状态之前  
  
canvas.save();//锁画布(为了保存之前的画布状态)  
canvas.translate(160, 10);//把当前画布的原点移到(160,10),后面的操作都以(160,10)作为参照点,  
canvas.clipRect(10, 10, 90, 90);//这里的真实坐标为左上(170,170)、右下(250,250)  
canvas.clipRect(30, 30, 70, 70, Region.Op.DIFFERENCE);  
drawScene(canvas);  
canvas.restore();  

不要小看这几个方法,设计的点接下来一个个分析。

canvas.save()&canvas.restore()

canvas.save()这个方法就是之前说的将对应的状态进行保存,canvas.restore()这个方法将保存的状态进行清除。具体在哪里应用呢,比如我现在需要平移,然后绘制平移完了,之后需要回到平移之后的状态,这种情况就需要进行回滚。

canvas.translate()

这个方法是平移,在onDraw()的方法中需要做的就是根据自己的位置,对canvas进行坐标对应,一般情况下我们都使用到的坐标系是绘制控件本身与canva的坐标对应。但是如果我们不断变换坐标,不断进行绘制,这个时候我们需要对每一个绘制的图形进行参数位置的设置这样很麻烦,所以我们就想到了相对坐标,那么平移就会把上一次的坐标变成当前的坐标再一次以这个坐标为基准进行绘制。这样就方便很多。

drawScaen(canvas)

这个方法并不是Android API内部的方法,而是我在这里写的一个方法,写这个方法目的是一个设计的概念,我们想,我们不能每一次得到任何一个形状就在自定义的View.onDraw中直接绘制,这样重复的工作会很多。那么怎么弄,我们应该使用继承关系,将重复的工作 不断抽取,最后将drawScaen(canvas)单独放到一个类中,这个类是我们设计的图形或者一个界面。这样的话我们就很久java开发规范中继承关系,与拥有关系将图形的绘制解耦合。

绘制源码分析

ViewRootImpl.java

private void performDraw() {
    try {
        //进行绘制操作
        draw(fullRedrawNeeded);
    } finally {
        mIsDrawing = false;
    }
    ...
    }
}
    private void draw(boolean fullRedrawNeeded) {
            //surface用来操作应用窗口的绘图表面
            Surface surface = mSurface;
            if (surface == null || !surface.isValid()) {
                return;
            }

            if (!sFirstDrawComplete) {
                synchronized (sFirstDrawHandlers) {
                    sFirstDrawComplete = true;
                    for (int i=0; i<sFirstDrawHandlers.size(); i++) {
                        post(sFirstDrawHandlers.get(i));
                    }
                }
            }

            scrollToRectOrFocus(null, false);

            if (mAttachInfo.mViewScrollChanged) {
                mAttachInfo.mViewScrollChanged = false;
                mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
            }

            int yoff;
            //计算窗口是否处于滚动状态
            final boolean scrolling = mScroller != null && mScroller.computeScrollOffset();
            if (scrolling) {
                yoff = mScroller.getCurrY();
            } else {
                yoff = mScrollY;
            }
            if (mCurScrollY != yoff) {
                mCurScrollY = yoff;
                fullRedrawNeeded = true;
            }
            //描述窗口是否正在请求大小缩放
            float appScale = mAttachInfo.mApplicationScale;
            boolean scalingRequired = mAttachInfo.mScalingRequired;

            //描述窗口的脏区域,即需要重新绘制的区域
            Rect dirty = mDirty;
            if (mSurfaceHolder != null) {
                // The app owns the surface, we won't draw.
                dirty.setEmpty();
                return;
            }

            //用来描述是否需要用OpenGL接口来绘制UI,当应用窗口flag等于WindowManager.LayoutParams.MEMORY_TYPE_GPU
            //则表示需要用OpenGL接口来绘制UI
            if (mUseGL) {
                if (!dirty.isEmpty()) {
                    Canvas canvas = mGlCanvas;
                    if (mGL != null && canvas != null) {
                        mGL.glDisable(GL_SCISSOR_TEST);
                        mGL.glClearColor(0, 0, 0, 0);
                        mGL.glClear(GL_COLOR_BUFFER_BIT);
                        mGL.glEnable(GL_SCISSOR_TEST);

                        mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
                        mAttachInfo.mIgnoreDirtyState = true;
                        mView.mPrivateFlags |= View.DRAWN;

                        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
                        try {
                            canvas.translate(0, -yoff);
                            if (mTranslator != null) {
                                mTranslator.translateCanvas(canvas);
                            }
                            canvas.setScreenDensity(scalingRequired
                                    ? DisplayMetrics.DENSITY_DEVICE : 0);
                            mView.draw(canvas);
                            if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
                                mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
                            }
                        } finally {
                            canvas.restoreToCount(saveCount);
                        }

                        mAttachInfo.mIgnoreDirtyState = false;

                        mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
                        checkEglErrors();

                        if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
                            int now = (int)SystemClock.elapsedRealtime();
                            if (sDrawTime != 0) {
                                nativeShowFPS(canvas, now - sDrawTime);
                            }
                            sDrawTime = now;
                        }
                    }
                }

                //如果窗口处于滚动状态,则应用窗口需要马上进行下一次全部重绘,调用scheduleTraversals()方法
                if (scrolling) {
                    mFullRedrawNeeded = true;
                    scheduleTraversals();
                }
                return;
            }

            //是否需要全部重绘,如果是则将窗口的脏区域设置为整个窗口区域,表示整个窗口曲云都需要重绘
            if (fullRedrawNeeded) {
                mAttachInfo.mIgnoreDirtyState = true;
                dirty.union(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
            }

            if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                Log.v(TAG, "Draw " + mView + "/"
                        + mWindowAttributes.getTitle()
                        + ": dirty={" + dirty.left + "," + dirty.top
                        + "," + dirty.right + "," + dirty.bottom + "} surface="
                        + surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
                        appScale + ", width=" + mWidth + ", height=" + mHeight);
            }

            if (!dirty.isEmpty() || mIsAnimating) {
                Canvas canvas;
                try {
                    int left = dirty.left;
                    int top = dirty.top;
                    int right = dirty.right;
                    int bottom = dirty.bottom;
                    //调用Surface.lockCanvas()来创建画布
                    canvas = surface.lockCanvas(dirty);

                    if (left != dirty.left || top != dirty.top || right != dirty.right ||
                            bottom != dirty.bottom) {
                        mAttachInfo.mIgnoreDirtyState = true;
                    }

                    // TODO: Do this in native
                    canvas.setDensity(mDensity);
                } catch (Surface.OutOfResourcesException e) {
                    Log.e(TAG, "OutOfResourcesException locking surface", e);
                    // TODO: we should ask the window manager to do something!
                    // for now we just do nothing
                    return;
                } catch (IllegalArgumentException e) {
                    Log.e(TAG, "IllegalArgumentException locking surface", e);
                    // TODO: we should ask the window manager to do something!
                    // for now we just do nothing
                    return;
                }

                try {
                    if (!dirty.isEmpty() || mIsAnimating) {
                        long startTime = 0L;

                        if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                            Log.v(TAG, "Surface " + surface + " drawing to bitmap w="
                                    + canvas.getWidth() + ", h=" + canvas.getHeight());
                            //canvas.drawARGB(255, 255, 0, 0);
                        }

                        if (Config.DEBUG && ViewDebug.profileDrawing) {
                            startTime = SystemClock.elapsedRealtime();
                        }

                        // If this bitmap's format includes an alpha channel, we
                        // need to clear it before drawing so that the child will
                        // properly re-composite its drawing on a transparent
                        // background. This automatically respects the clip/dirty region
                        // or
                        // If we are applying an offset, we need to clear the area
                        // where the offset doesn't appear to avoid having garbage
                        // left in the blank areas.
                        if (!canvas.isOpaque() || yoff != 0) {
                            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
                        }

                        dirty.setEmpty();
                        mIsAnimating = false;
                        mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
                        mView.mPrivateFlags |= View.DRAWN;

                        if (DEBUG_DRAW) {
                            Context cxt = mView.getContext();
                            Log.i(TAG, "Drawing: package:" + cxt.getPackageName() +
                                    ", metrics=" + cxt.getResources().getDisplayMetrics() +
                                    ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
                        }
                        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
                        try {
                            canvas.translate(0, -yoff);
                            if (mTranslator != null) {
                                mTranslator.translateCanvas(canvas);
                            }
                            canvas.setScreenDensity(scalingRequired
                                    ? DisplayMetrics.DENSITY_DEVICE : 0);
                            mView.draw(canvas);
                        } finally {
                            mAttachInfo.mIgnoreDirtyState = false;
                            canvas.restoreToCount(saveCount);
                        }

                        if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
                            mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
                        }

                        if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
                            int now = (int)SystemClock.elapsedRealtime();
                            if (sDrawTime != 0) {
                                nativeShowFPS(canvas, now - sDrawTime);
                            }
                            sDrawTime = now;
                        }

                        if (Config.DEBUG && ViewDebug.profileDrawing) {
                            EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
                        }
                    }

                } finally {
                    //UI绘制完成后,调用urface.unlockCanvasAndPost(canvas)S来请求SurfaceFlinger进行UI的渲染
                    surface.unlockCanvasAndPost(canvas);
                }
            }

            if (LOCAL_LOGV) {
                Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");
            }

            if (scrolling) {
                mFullRedrawNeeded = true;
                scheduleTraversals();
            }
        }
}
  • 调用Scroller.computeScrollOffset()方法计算应用是否处于滑动状态,并获得应用窗口在Y轴上的即时滑动位置yoff。
  • 根据AttachInfo里描述的数据,判断窗口是否需要缩放。
  • 根据成员变量React mDirty的描述来判断窗口脏区域的大小,脏区域指的是需要全部重绘的窗口区域。
  • 根据成员变量boolean mUserGL判断是否需要用OpenGL接口来绘制UI,当应用窗口flag等于WindowManager.LayoutParams.MEMORY_TYPE_GPU则表示需要用OpenGL接口来绘制UI.
  • 如果不是用OpenGL来绘制,则用Surface来绘制,先调用Surface.lockCanvas()来创建画布,UI绘制完成后,再调用urface.unlockCanvasAndPost(canvas)S来请求SurfaceFlinger进行UI的渲染

其实我们的核心有以下几个:

  • 通过Surface.lockCanvas()创建画布,所以我们应该在Surface中寻找答案,这个我们以后肯定会分析,但是目前是应用层,先把上层大概分析一下,要不然拿不动工作我就得走人了。
  • 在一个就是通过drawSoftware()方法进行软绘制,这个就是不开启硬件加速时候走的流程。

我们在压缩这个小结:

  • 绘制背景
  • 调用onDraw绘制自身内容
  • 调用dispatchDraw()绘制子控件
  • 绘制滚动条

接下来就是我们自定义绘制了,也是应用层那些图表的核心,所以今天吓得我赶紧看github上有没有开源的项目解解渴。还真有!

后续

今天运气巨好无比,导入人家的代码都使劲给我报错。我现在就把这些错分享出来

  • Android Studio出现Error:No service of type Factory available in ProjectScopeServices.
定位到根目录的build.gradle

classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
换成
1.4.1
原因是啥,原因是有人给google提了一个pach...
  • 还会报哪个错误
将导入的依赖项目中android{},dependencies{},apply plugin:,对应的其他东西都删掉,对没错删掉。
  • apply plugin: 'com.jfrog.bintray'找不到这个
找不到,如果项目中没用的话,直接注释掉
//apply plugin: 'com.jfrog.bintray'

今天下午大概看了一下人家写的流程,就是将View继承到底,一层包一层,将Canvas分离到每一个细节类中,这里的细节类就是那些饼状图,圆柱图什么的,它是每一种对应一个类。然后对数据也进行了封装,过程中使用了高内聚,使用变量比较多,然后都是进行设置。所以感觉人家的架构能力很好,尔等渣渣只能膜拜效仿,但是相信不出这个国庆,我也能看个八九不离五六哈哈,日后也写一个框架,不求功能多,只求架构好。

先给大家几张截图

821632408278798440.jpg
862654002375029744.png
275535549757258413.jpg
537191386133571491.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容