android自定义控件

  • 一、坐标系

  • 屏幕默认坐标系示例View坐标系get雨getRaw区别
  • 分类和流程

  • 自定义View绘制流程函数调用链

几个重要的函数

1.构造函数

构造函数是View的入口,可以用于初始化一些的内容,和获取自定义属性

View的构造函数有四种重载分别如下:

public void SloopView(Context context) {}
public void SloopView(Context context, AttributeSet attrs) {}
public void SloopView(Context context, AttributeSet attrs, int defStyleAttr) {}
public void SloopView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {}

有三个参数的构造函数中第三个参数是默认的Style,这里的默认的Style是指它在当前Application或Activity所用的Theme中的默认Style,且只有在明确调用的时候才会生效,以系统中的ImageButton为例说明:

public ImageButton(Context context, AttributeSet attrs) {
    //调用了三个参数的构造函数,明确指定第三个参数
    this(context, attrs, com.android.internal.R.attr.imageButtonStyle);
}

public ImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
    //此处调了四个参数的构造函数,无视即可
    this(context, attrs, defStyleAttr, 0); 
}

排除了两个之后,只剩下一个参数和两个参数的构造函数,他们的详情如下:

//一般在直接New一个View的时候调用。
public void SloopView(Context context) {}

//一般在layout文件中使用的时候会调用,关于它的所有属性(包括自定义属性)都会包含在attrs中传递进来。
public void SloopView(Context context, AttributeSet attrs) {}

以下方法调用的是一个参数的构造函数:

//在Avtivity中
SloopView view  new SloopView(this);

以下方法调用的是两个参数的构造函数:

//在layout文件中 - 格式为: 包名.View名
<com.sloop.study.SloopView
  android:layout_width"wrap_content"
  android:layout_height"wrap_content"/>

2.测量View大小(onMeasure)

View的大小不仅由自身所决定,同时也会受到父控件的影响,为了我们的控件能更好的适应各种情况,一般会自己进行测量。

测量View大小使用的是onMeasure函数,我们可以从onMeasure的两个参数中取出宽高的相关数据:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthsize = MeasureSpec.getSize(widthMeasureSpec);      //取出宽度的确切数值
    int widthmode = MeasureSpec.getMode(widthMeasureSpec);      //取出宽度的测量模式
    
    int heightsize = MeasureSpec.getSize(heightMeasureSpec);    //取出高度的确切数值
    int heightmode = MeasureSpec.getMode(heightMeasureSpec);    //取出高度的测量模式
}

从上面可以看出 onMeasure 函数中有 widthMeasureSpec 和 heightMeasureSpec 这两个 int 类型的参数, 毫无疑问他们是和宽高相关的, 但它们其实不是宽和高, 而是由宽、高和各自方向上对应的测量模式来合成的一个值:

测量模式一共有三种, 被定义在 Android 中的 View 类的一个内部类View.MeasureSpec中:

模式二进制数值描述
UNSPECIFIED 00 默认值,父控件没有给子view任何限制,子View可以设置为任意大小。
EXACTLY 01 表示父控件已经确切的指定了子View的大小。
AT_MOST 10 表示子View具体大小没有尺寸限制,但是存在上限,上限一般为父View大小。

用 MeasureSpec 的 getSize是获取数值, getMode是获取模式。

注意:

如果对View的宽高进行修改了,不要调用 super.onMeasure( widthMeasureSpec, heightMeasureSpec); 要调用 setMeasuredDimension( widthsize, heightsize)这个函数。

3.确定View大小(onSizeChanged)

这个函数在视图大小发生改变时调用。

因为View的大小不仅由View本身控制,而且受父控件的影响,所以我们在确定View大小的时候最好使用系统提供的onSizeChanged回调函数。

onSizeChanged如下:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
}

四个参数,分别为 宽度,高度,上一次宽度,上一次高度。

只需关注 宽度(w), 高度(h) 即可,这两个参数就是View最终的大小。

4.确定子View布局位置(onLayout)

确定布局的函数是onLayout,它用于确定子View的位置,在自定义ViewGroup中会用到,他调用的是子View的layout函数。

在自定义ViewGroup中,onLayout一般是循环取出子View,然后经过计算得出各个子View位置的坐标值,然后用以下函数设置子View位置。

child.layout(l, t, r, b);

5.绘制内容(onDraw)

onDraw是实际绘制的部分,也就是我们真正关心的部分,使用的是Canvas绘图。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
}

6.对外提供操作方法和监听回调

自定义完View之后,一般会对外暴露一些接口,用于控制View的状态等,或者监听View的变化.

  • 三、Canvas

操作类型

相关API

备注

绘制颜色

drawColor, drawRGB, drawARGB

使用单一颜色填充整个画布

绘制基本形状

drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc

依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧

绘制图片

drawBitmap, drawPicture

绘制位图和图片

绘制文本

drawText, drawPosText, drawTextOnPath

依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字

绘制路径

drawPath

绘制路径,绘制贝塞尔曲线时也需要用到该函数

顶点操作

drawVertices, drawBitmapMesh

通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用

画布剪裁

clipPath, clipRect

设置画布的显示区域

画布快照

save, restore, saveLayerXxx, restoreToCount, getSaveCount

依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数

画布变换

translate, scale, rotate, skew

依次为 位移、缩放、 旋转、错切

Matrix(矩阵)

getMatrix, setMatrix, concat

实际上画布的位移,缩放等操作的都是图像矩阵Matrix, 只不过Matrix比较难以理解和使用,故封装了一些常用的方法。

    private Paint mPaint = new Paint();初始化画笔

    private void initPaint() {

      mPaint.setColor(Color.BLACK);//设置画笔颜色

      mPaint.setStyle(Paint.Style.FILL);//设置画笔模式为填充

      mPaint.setStrokeWidth(10f); //设置画笔宽度为10px

    }

    public SloopView(Context context, AttributeSet attrs) {

      super(context, attrs); initPaint();

    }

绘制点:       canvas.drawPoint(200, 200, mPaint);

绘制线:       canvas.drawLine(300,300,500,600,mPaint);

绘制矩形:  canvas.drawRect(100,100,800,400,mPaint);

               Rect rect = new Rect(100,100,800,400); canvas.drawRect(rect,mPaint);

       RectF rectF = new RectF(100,100,800,400); canvas.drawRect(rectF,mPaint);

绘制圆:       canvas.drawCircle(500,500,400,mPaint);

绘制圆弧:  public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint){}

四、注意事项:

1、在 xml中创建了一个view时,所有在xml中声明的属性都会被传入到view的构造方法中的AttributeSet类型的参数当中。 通过调用Context的obtainStyledAttributes()方法返回一个TypedArray对象。然后直接用TypedArray对象获取自定义属性的值。TypedArray对象是共享的资源,所以在获取完值之后必须要调用recycle()方法来回收

2、自定义控件的属性发生改变之后,控件的样子也可能发生改变,在这种情况下就需要调用invalidate()方法让系统去调用view的onDraw()重新绘制

3、在onDraw方法中开始绘制之前,应该让画笔Paint对象的信息初始化完毕。这是因为View的重新绘制是比较频繁的,这就可能多次调用onDraw,所以初始化的代码不应该放在onDraw方法里


 参考:http://www.gcssloop.com/customview/CustomViewIndex
原文地址:https://www.cnblogs.com/xiao-dl/p/7508743.html