①自定义属性
②选择和设置构造方法
③重写onMeasure()方法
④重写onDraw()方法
⑤重写onLayout()方法
⑥重写其他事件的方法
https://blog.csdn.net/qq_36946446/article/details/89189320
2.1、定义自定义属性
2.2、使用自定义属性
2.3、获取自定义属性
3.1、一个参数的构造方法
构造方法代码
public CustomMenu(Context context) { …… }
这个构造方法只有一个参数Context上下文。当我们在JAVA代码中直接通过创建这个控件时就会调用这个方法。
3.2、两个参数的构造方法
public CustomMenu(Context context, AttributeSet attrs) { …… }
这个构造方法有两个参数:Context上下文和AttributeSet属性集。当我们需要在自定义控件中获取属性时,就默认调用这个构造方法。AttributeSet对象就是这个控件中定义的所有属性。我们可以通过AttributeSet对象的getAttributeCount()方法获取属性的个数,通过getAttributeName()方法获取到某条属性的名称,通过getAttributeValue()方法获取到某条属性的值。
注意:不管有没有使用自定义属性,都会默认调用这个构造方法。
3.3、三个参数的构造方法
public CustomMenu(Context context, AttributeSet attrs, int defStyleAttr) { …… }
这个构造方法中有三个参数:Context上下文、AttributeSet数据集和defStyleAttr自定义属性的引用。这个构造方法不会默认调用,必须要手动调用,这个构造方法和两个参数的构造方法唯一区别就是这个构造方法给我们默认传入了一个默认数据集。
defStyleAttr指向的时自定义属性的< declare-styleable >标签中定义的自定义属性集,我们在创建TypedArray对象时需要用到defStyleAttr。
3.4、三个构造方法的整合
一般情况下我们会将这三个构造方法串联起来,即层层调用,让最终的业务处理都集中在三个参数的构造方法。让一参的构造方法引用两参的构造方法,让两参的构造方法引用三参的构造方法。这样一来,就可以保证无论使用什么方式创建这个控件,最终都会到三个参数的构造方法中处理,减少了重复代码。
public CustomMenu(Context context) {
this(context, null);
}
public CustomMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 业务代码
}
onMeasure()方法主要负责测量,决定控件本身或其子控件所占的宽高。我们可以通过onMeasure()方法提供的参数widthMeasureSpec和heightMeasureSpec来分别通过MeasureSpec.getMode(xMeasureSpec)和MeasureSpec.getSize(xMeasureSpec)来获取到控件或其子view的测量模式和测量值。
测量模式分为以下三种情况:
EXACTLY:当宽度值设置为具体值时使用,如100dp、match_parent等,此时取出的size时精确的尺寸
AT_MOST:当宽高值设置为wrap_content时使用,此时取出的size时控件最大可获得的空间
UNSPECIFIED:当没有指定宽高值时使用
onMeasure()方法中常用的方法:
getChildCount():获取子view的数量
getChildAt(i):获取第i个子控件
subView.getLayoutParams.width/height:设置或获取子控件的宽或高
measureChild(child,widthMeasureSpec,heightMeasureSpec):测量子view的宽高
child.getMeasuredHeight/width:执行完MeasureChild()方法后就可以通过这种方式获取子view的宽高值
getPaddingLeft/Right/Top/Bottom():获取控件的四周内边距
setMeasuredDimension(width,height):重新设置控件的宽高。如果写了这句代码就需要删除“super.onMeasure(widthMeasureSpec,heightMeasureSpec)”这句代码
注意:onMeasure()方法可能被调用多次,这是因为控件中的内容或子view可能对分配给自己的空间不满意,因此向父空间申请重新分配空间。
onDraw()方法负责绘制,即如果我们希望得到的效果在Android原生控件没有现成的支持,那么我们就需要自己绘制我们的自定义控件的显示效果。
要学习onDraw()方法,我们就需要学习在onDraw()方法中使用最多的两个类:Paint和Canvas。
注意:每次触摸自定义View/ViewGroup时都会触发onDraw()方法
5.1、Paint类
Paint画笔对象,这个类中包含了如何绘制几何图形、文字和位图的样式和颜色信息,指定了如何绘制文本和图形。画笔对象有很多设置方法,大体分为两类:一类与图形绘制有关,一类与文本绘制有关。
Paint类中有如下方法:
1、图形绘制:
setArgb(int a,int r,int g,int b):设置绘制的颜色,a表示透明度,r、g、b表示颜色
setAlpha(int a):设置绘制的图形的透明度
setColor(int color):设置绘制的颜色
setAntiAlias(boolean a):设置是否使用抗锯齿功能,抗锯齿功能会消耗较大资源,绘制图形的速度会减慢
setDither(boolean a):设置是否使用图像抖动处理,会使图像颜色更加平滑饱满,更加清晰
setFileterBitmap(Boolean b):设置是否在动画中滤掉Bitmap的优化,可以加快显示速度;
setMaskFilter(MaskFilter mf):设置MaskFilter来实现滤镜的效果;
setColorFilter(ColorFilter cf):设置颜色过滤器,可以在绘制颜色时实现不同颜色的变换效果;
setPathEffect(PathEffect pe):设置绘制的路径的效果;
setShader(Shader s):设置Shader绘制各种渐变效果;
setShadowLayer(float r, int x, int y, int c):在图形下面设置阴影层,r为阴影角度,x和y为阴影在x轴和y轴上的距离,c为阴影的颜色;
setStyle(Paint.Style s):设置画笔的样式:FILL实心;STROKE空心;FILL_OR_STROKE同时实心与空心;
setStrokeCap(Paint.Cap c):当设置画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式;
setStrokeJoin(Paint.Join j):设置绘制时各图形的结合方式;
setStrokeWidth(float w):当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度;
setXfermode(Xfermode m):设置图形重叠时的处理方式;
2、文本绘制:
setTextAlign(Path.Align a):设置绘制的文本的对齐方式;
setTextScaleX(float s):设置文本在X轴的缩放比例,可以实现文字的拉伸效果;
setTextSize(float s):设置字号;
setTextSkewX(float s):设置斜体文字,s是文字倾斜度;
setTypeFace(TypeFace tf):设置字体风格,包括粗体、斜体等;
setUnderlineText(boolean b):设置绘制的文本是否带有下划线效果;
setStrikeThruText(boolean b):设置绘制的文本是否带有删除线效果;
setFakeBoldText(boolean b):模拟实现粗体文字,如果设置在小字体上效果会非常差;
setSubpixelText(boolean b):如果设置为true则有助于文本在LCD屏幕上显示效果;
3、其他方法:
getTextBounds(String t, int s, int e, Rect b):将页面中t文本从s下标开始到e下标结束的所有字符所占的区域宽高封装到b这个矩形中;
clearShadowLayer():清除阴影层;
measureText(String t, int s, int e):返回t文本中从s下标开始到e下标结束的所有字符所占的宽度;
reset():重置画笔为默认值。
setPathEffect(PathEffect pe):设置绘制的路径的效果:
常见有以下几种方案:
CornerPathEffect:可以用圆角来代替尖锐的角;
DathPathEffect:虚线,由短线和点组成;
DiscretePathEffect:荆棘状的线条;
PathDashPathEffect:定义一种新的形状并将其作为原始路径的轮廓标记;
SumPathEffect:在一条路径中顺序添加参数中的效果;
ComposePathEffect:将两种效果组合起来,先使用第一种效果,在此基础上应用第二种效果。
5.2、Canvas类
Canvas对象中可以绘制:
drawArc():绘制圆弧;
drawBitmap():绘制Bitmap图像
drawCircle():绘制圆圈
drawLine():绘制线条
drawOval();绘制椭圆
drawPath():绘制Path路径
drawPicture():绘制Picture图片
drawRect():绘制矩形
drawRoundRect():绘制圆角矩形
drawText():绘制文本
drawVertices():绘制顶点
Canvas对象的其他方法:
canvas.save():把当前绘制的图像保存起来,让后续的操作相当于是在一个新图层上绘制
canvas.restore():把当前画布调整到上一个save()之前的状态
canvas.translate(dx,dy):把当前画布的原点移到(dx,dy)点,后续操作都以(dx,dy)点作为参照
canvas.scale(x,y):把当前画布在水平方向上缩放x倍,竖直方向上缩放y倍
canvas.rotate(angle):将当前画布顺时针旋转angle度
onLayout()方法负责布局,大多数情况是在自定义ViewGroup中才会重写,主要用来确定子View在这个布局空间中的摆放位置
onLayout(boolean changed,int l,int t,int r,int b)方法有5个参数,其中changed表示这个控件是否有了新的尺寸或位置;l、t、r、b分别表示这个View相对于父布局的左右上下的位置。以下是onLayout()方法中常用的方法:
getChildCount():获取子View的数量
getChildAt(i):获取第i个子view
getWidth/Height():获取onMeasure()中返回的宽度和高度的测量值
child.getLayoutParams():获取到子View的LayoutParams对象
child.getMeasuredWidth/Height():获取onMeasure()方法中测量的子View的宽度和高度值
getPaddingLeft/Right/Top/Bottom():获取控件的四周内边距
child.layout(l,t,r,b):设置子View布局的上下左右边的坐标
7.1、generateLayoutParams()
generateLayoutParams()方法用在自定义ViewGroup中,用来指明子控件之间的关系,即与当前的ViewGroup对应的LayoutParams。我们只需要在方法中返回一个我们想要使用的LayoutParams类型的对象即可。在generateLayoutParams()方法中需要传入一个AttributeSet对象作为参数,这个对象是这个ViewGroup的属性集,系统根据这个ViewGroup的属性集来定义子View的布局规则,供子View使用。例如,在自定义流式布局中,我们只需要关心子控件之间的间隔关系,因此我们需要在generateLayoutParams()方法中返回一个new MarginLayoutParams()即可。
7.2、onTouchEvent()
onTouchEvent()方法用来监测用户手指操作。我们通过方法中MotionEvent参数对象的getAction()方法来实时获取用户的手势,有UP、DOWN和MOVE三个枚举值,分别表示用于手指抬起、按下和滑动的动作。每当用户有操作时,就会回掉onTouchEvent()方法。
7.3、onScrollChanged()
如果我们的自定义View / ViewGroup是继承自ScrollView / HorizontalScrollView等可以滚动的控件,就可以通过重写onScrollChanged()方法来监听控件的滚动事件。这个方法中有四个参数:l和t分别表示当前滑动到的点在水平和竖直方向上的坐标;oldl和oldt分别表示上次滑动到的点在水平和竖直方向上的坐标。我们可以通过这四个值对滑动进行处理,如添加属性动画等。
7.4、invalidate()
invalidate()方法的作用是请求View树进行重绘,即draw()方法,如果视图的大小发生了变化,还会调用layout()方法。一般会引起invalidate()操作的函数如下:
直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身;
调用setSelection()方法,请求重新draw(),但只会绘制调用者本身;
调用setVisibility()方法,会间接调用invalidate()方法,继而绘制该View;
调用setEnabled()方法,请求重新draw(),但不会重新绘制任何视图,包括调用者本身。
7.5、postInvalidate()
功能与invalidate()方法相同,只是postInvalidate()方法是异步请求重绘视图。
7.6、requestLayout()
requestLayout()方法只是对View树进行重新布局layout过程(包括measure()过程和layout()过程),不会调用draw()过程,即不会重新绘制任何视图,包括该调用者本身。
7.7、requestFocus()
请求View树的draw()过程,但只会绘制需要重绘的视图,即哪个View或ViewGroup调用了这个方法,就重绘哪个视图。