[Android]自定义view
目录
为什么需要自定义view?
View的绘制流程
自定义View
OnMeasure
代码部分
三种测量模式
1. EXACTLY
2. AT_MOST
3. UNSPECIFIED
重写OnDraw方法
自定义属性
为什么需要自定义view?
Android 提供了大量现成控件,但当你需要实现独特的视觉表现、复杂的动画或者当前现成的控件已经没有办法去满足你的需求时,这时可以自定义view来完成我们所期望的需求;
View的绘制流程
-
onMwasure
:测量View宽高 -
onLayout
: 计算View的位置并布局 -
onDraw
:
绘制View
自定义View
这里有两个我们必须重写的方法:
public xxview(Context context) {super(context);
}public xxview(Context context, @Nullable AttributeSet attrs) {super(context, attrs);
}
第二个方法中有一个参数:AttributeSet
-
你在布局文件里写
<com.example.xxView ... />
并设置属性时,系统会把这些属性打包成一个AttributeSet
对象传给构造函数; -
我们可以通过它,拿到在 XML 中声明的属性,从而在代码里初始化 View 的样式;
总结来说,就是拿到XML文件中的属性并且设立初始值;
OnMeasure
前文提到:这个方法用来测量自定义view的宽高尺寸;
那么为什么需要测量宽高尺寸呢?不是应该都在XML文件中定义过了么?
但其实,我们在XML文件中有三种定义,分别是wrap_content,match_parent
还有精确值;前两者并没有指定具体的大小,所以这个时候需要我们手动去处理和设置尺寸;
当然,在View类中提供的onMeasure方法中,也有设置,但是如果你在XML文件中写的是wrap_content
,那么最后显示的效果是充满父布局的,为什么会这样?稍后会有解答...
重写OnMeasure方法:
代码部分
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int wigth = xxonmeasure(100, widthMeasureSpec);int height = xxonmeasure(200, heightMeasureSpec);setMeasuredDimension(wigth,height);}protected int xxonmeasure(int defaultsize,int measureSpec) {int size = defaultsize;int mode = MeasureSpec.getMode(measureSpec);int sizes= MeasureSpec.getSize(measureSpec);switch (mode){case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY: {size = sizes;break;}case MeasureSpec.UNSPECIFIED:{size= defaultsize;break;}}return size;
}
在了解这些之前,我们先来看看测量模式:
三种测量模式
1. EXACTLY
-
含义:父容器已经指定了 确切的大小;
-
什么情况下:子 View 的 layout_width / layout_height 是具体值或者是
match_parent
; -
那为什么
match_parent
是精确模式呢?因为父容器的大小已经确定,所以自然当前的情况也已经确定了;因为尺寸大小的确定是自上而下的;
2. AT_MOST
-
含义:父容器指定了一个 最大的值;
-
什么情况下:子 View 的 layout_width / layout_height 是
wrap_content
; -
为什么?因为当前view设定的是包含当前内容即可,没有办法确定具体的值,我们只能获取到父容器的值,知道最大限制,所以此时我们的模式就是知道它的最大值;
3. UNSPECIFIED
-
含义:父容器对 View 的大小没有限制;
-
什么情况下:View 自己决定大小,比如
ScrollView
的子元素高度可能是无限制的,能撑多大就多大; -
这种情况比较少见;
在onMeasure方法中,有两个我们不太熟悉的参数: int widthMeasureSpec
, int heightMeasureSpec
widthMeasureSpe
包含是测量模式和测量的大小,其实准确来说是父view提供的参考大小,最终的大小我们需要自己处理的;
那么一个Int型的数据为什么能包含测量模式和大小呢?一个Int占用32个bit,三种测量模式只需要两个bit,所以前两个bit存的是测量模式,后30存的是测量的大小;我们可以直接用封装好的方法去获取:
-
获取模式
int mode = MeasureSpec.
getMode
(measureSpec);
-
获取大小
int sizes= MeasureSpec.
getSize
(measureSpec);
setMeasuredDimension(wigth,height);
这行代码是设置确定好最终的尺寸;
现在重新阅读代码,去理解写的逻辑;
switch (mode){case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY: {size = sizes;break;}case MeasureSpec.UNSPECIFIED:{size= defaultsize;break;}}
这里如果模式是MeasureSpec.AT_MOST,那么 size = sizes;就是说最终的大小是父容器的大小,当前你可以根据你自己想要的效果去设置不同的值;
重写OnDraw方法
用 canvas画板,我们还可以绘制其他的图形,颜色等,这里绘制了圆形;
@Override
protected void onDraw(@NonNull Canvas canvas) {super.onDraw(canvas);int r = getMeasuredWidth() / 2;int X = getLeft() + r;int Y = getTop() + r;Paint paint = new Paint();paint.setColor(Color.GREEN);canvas.drawCircle(X, Y, r, paint);
}
canvas.drawCircle(X, Y, r, paint);
前两个参数是圆的横纵坐标,第三个参数是半径,第四个参数是对颜色等外观方面的设置
自定义属性
在valus下:申明我们的属性,format
是说明类型,比如这里的类型就是尺寸类型比如sp,dp;
<resources><declare-styleable name="xxview"><attr name="defaultsize" format="dimension"></attr></declare-styleable>
</resources>
然后在XML文件里面去定义值;
在构造方法里去设置默认值;
public xxview(Context context, @Nullable AttributeSet attrs) {super(context, attrs);TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.xxview);int defalutSize = a.getDimensionPixelSize(R.styleable.xxview_defaultsize, 100);a.recycle();
}
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.xxview);
:用来从 XML 中解析你自定义的属性集合 xxview;
a.getDimensionPixelSize(R.styleable.
xxview_defaultsize
, 100);
这里获取的就是 XML 里 <xxview app:defaultsize="150dp" />
的值,如果 XML 没有写这个属性,就用 第二个参数 100
当默认值;
a.recycle();
回收这个对象;
本次分享到此结束;