Android自定义控件——01绘图基础

1.绘图基础

1.1 基本图形绘制

1.1.1 概述

  • Paint类是画笔,Canvas类是画布。
  • Paint类里设置:画笔大小、粗细、画笔颜色、透明度、字体的样式等。
  • Canvas类里设置:如圆形、矩形、文字等。

1.1.2 Paint使用基础

  • setStyle() :设置填充样式

    • Paint.Style.FILL : 仅填充内部。
    • Paint.Style.FILL_ AND_ STROKE :填充内部和描边。
    • Paint.Style.STROKE : 仅描边。
  • setStrokeWidth():设置描边宽度值, 单位是px 。

  • 举例:

    class BaseView : View {
        constructor(context: Context) : super(context) {}
        constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
        constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
    
        override fun onDraw(canvas: Canvas) {
            super.onDraw(canvas)
            //设置画笔的基本属性
            /**
             * 实际上:不允许在onDraw()函数中创建Paint对象,onDraw()函数会被多次调用,会引起GC。
             * 一般在自定义控件的构造函数中创建变量。
             */
            val paint = Paint()
            paint.color = Color.RED//颜色
            paint.style = Paint.Style.STROKE//填充样式
            paint.strokeMiter = 50f//描边宽度 单位px
            canvas.drawCircle(190f, 200f, 150f, paint)
        }
    }
    

1.1.3 Canvas使用基础

  • 画布背景设置:

    void drawColor(int color)// 8位的0xAARRGGBB样式颜色值
    void drawARGB(int a,int r,int g,int b)//a指Alpha(透明度)
    void drawRGB(int r,int g,int b)
    
  • 画直线:

    void drawLine(float startX,float startY,float stopX,float stopY,Paint paint)
    
    • startX :起始点X 坐标。
    • startY :起始点Y 坐标。
    • stopX : 终点X 坐标。
    • stopY : 终点Y 坐标。
  • 画点:

    void drawPoint(float x,float y,Paint paint)
    
    • float x : 点的X 坐标。
    • float y : 点的Y 坐标。
  • 矩形工具类RectF 、Rect:

    • RectF 保存float类型数值的矩形结构;Rect保存int类型数值的矩形结构。
    RectF()
    RectF(float left,float top,float right,float bottom)//常用
    RectF(RectF r)
    RectF(Rect r)
        
    Rect()
    Rect(int left,工nt to,int right,int bottom)
    Rect(Rect r)
    
  • 矩形

    void drawRect(float left,float top,float right,float bottom,Paint paint)
    void drawRect(RectF rect,Paint paint)
    void drawRect(Rect r,Paint paint)
    

1.1.4 Color

  • 常量颜色:

    int BLACK
    int BLUE
    int CYAN
    int DKGRAY
    int GRAY
    int GREEN
    int LTGRAY
    int MAGENTA
    int RED
    int TRANSPARENT
    int WHITE
    int YELLOW
    
    • 通过Color.XXX 来直接使用这些颜色。
  • 构造颜色:

    static int argb(int alpha,int red,int green,int blue)//带透明度的颜色
    static int rgb(int red, int green, int blue)//不带透明度的颜色
    
    • 色彩分量的取值范围都是0~ 255 。(十六进制表示更为直观:S0x00~0xFF)
  • 提取颜色分量:

    static int alpha(int color)
    static int red(int color)
    static int green(int color)
    static int blue(int color)
        
    //example 
    int green = Color.green(0xFF000F00);
    

1.1.5 补充

  • **抗锯齿功能:**setAntiAlias()

    void setAntiAlias(boolean aa)
    //example
    paint.setAntiAlias(true)//打开抗锯齿功能
    
  • 多条直线:

    //构造函数1
    void drawLines(float[] pts, Paint paint)//pts中两两连线
    
    //构造函数2
    void drawLines(float[] pts, int offset, int count, Paint paint)//pts中两两连线
    
    • int offset:集合中跳过的数值个数(指单个数值)。
    • int count:参与绘制的数值个数(指单个数值)。
  • 点:

    void drawPoint(float x, float y, Paint paint)
    
  • 多个点:

    void drawPoints(float[] pts, Paint paint)
    void drawPoints(float[] pts, int offset, int count, Paint paint)
    
  • 圆角矩形

    void drawRoundRect(RectF rect, float rx, float ry, Paint paint)
    
    • RectF rect:要绘制的矩形。
    • float rx:生成圆角的椭圆的X轴半径。
    • float ry:生成圆角的椭圆的Y轴半径。
  • 圆形:

    void drawCircle(float cx, float cy, float radius, Paint paint)
    
    • float cx:圆心点的X轴坐标。
    • float cy:圆心点的Y轴坐标。
    • float radius:圆的半径。
  • 椭圆:

    void drawOval(RectF oval, Paint paint)
    
  • 弧:

    void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
    
    • boolean useCenter:是否有弧的两边。
  • Rect与RectF的补充:

    • 判断是否包含某个点:

      boolean contains(int x, int y)
      
    • 判断是否包含某个矩形:

      boolean contains(int left, int top, int right, int bottom)
      boolean contains(Rect r)
      
    • 判断两个矩形是否相交:

      //静态方法
      static boolean intersects(Rect a, Rect b)
      //成员方法
      boolean intersects(int left, int top, int right, int bottom)
          
      //判断相交并返回结果
      boolean intersect(Rect r)
      boolean intersect(int left, int top, int right, int bottom)  
          
      /**
       * 区别:
       * intersect成员方法不仅会返回是否相交的结果,而且会把相交的部分的矩形赋给当前Rect对象。
       * 如果两个矩形不想交,则当前Rect对象值不变。
      
    • 合并两个矩形:

      /**
       * 无论两个矩形是否相交,取两个矩形最小左上角点作为结果矩形的左上角点,
       * 取两个矩形最大右下角点作为结果矩形的右下角点。
       * 若一方为空,则将有值的一方作为最终结果。
       */
      public void union(int left, int top, int right, int bottom)
      public void union(Rect r)    
      
    • 合并矩形与某个点:

      /**
       * 如果不相交,根据目标点(x,y)的位置,将目标点设置为当前矩形的左上角点或者右下角点。
       * 如果当前矩形是一个空矩形,则最后结果的矩形为([0,0],[x,y])
       *
       */
      public void union(int x, int y)
      

1.2 路径

1.2.1 概述

  • 在Canvas中绘制路径。
void drawPath(Path path,Paint paint)

1.2.2 直线路径

//画一条直线路径,一般涉及下面3个函数。
void moveTo(float x1,float y1)
//(x1,y1)是直线的起始点。
void lineTo(float x2,float y2)
//(x2,y2)是直线的终点。
void close()
  • 如果连续画了几条直线,但没有形成闭环,那么调用close()函数会将路径首尾点连接起来,形成闭环。

1.2.3 弧线路径

void arcTo(RectF oval,float startAngle,float sweepAngle)
  • RectF oval :生成椭圆的矩形。
  • float startAngle :弧开始的角度,以X轴正方向为0度。
  • float sweepAngle :弧持续的角度。
  • 举例:
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
 
    val paint = Paint()
    paint.color = Color.RED
    paint.style = Paint.Style.STROKE
    paint.strokeWidth = 5f
        
    val path=Path();
    path.moveTo(10f,10f)
    val rectF = RectF(100f,10f,200f,100f)
    path.arcTo(rectF,0f,90f)
    
    canvas.drawPath(path,paint)
}
  • 只画了一条弧,但弧最终会和起始点(10,10)连接起来,因为在默认情况下路径都是连贯的

  • 存在两种路径不连贯的情况:

    • 调用 add:XXX 系列函数,将直接添加固定形状的路径。
    • 调用moveTo() 函数改变绘制起始位置。
  • 针对上述绘制弧线的问题,Path 类也提供了另外两个重载方法。

void arcTo(float left,float top,float right,float bottom,float startAngle,float sweepAngle,boolean forceMoveTo)
void arcTo(RectF oval,float startAngle ,float sweepAngle,boolean forceMoveTo)
  • 参数boolean forceMoveTo 的含义是:是否强制地将弧的起始点作为绘制起始位置。

1.2.4 补充

  • addXXX 系列函数:

    • 添加矩形路径:

      void addRect(float left,float top,float right,float bottom, Path.Direction dir)
      void addRect(RectF rect, Path.Direction dir)
      
    • Path.Direction参数有两个值:(主要用于依据路径布局文字)

      • Path.Direction.CCW:创建逆时针方向的矩形路径。
      • Path.Direction.CW:创建顺时针方向的矩形路径。
    • 添加圆角矩形路径:

      void addRoundRect(RectF rect, float[] radii, Path.Direction dir)
      void addRoundRect(RectF rect, float rx, float ry, Path.Direction dir)
      
    • float[] radii:传入8个数值,分为四组,分别对应每个角的椭圆的横轴半径和纵轴半径。

    • 添加圆形路径

      void addCircle(float x, float y, float radius, Path.Direction dir)
      
    • 添加椭圆路径:

      void addOval(RectF oval, Path.Direction dir)
      
    • 添加弧形路径:

      void addArc(float left,float top,float right,float bottom, float startAngle, float sweepAngle)
      void addArc(RectF oval, float startAngle, float sweepAngle)
      
  • 填充模式:

    • Path的填充模式与Paint的填充模式不同。Path的填充模式是指填充Path的哪部分。Path.FillType表示Path的填充模式,它存在4个枚举值:

      • FillType.WINDING:默认值,当两个图形相交时,取各自和相交部分显示。
      • FillType.EVENT_ODD:取path所在并不相交的区域。
      • FillType.INVERSE_WINDING:取path的外部区域。
      • FillType.INVERSE_EVEN_ODD:取path的外部和相交区域。
  • 重置路径:

    void reset()
    void rewind()
    
    • 共同点是都会清空内部所保存的所有路径,但二者也有区别。
    • rewind()函数会清除FillType及所有的直线、曲线、点的数据等,但是会保留数据结构。可以实现快速重用,提高一定的性能。注意:只有在绘制相同的路径时,这些数据结构才是可以复用的。
    • reset()函数类似于新建一个路径对象,它的所有数据空间都会被回收并重新分配,但不会清除FillType。
    • 整体而言,rewind()函数不会清除内存,但会清楚FillType;而 reset()函数则会清除内存,但不会清除FillType。

1.3 Region

  • Region 译为“区域”,区域是一块任意形状的封闭图形。

1.3.1 构造Region

  • 直接构造:
public Region(Region region)//复制一个Region 的范围
public Region (Rect r)  //创建一个矩形区域
public Region(int left,int top,int right,int bottom)//创建一个矩形区域
  • 举例:
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    //设置画笔的基本属性
    val paint = Paint()
    paint.color = Color.RED//颜色
    paint.style = Paint.Style.STROKE//填充样式
    paint.strokeWidth = 5f

    val path = Path();
    path.moveTo(10f, 10f)
    val rect = Rect(100, 50, 500, 800)
    val region = Region(rect)
    drawRegion(canvas, region, paint)
}

private fun drawRegion(canvas: Canvas, region: Region, paint: Paint) {
    val iter = RegionIterator(region)
    val r = Rect()
    while (iter.next(r)) {
        canvas.drawRect(r, paint)
    }
}
  • 间接构造:

    • Region 的空构造函数:
    public Region()
    
    • set 系列函数:
    public void setEmpty()//置空
    public boolean set(Region region)//利用新的区域替换原来的区域
    public boolean set(Rect r)//利用矩形所代表的区域替换原来的区域
    public boolean set(int left,int top,int right,int bottom)//根据矩形的两个角点构造出矩形区域来替换原来的区域
    public boolean setPath(Path path,Region clip)//根据路径的区域与某区域的交集构造出新的区域。
    
    • 注意:无论调用set系列函数的Region 是不是有区域值,当调用set系列函数后,原来的区域值就会被替换成se 系列函数里的区域值。
  • 利用**setPath(Path path,Region clip)**函数构造不规则区域:

    • Path path:用来构造区域的路径。
    • Region clip:与前面的path所构成的路径取交集,并将该交集设置为最终的区域。
    • 举例:
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val paint = Paint()
        paint.color = Color.RED
        paint.style = Paint.Style.FILL
        //构造一条椭圆路径
        val ovalPath = Path()
        val rect = RectF(50f, 50f, 200f, 500f)
        /**
         * PathDirectionCCW   逆时针
         * PathDirectionCW  顺时针
         */
        ovalPath.addOval(rect,Path.Direction.CW)
        //在setPath()函数中传入一个比椭圆区域小的矩形区域,让其取交集
        val rgn =Region()
        rgn.setPath(ovalPath,Region(50,50,200,200))
    
        drawRegion(canvas, rgn, paint)
    }
    
    private fun drawRegion(canvas: Canvas, region: Region, paint: Paint) {
    val iter = RegionIterator(region)
        val r = Rect()
        while (iter.next(r)) {
            canvas.drawRect(r, paint)
        }
    }
    

1.3.2 区域相交

  • Region不是用来绘图的,Region最重要的功能在区域的相交操作中。

  • union()函数

    boolean union(Rect r)
    
    • 用于与指定矩形取并集,即将Rect 所指定的矩形加入当前区域中。
  • 区域操作:

    • 除通过union()函数合并指定矩形以外,Region还提供了几个更加灵活的操作函数:

      boolean op(Rect r,Op op)
      boolean op(int left,int top,int right,int bottom,Op op)
      boolean op(Region region,Op op)
      
    • 函数的含义是:用当前的Region 对象与指定的一个Rect 对象或者Region 对象执行相交操作,并将结果赋给当前的Region对象。计算成功,则返回true ,否则返回false。

    • Op 参数值有如下6 个:

      public enum Op {
          DIFFERENCE(O),//最终区域为region1 与 region2 不同的区域
          INTERSECT(1),//最终区域为region1 与 region2 相交的区域
          UNION(2),//最终区域为region1 与 region2 组合在一起的区域
          XOR(3),//最终区域为region1 与 region2 相交之外的区域
          REVERSE_DIFFERENCE(4),//最终区域为region2 与 region1 不同的区域
          REPLACE (5);//最终区域为region2 的区域
      }
      
    • 还存在一系列方法:

      boolean op(Rect rect,Region region,Op op)
      boolean op(Region region1,Region region2,Region.Op op)
      

1.3.3 补充

  • 枚举区域RegionIterator类

    • 对于特定的区域,可以使用多个矩形来表示其大致形状。

    • 构造函数:根据区域构建对应的矩形集

      RegionIterator(Region region)
      
    • 获取下一矩形,将结果保存在参数Rect r中:

      boolean next(Rect r)
      
    • 由于Canvas中没有直接绘制Region的函数,绘制一个区域,只能通过RegionIterator类构造矩形集来逼近显示区域:

      private void drawRegion(Canvas canvas, Region rgn, Paint paint){
          RegionIterator iter = new RegionIterator(rgn);
          Rect r = new Rect();
          
          while(iter.next(r)){
              canvas.drawRect(r,paint);
          }
      }
      
  • 几个判断方法

    public boolean isEmpty();
    public boolean isRect();
    public boolean isComplex();//判断该区域是否是多个矩阵的组合
    
  • getBound 系列函数

    //返回能够包裹当前路径的最小矩形
    public Rect getBounds()
    public boolean getBounds(Rect r)
    //返回当前矩形所对应的Path对象
    public Path getBoundaryPath()
    public boolean getBoundaryPath(Path path)
    
  • 是否包含:

    public boolean contains(int x, int y)
    public boolean quickContains(Rect r)
    public boolean quickContains(int left,int top,int right,int bottom)
    
  • 是否相交:

    public boolean quickReject(Rect r)
    public boolean quickReject(int left,int top,int right,int bottom)
    public boolean quickReject(Region rgn)
    
  • 平移变换:

    public void translate(int dx,int dy)
    //将平移结果赋值给dst,当前Region保持不变
    public void translate(int dx,int dy,Region dst)
    

1.4 Canvas(画布)

  • 除可以在Canvas 上面绘图以外,还可以对画布进行变换及裁剪等操作。

1.4.1 Canvas 变换

  • 平移(Translate ):

    void translate(float dx,float dy)
    
    • float dx:水平方向平移的距离,正数为向正方向(向右)平移的量,负数为向负方向(向左)平移的量。
    • float dy:垂直方向平移的距离,正数为向正方向(向下)平移的量,负数为向负方向(向上)平移的量。
  • 裁剪画布(clip 系列函数):

    • 裁剪画布是指利用clip 系列函数, 通过与Rect 、Path 、Region 取交、井、差等集合运算来获得最新的画布形状。除调用save()、restore()函数以外,这个操作是不可逆的, 一旦Canvas被裁剪,就不能恢复。

    • 注意:在使用裁剪画布系列函数时,需要禁用硬件加速功能。

      setLayerType(LAYER_TYPE_OFTWARE,null);
      
    • 裁剪画布系列函数:

      boolean clipPath(Path path)
      boolean clipPath(Path path,Region,Op op)
      boolean clipRect(Rect rect,Region.Op op)
      boolean clipRect(RectF rect,Region.Op op)
      boolean clipRect(int left,int top,int right,int bottom)
      boolean clipRect(float left,float top,float right,float bottom)
      boolean clipRect(RectF rect)
      boolean clipRect(float left,float top,float right,float bottom,Region,Op op)
      boolean clipRect(Rect rect)
      boolean clipRegion(Region region)
      boolean clipRegion(Region region,Region.Op op)
      

1.4.2 画布的保存与恢复

  • **save()restore()**函数:

    int save()
    void restore()
    
    • save():每次调用都会先保存当前画布的状态, 然后将其放入特定的栈中。
    • restore():每次调用都会把栈中顶层的画布状态取出来,并按照这个状态恢复当前的画布, 然后在这个画布上作画。

1.4.3 补充

  • 旋转:

    void rotate(float degrees)//旋转中心点(0,0)
    void rotate(float degrees, float px, float py)
    
  • 缩放

    public void scale(float sx, float sy)
    
    • float sx:水平方向伸缩的比例。假设原坐标轴的比例为n,不变时为1,变更后的X轴密度为n*sx。即sx是小数表示缩小,sx是整数表示放大。
    • float sy:垂直方向伸缩的比例。与sx类似。
  • 扭曲(Skew):

    void skew(float sx,float sy)
    
    • float sx:将画布在X轴方向上倾斜相应的角度,sx为倾斜角的正切值
    • float sy:将画布在Y轴方向上倾斜相应的角度,sy为倾斜角的正切值
  • restoreToCount(int saveCount)函数:

    //save函数的完整声明
    public int save();//返回当前所保存的画布所在栈的索引
    
    //一直出栈,直到指定索引的画布出栈为止
    public void restoreToCount(int saveCount)
    

1.5 文字

1.5.1 Paint设置

//普通设置
paint.setStrokewidth(5);	//设置画笔宽度
paint.setAntiAlias(true);	//指定是否使用抗锯齿功能。如果使用,则会使绘图速度变慢
paint.setStyle(Paint.Style.FILL);	//绘图样式,对于文字和几何图形都有效
paint.setTextAlign(Align.CENTER);	//设置文字对齐方式,取值为Align.CENTER、Align.LEFT、Align.RIGHT
paint.setTextSize(12);		//设置文字大小

//样式设置
paint.setFakeBoldText(true);	//设置是否为粗体文字
paint.setUnderlineText(true);	//设置下画线
paint.setTextSkewX((float) -0.25);//设置字体水平倾斜度,普通斜体字设为-0.25
paint.setStrikeThruText(true);	//设置带有删除线效果

//其他设置
paint.setTextScalex(2);	//只会将水平方向拉伸,高度不会变

1.5.2 Canvas绘制文本

  • 普通绘制:

    void drawText(String text,float x,float y,Paint paint)//参数(x,y)是起始点坐标。
    void drawText(CharSequence text,int start,int end,float X,float y,Paint paint)
    void drawText (String text,int start,int end,float x,float y,Paint paint)
    void drawText (char[] text,int index,int count,float x,float y,Paint paint)
    
  • 逐个指定文字位置:

    void drawPosText(String text,float[] pos,Paint paint)
    void drawPosText(char[] text,int index,int count,float[] pos,Paint paint)
    
  • 沿路径绘制:

    void drawTextOnPath(String text,Path path,float hOffset,float vOffser,Paint paint)
    void drawTextOnPath(char[] text,int index,int count,Path path,float hoffset,float vOffset,Paint paint)
    
    • float hOffset:与路径起始点的水平偏移量。
    • float vOffset:与路径中心的垂直偏移量。

1.5.3 设置字体样式

Typeface setTypeface(Typeface typeface)
  • Android 自带的字体样式:

    • Typeface.SANS_ SERIF
    • Typeface.MONOSPACE
    • Typeface .SERIF
  • defaultFromStyle()函数

    //根据字体样式获取对应的默认字体。
    Typeface defaultFromStyle(int style)
    
    • Typeface.NORMAL:正常字体。
    • Typeface.BOLD:粗体。
    • Typeface.ITALIC:斜体。
    • Typeface. BOLD_ ITALIC:粗斜体。
  • create(String familyName, int style)函数:

    //直接通过指定字体名来加载系统中自带的字体样式。如果字体样式不存在用系统样式替代并返回。
    Typeface create(String fami1yName,int style)
    
  • 自定义字体:

    //既可以从assets文件夹中获取字体样式,也可以从指定的文件路径中获取字体样式。
    Typeface createFromAsset(AssetManager mgr,String path)
    Typeface createFromFile(String path)
    Typeface createFromFile(File path)
        
    //使用举例:
    AssetManager mgr= mContext.getAssets(); //得到AssetManager
    //根据路径得到Typeface
    Typeface typeface=Typeface.createFromAsset(mgr, "fonts/jian_ luobo. ttf");
    paint.setTypeface(typeface);
    

1.6 控件的使用方法

1.6.1 动态添加控件

  • 需要给根节点添加ID。

  • 举例:

    LinearLayout rootView = (LinearLayout)findViewbyId(R.id.root);
    CustomView customView = new CustomView(this);
    
    LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
    
    rootView.addView(customView,layoutParams)
    

1.6.2 LayoutParams

  • 用来设置控件的宽和高,对应XML中的layout_widthlayout_height 属性。

  • LayoutParams存在三个构造函数:

    public LayoutParams(int width, int height) //px
    public LayoutParams(Context c, AttributeSet attrs)
    public LayoutParams(LayoutParams source)
    

参考资料

  • 《Android自定义控件开发入门与实战》
发布了64 篇原创文章 · 获赞 65 · 访问量 8803

猜你喜欢

转载自blog.csdn.net/qq_33334951/article/details/103687514
今日推荐