赞
踩
前面系列文章总结了Paint 的相关知识,图形绘制中另一个十分重要的对象Canvas也是需要我们去重点掌握的,在Android无论是绘制图像还是控件都离不开Canvas,而进行绘制则需要坐标体系作为参照,那么接下来这篇文章就进行Canvas和坐标体系的相关总结。
相关文章链接如下:
如果把Android绘画当成现实中的画家作画,Paint是画家手中的“画笔”保存了绘制的“色彩和笔刷”,Canvas自然就是画家笔下的画板,而画家自然就是GPU(由Framework 层通过JNI去调用),在现实生活中画家可以自主决定从哪个点开始起笔,又延伸到哪点,而在机器世界里都是需要去一系列的逻辑计算的,因而图形坐标系(即在Canvas去具体绘制图形的位置叫做坐标系)应运而生,而在Android Canvas中存在两种坐标系概念::Android坐标系(Canvas自己的坐标系)和视图坐标系(绘制坐标系)。
Android坐标系可以看成是物理存在的坐标系,也可以理解为绝对坐标,是由Surface创建出来的矩形区域决定的,看成最外层面板的位置,就是以屏幕的左上角是坐标系统原点(0,0),原点向右延伸是X轴正方向,原点向下延伸是Y轴正方向,准确地来说是以最顶层View的左上角为原点,而Canvas 默认的大小就为屏幕分辨率的大小,所以相当于是屏幕的左上角,Android坐标系是唯一的且一经确定不能修改,比如系统的getLocationOnScreen(int[] location)实际上获取Android坐标系中位置(即该View左上角在Android坐标系中的坐标),还有getRawX()、getRawY()获取的坐标也是Android坐标系的坐标。
视图坐标系是相对坐标系,绘制过程是以父视图为参照物,可以修改但过程不可逆,以父视图的左上角为坐标原点(0,0),原点向右延伸是X轴正方向,原点向下延伸是Y轴正方向,getX()、getY()就是获取视图坐标系下的坐标。
子View的存在是依附于父View的,所以用的是相对坐标来表示,如下方法可以获得子View到其父View(ViewGroup)的距离:
无论是View还是ViewGroup,Touch事件都会经由onTouchEvent(MotionEvent event)方法来处理,通过MotionEvent实例event可以获取相关坐标信息。
如果在Activity的OnCreate()事件调用这些方法,那么输出那些参数全为0,必须要等UI控件都加载完了才能获取到。
getLocalVisibleRect() :返回一个填充的Rect对象, 所有的View都是以一块矩形内存空间存在的
getGlobalVisibleRect() :获取Android坐标系的一个视图区域, 返回一个填充的Rect对象且该Rect是基于总整个屏幕的
getLocationOnScreen :计算该视图在Android坐标系中的x,y值,获取在当前屏幕内的绝对坐标
(这个值是要从屏幕顶端算起,当然包括了通知栏和状态栏的高度)
getLocationInWindow ,计算该视图在它所在的widnow的坐标x,y值,获取在整个window的绝对坐标
int[] location = new int[2];
view.getLocationOnScreen(location);
int x = location[0];
int y = location[1];
在Google官方文档中是这样介绍Canvas 的(The Canvas class holds the “draw” calls),虽然字面意思翻译为画布,但是本质上来说还是与我们现实中的画布有所区别的。首先画布并不是绘制的具体执行者,而是一个传递绘制信息的封装工具类,因为Android的2D绘制工作的核心流程是把绘制的信息保存到Canvas里,Framework层通过JNI 调用C/C++代码传递到openGL,再由openGL 传递给GPU,最终由GPU去完成真正的绘制,所以也可以理解为用于与底层通信的“绘制会话”。
要进行2D绘制,无论是系统控件还是自定义View都离不开Canvas,当然还有以下三大角色:
Canvas决定了图形绘制的位置、形状;而Paint决定了其对应的色彩和样式。
涉及到到Android 源码部分的,皆经过了精简,只保留了与Canvas有关的重要源码,另外在Android Studio中可以通过快键键Ctrl+Shift+N快速查找SDK中的源码文件。
从源码角度上来看Canvas 是由native层分配到Surface中的一块初始大小为屏幕分辨率的矩形绘制区域,(即我们所有的绘制都是在这个区域之内),完成了测量、布局工作之后就开始进行绘制工作,我们先后往前推,首先从ViewRootImpl的performTraversals方法遍历ViewTree开始
Surface——Handle onto a raw buffer that is being managed by the screen compositor.
private void performTraversals() { // cache mView since it is used so much below... final View host = mView; WindowManager.LayoutParams lp = mWindowAttributes; boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw); if (layoutRequested) { final Resources res = mView.getContext().getResources(); if (mFirst) { ... mAttachInfo.mInTouchMode = !mAddedTouchMode; ensureTouchModeLocally(mAddedTouchMode); } else { if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) { insetsChanged = true; } if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { windowSizeMayChange = true; if (shouldUseDisplaySize(lp)) { // NOTE -- system code, won't try to do compat mode. Point size = new Point(); mDisplay.getRealSize(size); desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else { Configuration config = res.getConfiguration(); desiredWindowWidth = dipToPx(config.screenWidthDp); desiredWindowHeight = dipToPx(config.screenHeightDp); } } } ... // Ask host how big it wants to be windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight); } if (mSurfaceHolder != null) { // The app owns the surface; tell it about what is going on. if (mSurface.isValid()) { mSurfaceHolder.mSurface = mSurface; } mSurfaceHolder.setSurfaceFrameSize(mWidth, mHeight); mSurfaceHolder.mSurfaceLock.unlock(); if (mSurface.isValid()) { if (!hadSurface) { mSurfaceHolder.ungetCallbacks(); mIsCreating = true; SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { c.surfaceCreated(mSurfaceHolder); } } surfaceChanged = true; } if (surfaceChanged || surfaceGenerationId != mSurface.getGenerationId()) { SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { c.surfaceChanged(mSurfaceHolder, lp.format, mWidth, mHeight); } } } mIsCreating = false; } else if (hadSurface) { mSurfaceHolder.ungetCallbacks(); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { c.surfaceDestroyed(mSurfaceHolder); } } mSurfaceHolder.mSurfaceLock.lock(); try { mSurfaceHolder.mSurface = new Surface(); } finally { mSurfaceHolder.mSurfaceLock.unlock(); } } } ... final ThreadedRenderer threadedRenderer = mAttachInfo.mThreadedRenderer; if (!mStopped || mReportNextDraw) { if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) { int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); // Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); int width = host.getMeasuredWidth(); int height = host.getMeasuredHeight(); if (lp.horizontalWeight > 0.0f) { width += (int) ((mWidth - width) * lp.horizontalWeight); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); measureAgain = true; } if (measureAgain) { //再次执行绘制 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } layoutRequested = true; } } } if (!cancelDraw && !newSurface) { //!!!执行绘制!!! performDraw(); } else { if (isViewVisible) { // Try again scheduleTraversals(); } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).endChangingAnimations(); } mPendingTransitions.clear(); } } ... }
在遍历ViewTree的方法内部会执行ViewRootImpl的performDraw方法,
private void performDraw() { if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) { return; } else if (mView == null) { return; } try { ///执行绘制 boolean canUseAsync = draw(fullRedrawNeeded); if (usingAsyncReport && !canUseAsync) { mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null); usingAsyncReport = false; } } finally { mIsDrawing = false; } ... }
在performDraw内部调用ViewRootImpl的draw方法,在draw方法内部首先初始化Surface(在ViewRootImpl加载时就首先通过new 创建Surface对象)
private boolean draw(boolean fullRedrawNeeded) { //在ViewRootImpl加载时就首先通过new 创建Surface对象 Surface surface = mSurface; if (!surface.isValid()) { return false; } scrollToRectOrFocus(null, false); ... if (mAttachInfo.mViewScrollChanged) { mAttachInfo.mViewScrollChanged = false; mAttachInfo.mTreeObserver.dispatchOnScrollChanged(); } ///通过new 创建出对应的实例,并用屏幕分辨率进行初始化{@link mDirty.set(0, 0, mWidth, mHeight);} final Rect dirty = mDirty; if (mSurfaceHolder != null) { // The app owns the surface, we won't draw. dirty.setEmpty(); if (animating && mScroller != null) { mScroller.abortAnimation(); } return false; } if (fullRedrawNeeded) { mAttachInfo.mIgnoreDirtyState = true; dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); } mAttachInfo.mTreeObserver.dispatchOnDraw(); boolean accessibilityFocusDirty = false; final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable; if (drawable != null) { final Rect bounds = mAttachInfo.mTmpInvalRect; final boolean hasFocus = getAccessibilityFocusedRect(bounds); if (!hasFocus) { bounds.setEmpty(); } } ... mAttachInfo.mDrawingTime = mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS; boolean useAsyncReport = false; if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) { if (invalidateRoot) { mAttachInfo.mThreadedRenderer.invalidateRoot(); } dirty.setEmpty(); final boolean updated = updateContentDrawBounds(); if (mReportNextDraw) { mAttachInfo.mThreadedRenderer.setStopped(false); } if (updated) { requestDrawWindow(); } useAsyncReport = true; // draw(...) might invoke post-draw, which might register the next callback already. final FrameDrawingCallback callback = mNextRtFrameCallback; mNextRtFrameCallback = null; mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback); } else { if (mAttachInfo.mThreadedRenderer != null && !mAttachInfo.mThreadedRenderer.isEnabled() && mAttachInfo.mThreadedRenderer.isRequested() && mSurface.isValid()) { try { mAttachInfo.mThreadedRenderer.initializeIfNeeded( mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets); } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return false; } mFullRedrawNeeded = true; scheduleTraversals(); return false; } ///第一次执行时候,调用这个方法 if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) { return false; } } } return useAsyncReport; }
并把Surface对象传递至ViewRootImpl的drawSoftware方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets) { // Draw with software renderer. //通过软件渲染器进行绘图 final Canvas canvas; int dirtyXOffset = xoff; int dirtyYOffset = yoff; if (surfaceInsets != null) { dirtyXOffset += surfaceInsets.left; dirtyYOffset += surfaceInsets.top; } try { dirty.offset(-dirtyXOffset, -dirtyYOffset); final int left = dirty.left; final int top = dirty.top; final int right = dirty.right; final int bottom = dirty.bottom; ///创建并初始化Canvas canvas = mSurface.lockCanvas(dirty); // TODO: Do this in native canvas.setDensity(mDensity); } catch (Surface.OutOfResourcesException e) { handleOutOfResourcesException(e); return false; } catch (IllegalArgumentException e) { mLayoutRequested = true; // ask wm for a new surface next time. return false; } finally { dirty.offset(dirtyXOffset, dirtyYOffset); // Reset to the original value. } try { if (!canvas.isOpaque() || yoff != 0 || xoff != 0) { canvas.drawColor(0, PorterDuff.Mode.CLEAR); } dirty.setEmpty(); try { canvas.translate(-xoff, -yoff); if (mTranslator != null) { mTranslator.translateCanvas(canvas); } canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); //调用View的draw方法 mView.draw(canvas); drawAccessibilityFocusedDrawableIfNeeded(canvas); } finally { if (!attachInfo.mSetIgnoreDirtyState) { // Only clear the flag if it was not set during the mView.draw() call attachInfo.mIgnoreDirtyState = false; } } } finally { try { surface.unlockCanvasAndPost(canvas); } catch (IllegalArgumentException e) { mLayoutRequested = true; // ask wm for a new surface next time. //noinspection ReturnInsideFinallyBlock return false; } } return true; }
在这个方法内部通过Surface的lockCanvas方法(对应的是Surface层的nativeLockCanvas方法)创建并初始化Canvas,简单来说就是在Surface中分配了一个预订的矩形区域。
public Canvas lockCanvas(Rect inOutDirty)
throws Surface.OutOfResourcesException, IllegalArgumentException {
synchronized (mLock) {
checkNotReleasedLocked();
if (mLockedObject != 0) {
throw new IllegalArgumentException("Surface was already locked");
}
///真正创建并初始化Canvas
mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
return mCanvas;
}
}
Canvas的绘图坐标系并不是唯一不变的,它与Canvas的Matrix(3x3)有关系,当对应的Matrix发生改变的时候,绘图坐标系也随之进行对应的变换, 而且这个过程是不可逆的,可以借助save和restore方法来保存和还原变化操,Matrix又是通过我们设置translate、rotate、scale、skew值来进行改变的。
绘图坐标系底层是通过矩阵乘法运算的。
Canvas从底层被创建时就默认构建了一个图层,save之前所有的操作都是在这个图层上进行绘制的,而save作用是将之前的所有已绘制的图像保存起来,让后续的操作就好像在一个新的图层上操作一样。比如你可以先保存目前画纸的位置(save),然后旋转90度,向下移动100像素后画一些图形
可以理解为合并图层操作,作用是将save()之后绘制的所有图像与sava()之前的图像进行合并。
改变绘图坐标系的操作本质上都是通过改变其对应的矩阵。
绘图矩阵的绘图坐标系移动是一个不可逆转的状态也就是说,一旦矩阵移动完成之后,那么他不能回到之前的位置,translate其实是把坐标系的原点坐标移动,比如说canvas.translate(200,200),则是把原点移动到原来(200,200)处,原点就是绘图的起点处。
rotate(float degrees)这个方法的旋转中心是坐标的原点,对绘图坐标系进行翻转。
虽然绘图坐标系的转换是一个不可逆转的过程,但是我们可通过save保存再通过restore进行恢复,其实我们在进行save操作时,就是在Canvas当中将我们save下来的坐标系保存到一个状态栈,执行restore或者是restoreToCount时再从状态栈中还原回来。简而言之,每一次的save操作本质上是把当前绘图坐标系入栈,而restore或者restoreToCount就是出栈的,通过save、 restore方法来保存和还原变换操作Matrix以及Clip剪裁。
/** * Auther: Crazy.Mo * DateTime: 2017/4/28 16:34 * Summary: */ public class ClockView extends View { private Context context; private Paint paintOutSide,paintDegree; private float outWidth,outHeight; public ClockView(Context context) { this(context, null); init(context); } public ClockView(Context context, AttributeSet attrs) { this(context, attrs,0); init(context); } public ClockView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context){ this.context=context; initOutSize(); } private void initOutSize(){ WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);//获取WM对象 DisplayMetrics dm = new DisplayMetrics(); manager.getDefaultDisplay().getMetrics(dm); outHeight=(float) dm.heightPixels;//获取真实屏幕的高度以px为单位 outWidth=(float)dm.widthPixels; } /** * 画外圈圆 * @param canvas */ private void drawOutCircle(Canvas canvas){ paintOutSide=new Paint(); paintOutSide.setColor(Color.GREEN); paintOutSide.setStyle(Paint.Style.STROKE); paintOutSide.setAntiAlias(true); paintOutSide.setDither(true); paintOutSide.setStrokeWidth(6f); canvas.drawCircle(outWidth/2.0f,outHeight/2.0f,outWidth/2.0f,paintOutSide); } /** * 画刻度 */ private void drawDegree(Canvas canvas){ paintDegree=new Paint(); paintDegree.setColor(Color.RED); paintDegree.setStyle(Paint.Style.STROKE); paintDegree.setAntiAlias(true); paintDegree.setDither(true); paintDegree.setStrokeWidth(3f); for(int i=0;i<24;i++){ if(i==0||i==6||i==12||i==18){ paintDegree.setStrokeWidth(6f); paintDegree.setTextSize(30); canvas.drawLine(outWidth/2.0f,(outHeight/2.0f-outWidth/2.0f),outWidth/2.0f,(outHeight/2.0f-outWidth/2.0f+60),paintDegree); String degreeTxt=String.valueOf(i); canvas.drawText(degreeTxt,(outWidth/2-paintDegree.measureText(degreeTxt)/2),(outHeight/2-outWidth/2+90),paintDegree); }else { paintDegree.setStrokeWidth(4f); paintDegree.setTextSize(20); canvas.drawLine(outWidth/2.0f,(outHeight/2.0f-outWidth/2.0f),outWidth/2.0f,(outHeight/2.0f-outWidth/2.0f+40),paintDegree); String degreeTxt=String.valueOf(i); canvas.drawText(degreeTxt,(outWidth/2-paintDegree.measureText(degreeTxt)/2)+20,(outHeight/2-outWidth/2+40),paintDegree); } canvas.rotate(15,outWidth/2,outHeight/2); } } private void drawPointor(Canvas canvas){ Paint paintHour=new Paint(); paintHour.setColor(Color.RED); paintHour.setStyle(Paint.Style.STROKE); paintHour.setAntiAlias(true); paintHour.setDither(true); paintHour.setStrokeWidth(12f); Paint paintMin=new Paint(); paintMin.setColor(Color.RED); paintMin.setStyle(Paint.Style.STROKE); paintMin.setAntiAlias(true); paintMin.setDither(true); paintMin.setStrokeWidth(8f); canvas.save(); canvas.translate(outWidth/2,outHeight/2); canvas.drawLine(0,0,100,100,paintHour); canvas.drawLine(0,0,100,150,paintMin); canvas.restore(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawOutCircle(canvas); drawDegree(canvas); drawPointor(canvas); } }
与状态栈概念类似的还有一个Layer栈,Android中的绘图机制很多都是借鉴了Photoshop的概念,在Photoshop中一张原始的素材可能是由很多图层叠加而成,Android也借鉴了这一机制,所谓Layer图层其本质就是内存中一块矩形的区域,在Android中图层是基于栈的数据结果进行管理的,通过方法canvas.saveLayer或saveLayerAlpha来创建新的带有透明度的图层并且放入到图层栈中(离屏Bitmap-离屏缓冲),并且会将saveLayer之前的一些Canvas操作延续过来,后续的绘图操作都在新建的layer上面进行,出栈则是通过方法restore、restoreToCount,出入栈造成的操作区别是:入栈时所有的绘制操作都发生在当前这个图层,而出栈之后则会把操作绘制到上一个图层。
@Override
protected void onDraw(Canvas canvas) {
//相当于是默认绘制白色背景、蓝色圆在整个画布上,可以看成PS中的背景
canvas.drawColor(Color.WHITE);
paintOutSide.setColor(Color.BLUE);
canvas.drawCircle(100,100,100,paintOutSide);
canvas.saveLayerAlpha(0,0,400,400,125,ALL_SAVE_FLAG);//执行saveLayerAlpha 相当于是创建了一个新的图层绘制红色圆,其中125代表alpha值0~255,你可以尝试着修改透明值进行测试可以加深对于图层的理解
paintOutSide.setColor(Color.RED);
canvas.drawCircle(150,150,100,paintOutSide);
canvas.restore();
}
未完待续
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。