首先纠正下上篇的一个描述性错误,对于invalidate
和postInvalidate
的区别:
-
invalidate
:在主线程内部更新时使用; -
postInvalidate
:在非UI线程更新时使用,postInvalidate
会把更新要求回传至ViewRootImpl
中进行响应;
延续上文内容,进一步针对View
的基本概念做详细介绍,接下来我们将学习编写自己的第一个自定义View
,一起开始吧!
相信大家在实际生活中都或多或少的画过一些东西,那么我们画一个圆需要做什么准备工作呢?
-
圆的呈现体:一张纸或地面的一片区域等等,用于圆的展示;
-
绘制圆的工具:铅笔,圆珠笔,钢笔等等,用于绘制圆;
-
圆的圆心及半径:确定圆绘制在哪儿,绘制多大;
同样的,在自定义View
的开发过程中,我们也需要相似的东西(随后文章中简称为绘制类型自定义View
的三要素):
-
Canvas
:画布,用于绘制View
所要显示的内容,一般来自onDraw()
函数的传入,onDraw()
函数原型如代码片段-onDraw()所示; -
Paint
:画笔,用于绘制View
所需要绘制的内容,相当于笔,在其内部可以设置颜色,粗细,是否实心等信息,一般通过new
的方式获取该类对象; -
Point
:点,用于确定View
所需要绘制的内容大小及位置等相关信息,当然这里的Point
不具有实际意义,也有可能是线段,矩形等,只不过大多数是通过点的组合关系确定而已;
/** onDraw() **/
protected void onDraw(Canvas canvas) {
}
获取宽高
上面我们已经简单了解到View
三要素的前两个要素的获取方式,那么点又该如何得到呢?相信细心的朋友要吐槽我了,上一篇不就说过了么?左上角是坐标原点,向下Y正向,向右X正向,就是这么简单。
那么对于一些关键点呢?例如说View
的中心,小伙伴们估计又要心急了,有啥好说的,View#getWidth()/2
,View#getHeight()/2
。
但是真的可以做到么?答案是必须等到View
走完onMeasure
流程后再使用这种方式,否则数据不准确。那么有什么办法呢?
好,大招来了,上篇中刚讲过View的Top
,left
,right
,bottom
,仔细看图,监听这四个值是不是可以得到View
的宽高,很显然可行。系统已经将这一块帮我们做好了,在setLeft()
,setRight()
,setTop()
,setBottom()
中均调用了onSizeChanged
方法,所以我们只需重写onSizeChanged
就可以获得View
的宽高了,详情见代码片段-setTop(),同事我们可以看到系统中计算View
的宽高方式。
width = right - left
height = bottom - top
/** setTop **/
public final void stTop(int top) {
if (top != mTop) {
....
//获取View宽度
int width = mRight - mLeft;
int oldHeight = mBottom - mTop;
mTop = top;
mRenderNode.setTop(mTop);
//sizeChange内部调用了onSizeChanged
sizeChange(width, mBottom - mTop, width, oldHeight);
....
}
}
private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
//调用onSizeChanged
onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
....
rebuildOutline();
}
这里要注意一点,宽高只是限制了显示区域(如上文View边界图所示),和画布大小无关,在View内部绘制区域理论是说是无限大的。
第一个自定义View
知道了如何获取View
的宽高,接下来我们就可以开始一次简单的绘制了(在View
最中心绘制一个半径200的圆):
打开Android Studio,新建Android Studio,在其内创建Java class 并使其继承自View
,重写View
的构造方法,代码如片段-Constructor:
/* Constructor */
public class PaintView extends View {
public PaintView(Context context) {
super(context);
}
public PaintView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public PaintView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
声明画笔和宽高
1.在该类中添加片段-Paint Point中的成员属性:
/* Paint Point */
/*
画笔,用于View内部内容的绘制
*/
private Paint mPaint;
/*
用于存储View的宽高
*/
private int mWidth,mHeight;
2.初始化宽高,重写onSizeChanged
方法,代码如片段-onSizeChanged:
/* onSizeChanged */
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w > 0 && h > 0){
mWidth = w;
mHeight = h;
}
}
3.初始化画笔,并为画笔设置颜色,实心等属性,代码如片段-Paint init:
/* Paint init */
//在View构造函数中调用
private void init(){
//新建画笔对象
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//为画笔设置颜色
mPaint.setColor(Color.BLUE);
//设置画笔实心空心,Style.FILL--实心,Style.STROKE--空心
mPaint.setStyle(Style.FILL);
}
获取画布绘制圆
如上文所述,重写View#onDraw
方法获取画布,并绘制圆,代码如片段-onDraw Method:
/* onDraw Method */
/**
* 前两个参数分别代表圆心的X坐标和Y坐标
* 第三个参数是圆半径
* 第四个参数是绘制圆所使用的画笔
*/
canvas.drawCircle(mWidth/2,mHeight/2,200,mPaint);
这里我们调用了画布的画圆方法,相关参数说明在注释上。
XML中声明使用
在Android体系内部View是通过包名的方式反射生成View
对象的(后续View树的生成部分会做详细说明),所以针对自定义View
,我们要使用全包名的方式在XML中引用,当然也可以通过new的方式生成,代码如片段-activity_main.xml:
<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.code.customview.MainActivity">
<com.example.code.customview.PaintView
android:id="@+id/four_arc_view"
android:layout_width="400dp"
android:layout_height="400dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
好了点击三角运行一下吧,如果你没出错,效果应该是图-结果那样的。
举一反三,那么请朋友们尝试下,修改画笔颜色,修改画笔样式,在界面左上角200,200位置绘制一个空心圆环吧!
精力更多的亲们,现在可以尝试绘制图-水波纹中的效果了,其中
Paint#setStrokeWidth()可用于设置画笔粗细
Paint#getStrokeWidth()可用于获取画笔宽度
Paint#setAlpha() 可用于改变颜色值的透明度,取值在0-255之间
完成的你们可以在后台秀绘制结果给我哦!