androiddraw
❶ 如何使用DroidDraw进行Android界面设计
1、DroidDraw是一个GUI可视化设计器,基于是基于Java Swing的Android界面设计器,用它来编辑android界面,非常直观、方便,为开发者节约很多时间。我们可以用它生成繁杂的layout文件(XML文件),设计完以后可以导出xml文件,然后复制到我们的工程中使用。
使用方法很简单,首先下载该软件,运行它,然后软件点击响应组件,通过拖拽来调整大小,生成文件保存成为XML,复制到您的Android工程中即可。
2、AnDroidDraw是一个与DroidDraw集成的Android应用程序,它允许你从DroidDraw应用程序下载你的GUIs,也允许你在一个Android设备上预览你的GUIs。
它其实是一个android应用的安装包apk文件,下载下来的文件AnDroidDraw.apk,然后要安装到模拟器上(这里一定要保证Android环境已搭建好,SDK和ADT配置好),步骤如下:
(1)在cmd下启动模拟器
启动模拟器的命令是命令行中输入emulator @自定义的模拟器名称
如果你也出先这样的问题,请先检查你的系统环境变量是否配置正确。
(2)使用 adb install AnDroidDraw.apk命令把它安装到你的 Android 设备上
(3)安装完此apk文件以后,打开模拟器,就可以看到模拟器上的该应用图标。
(4)安装一个端口转发规则: adb forward tcp:6100
(5)你的 Android 设备上运行 AnDroidDraw
(6)在电脑上运行 DroidDraw ,并且创建一个 GUI ,接下来从 DroidDraw 菜单中选择 "Project"->"Send GUI to Deviec",就是将设计出来的xml发送给模拟器正在运行的AnDroidDraw程序,如果发送成功会弹出“Upload succeeded”消息框。
(7)Android 屏幕上看到你在DroidDraw创建的 GUI 的的xml
❷ Android绘图基础--Canvas和Drawable
Github链接
画2D图形有两种方法:
Canvas实际上是封装了各种draw方法的类,调用draw方法把图形绘制到底层的Surface上,即绘制在Window上。
这个例子中构造了两个Canvas和一个Bitmap,分别调用其draw方法,先是mCanvas往Bitmap里绘制一个方块,再在onDraw方法内调用canvas.drawBitmap绘制这个方块。
思考一个问题,为什么mCanvas需要设置Bitmap?
很简单,因为它没有持有一块内存地址,自然没法绘制。来看一下draw的起点ViewRootImpl(软件绘制,不开启硬件加速下)。
这个通过mSurface.lockCanvas返回的Canvas是View.draw的canvas变量,所以当1,2情况时,Canvas都持有一个Bitmap,指向共享内存里的某一小块,当调用Canvas.draw方法时就能绘制出东西。但对于自定义Canvas来说并不是,即使设置一个Bitmap和绘制了Bitmap,但不往共享内存上写,屏幕上是不会显示的,SurfaceView同理,通过Surface.lockCanvas获取持有共享内存的Canvas,绘制完毕后调用Surface.unlockCanvasAndPost把绘制内容显示到surface上并release掉Canvas。
顺带一提Canvas.save和Canvas.restore方法,如下Demo
效果图如
画的是三个颜色和旋转角度都不同的小方形。
步骤1把默认坐标系旋转20°,画出第一个蓝色的方形,步骤2保存当前的matrix(旋转了20°),继续旋转20°,此时坐标系已经旋转了40°,画出第二个黄色的方块,步骤3,恢复上一步保存的matrix(旋转了20°),此时坐标系还是旋转了20°,步骤4,再旋转40°,此时坐标系旋转了60°,画出第三个黑色方块。
Canvas.save用于保存当前matrix和clip,Canvas.restore用于恢复上次保存的matrix和clip。
Drawable是一个能画出来的物体的抽象,使用前需要调用setBounds确定位置和大小,通过getIntrinsicHeight和getIntrinsicWidth取到实际大小。Drawable可以有几种形式存在:Bitmap、Nine Patch、Vector、Shape、Layers等。
从Resource.getDrawable会判断是否.xml结尾,不是的话走6,7步,如果从xml中读取,需要getResource.getDrawable -> ResourceImpl.loadDrawableForCookie -> drawable.createFromXml -> DrawableInflater.inflateFromXmlForDensity -> drawable.inflateFromTag
看一下Shape实现类GradientDrawable的inflate实现,读取各项属性并赋值,到draw方法。
调用canvas.drawRect把mRect画出来,而mRect的赋值在ensureValidRect。[图片上传失败...(image-a25af0-1515826613001)]
bounds在哪里设置的?答案是ImageView.updateDrawable内,会调用Drawable.getIntrinsicHeight赋值(从xml中size属性读取),再调用configureBounds -> setBounds,如果使用的不是ImageView,一定要在draw之前 调用setBounds ,否则size就会出错。
回到loadDrawableForCookie,再看一下6,7步加载图片的过程,通过AssetManager读取图片流数据,通过Drawable.createFromResourceStream这个我们经常使用的方法获取到Drawable。
取到屏幕密度之后调用BitmapFactory.decodeResourcesStream,计算密度后调用native创建Bitmap,感兴趣的同学可以看下更具体的分析文章(如 理解Bitmap )。
本文探究了两点
Android 7.1.1 源码
Android 官方文档, Canvas and Drawable , Drawable 等
❸ Android UI绘制之View绘制的工作原理
这是AndroidUI绘制流程分析的第二篇文章,主要分析界面中View是如何绘制到界面上的具体过程。
ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View的三大流程均是通过 ViewRoot 来完成的。在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRootImpl 对象,并将 ViewRootImpl 对象和 DecorView 建立关联。
measure 过程决定了 View 的宽/高, Measure 完成以后,可以通过 getMeasuredWidth 和 getMeasuredHeight 方法来获取 View 测量后的宽/高,在几乎所有的情况下,它等同于View的最终的宽/高,但是特殊情况除外。 Layout 过程决定了 View 的四个顶点的坐标和实际的宽/高,完成以后,可以通过 getTop、getBottom、getLeft 和 getRight 来拿到View的四个顶点的位置,可以通过 getWidth 和 getHeight 方法拿到View的最终宽/高。 Draw 过程决定了 View 的显示,只有 draw 方法完成后 View 的内容才能呈现在屏幕上。
DecorView 作为顶级 View ,一般情况下,它内部会包含一个竖直方向的 LinearLayout ,在这个 LinearLayout 里面有上下两个部分,上面是标题栏,下面是内容栏。在Activity中,我们通过 setContentView 所设置的布局文件其实就是被加到内容栏中的,而内容栏id为 content 。可以通过下面方法得到 content:ViewGroup content = findViewById(R.android.id.content) 。通过 content.getChildAt(0) 可以得到设置的 view 。 DecorView 其实是一个 FrameLayout , View 层的事件都先经过 DecorView ,然后才传递给我们的 View 。
MeasureSpec 代表一个32位的int值,高2位代表 SpecMode ,低30位代表 SpecSize , SpecMode 是指测量模式,而 SpecSize 是指在某种测量模式下的规格大小。
SpecMode 有三类,如下所示:
UNSPECIFIED
EXACTLY
AT_MOST
LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。
对于顶级View,即DecorView和普通View来说,MeasureSpec的转换过程略有不同。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同确定;
对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams共同决定;
MeasureSpec一旦确定,onMeasure就可以确定View的测量宽/高。
小结一下
当子 View 的宽高采用 wrap_content 时,不管父容器的模式是精确模式还是最大模式,子 View 的模式总是最大模式+父容器的剩余空间。
View 的工作流程主要是指 measure 、 layout 、 draw 三大流程,即测量、布局、绘制。其中 measure 确定 View 的测量宽/高, layout 确定 view 的最终宽/高和四个顶点的位置,而 draw 则将 View 绘制在屏幕上。
measure 过程要分情况,如果只是一个原始的 view ,则通过 measure 方法就完成了其测量过程,如果是一个 ViewGroup ,除了完成自己的测量过程外,还会遍历调用所有子元素的 measure 方法,各个子元素再递归去执行这个流程。
如果是一个原始的 View,那么通过 measure 方法就完成了测量过程,在 measure 方法中会去调用 View 的 onMeasure 方法,View 类里面定义了 onMeasure 方法的默认实现:
先看一下 getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 方法的源码:
可以看到, getMinimumWidth 方法获取的是 Drawable 的原始宽度。如果存在原始宽度(即满足 intrinsicWidth > 0),那么直接返回原始宽度即可;如果不存在原始宽度(即不满足 intrinsicWidth > 0),那么就返回 0。
接着看最重要的 getDefaultSize 方法:
如果 specMode 为 MeasureSpec.UNSPECIFIED 即未指定模式,那么返回由方法参数传递过来的尺寸作为 View 的测量宽度和高度;
如果 specMode 不是 MeasureSpec.UNSPECIFIED 即是最大模式或者精确模式,那么返回从 measureSpec 中取出的 specSize 作为 View 测量后的宽度和高度。
看一下刚才的表格:
当 specMode 为 EXACTLY 或者 AT_MOST 时,View 的布局参数为 wrap_content 或者 match_parent 时,给 View 的 specSize 都是 parentSize 。这会比建议的最小宽高要大。这是不符合我们的预期的。因为我们给 View 设置 wrap_content 是希望View的大小刚好可以包裹它的内容。
因此:
如果是一个 ViewGroup,除了完成自己的 measure 过程以外,还会遍历去调用所有子元素的 measure 方法,各个子元素再递归去执行 measure 过程。
ViewGroup 并没有重写 View 的 onMeasure 方法,但是它提供了 measureChildren、measureChild、measureChildWithMargins 这几个方法专门用于测量子元素。
如果是 View 的话,那么在它的 layout 方法中就确定了自身的位置(具体来说是通过 setFrame 方法来设定 View 的四个顶点的位置,即初始化 mLeft , mRight , mTop , mBottom 这四个值), layout 过程就结束了。
如果是 ViewGroup 的话,那么在它的 layout 方法中只是确定了 ViewGroup 自身的位置,要确定子元素的位置,就需要重写 onLayout 方法;在 onLayout 方法中,会调用子元素的 layout 方法,子元素在它的 layout 方法中确定自己的位置,这样一层一层地传递下去完成整个 View 树的 layout 过程。
layout 方法的作用是确定 View 本身的位置,即设定 View 的四个顶点的位置,这样就确定了 View 在父容器中的位置;
onLayout 方法的作用是父容器确定子元素的位置,这个方法在 View 中是空实现,因为 View 没有子元素了,在 ViewGroup 中则进行抽象化,它的子类必须实现这个方法。
1.绘制背景( background.draw(canvas); );
2.绘制自己( onDraw );
3.绘制 children( dispatchDraw(canvas) );
4.绘制装饰( onDrawScrollBars )。
dispatchDraw 方法的调用是在 onDraw 方法之后,也就是说,总是先绘制自己再绘制子 View 。
对于 View 类来说, dispatchDraw 方法是空实现的,对于 ViewGroup 类来说, dispatchDraw 方法是有具体实现的。
通过 dispatchDraw 来传递的。 dispatchDraw 会遍历调用子元素的 draw 方法,如此 draw 事件就一层一层传递了下去。dispatchDraw 在 View 类中是空实现的,在 ViewGroup 类中是真正实现的。
如果一个 View 不需要绘制任何内容,那么就设置这个标记为 true,系统会进行进一步的优化。
当创建的自定义控件继承于 ViewGroup 并且不具备绘制功能时,就可以开启这个标记,便于系统进行后续的优化;当明确知道一个 ViewGroup 需要通过 onDraw 绘制内容时,需要关闭这个标记。
参考:《Android开发艺术探索》
❹ 如何用android 画出三角形
。支支吾吾也没说出来,哎,回家一想太可气 了,就找到方法。原来是android.graphics.Path这个类,实现的。Api里的描述如下:The Path class encapsulates compound (multiple contour) geometric paths consisting of straight line segments, quadratic curves, and cubic curves. It can be drawn with canvas.drawPath(path, paint), either filled or stroked (based on the paint's Style), or it can be used for clipping or to draw text on a path. 它大概的意思是说path类包含了多种组件,比如直线段,二次曲线,三次曲线,它可以在画布上绘制,无论是填充或笔画。下面举例说明: package cn.lgl.draw; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.graphics.Shader; import android.view.View; public class MyView extends View { public MyView(Context context) { super(context); // TODO Auto-generated constructor stub } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); /*设置背景为白色*/ canvas.drawColor(Color.WHITE); Paint paint=new Paint(); /*去锯齿*/ paint.setAntiAlias(true); /*设置paint的颜色*/ paint.setColor(Color.RED); /*设置paint的 style 为STROKE:空心*/ paint.setStyle(Paint.Style.STROKE); /*设置paint的外框宽度*/ paint.setStrokeWidth(3); /*画一个空心三角形*/ Path path=new Path(); path.moveTo(10,330); path.lineTo(70,330); path.lineTo(40,100); path.close(); canvas.drawPath(path, paint); /*设置paint 的style为 FILL:实心*/ paint.setStyle(Paint.Style.FILL); /*设置paint的颜色*/ paint.setColor(Color.BLUE); /*画一个实心三角形*/ Path path2=new Path(); path2.moveTo(90,330); path2.lineTo(150,330); path2.lineTo(120,270); path2.close(); canvas.drawPath(path2, paint); Shader mShader=new LinearGradient(0,0,100,100, new int[]{Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW}, null,Shader.TileMode.REPEAT); paint.setShader(mShader); /*画一个渐变色三角形*/ Path path4=new Path(); path4.moveTo(170,330); path4.lineTo(230,330); path4.lineTo(200,270); path4.close(); canvas.drawPath(path4,paint); } }