自定义View
自定义View
自定义View
我新建了一个Customization包,然后创建CustomizationView类
1.简单的一个自定义View
public class CustomizationView extends View {
private int color = Color.RED;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CustomizationView(Context context) {
super(context);
init();
}
public CustomizationView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomizationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public CustomizationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
private void init(){
mPaint.setColor(color);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int radius = Math.min(width,height)/2;
canvas.drawCircle(width/2,height/2,radius,mPaint);
}
}
2.代码解析
代码其实很好理解,先是
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
是用于创建一个画笔对象并初始化的代码。Paint.ANTI_ALIAS_FLAG
是一个标志,表示在绘制图形时使用抗锯齿功能,使得图形边缘更加平滑。
不仅有Paint.ANTI_ALIAS_FLAG还有
然后重写四个构造方法,每个里面都调用init()方法
我记得我之前没有重写第二个还是第三个还是第四个最后项目报错了,把那几个重写方法加上就好了
所以我们看看这几个重写方法有什么区别
3.重写方法的区别
传入的参数 | 作用 |
---|---|
Context context | 这是最基本的构造函数,接受一个Context 参数。Context 表示当前的上下文,用于提供应用程序的全局信息和资源。在这个构造函数中,它调用了父类View 的相应构造函数super(context) 来执行基本的初始化 |
Context context, @Nullable AttributeSet attrs | 除了Context 之外,还有一个AttributeSet 参数。AttributeSet 是一个接口,用于获取 XML 布局文件中的属性值。这个构造函数在调用父类的相应构造函数时传递了这两个参数 |
Context context, @Nullable AttributeSet attrs, int defStyleAttr | 还有一个defStyleAttr 参数。defStyleAttr 是一个主题属性的引用,用于指定默认的样式资源。在这个构造函数中,它调用了父类的相应构造函数,并传递了这三个参数 |
Context context, @Nullable AttributeSet attrs, int defStyleAttr, |
defStyleRes 是一个样式资源的引用,用于指定视图的默认样式。在这个构造函数中,它调用了父类的相应构造函数,并传递了这四个参数 |
简单来说,第一个就是只传递一个上下文;
第二个多传递一个AttributeSet attrs,用来获取XML属性值,比如margin这些的
第三个多比第二个多传递一个int defStyleAttr,用来获取应用程序的theme
第四个比第三个多传递一个int defStyleRes,用来获得style
然后在init()里面
mPaint.setColor(color);
设置画笔的颜色
最后重写onDraw()
**canvas.drawCircle(width/2,height/2,radius,mPaint);**中前面的两个参数是指圆心的坐标。它们决定了圆在Canvas
上的位置。
radius是半径,mPaint是那个画笔
4.问题
这是最开始的图
1.margin与padding
如果我在自定义View的xml中写上
android:layout_marginTop="200dp"
后
view确实向下了,
但是如果我用的不是margin,而是padding
图却是
没有变化
这是因为margin属性是由ViewGroup控制的,不需要自定义的view的特殊处理
但是padding不是这样的,直接继承view或者viewGroup,padding默认无法生效
解决的办法呢就是在**onDrawn()**中自己进行修改
这是我的xml文件
<Customization.CustomizationView
android:layout_width="100dp"
android:layout_height="200dp"
android:paddingTop="50dp"
android:background="@color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
在onDraw中
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
int width = getWidth()-paddingLeft-paddingRight;
int height = getHeight()-paddingBottom-paddingTop;
int radius = Math.min(width,height)/2;
canvas.drawCircle(paddingLeft+width/2,paddingTop+height/2,radius,mPaint);
}
效果如图
2.wrap_content的问题
我在xml文件中
<Customization.CustomizationView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:paddingTop="50dp"
android:background="@color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
我宽度明明是wrap_content,最后效果和match_parent差不多
这是为什么呢?
在view的onMeasure中我们曾经讲过
当view是wrap_content的时候,它的MeasureSpec是MeasureSpec.AT_MOST。
它的大小为父类ViewGroup的剩余的大小,所以显示出来的效果和match_parent差不多
以下是解决wrap_content问题的相关代码
在onMeasure中重写
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode == MeasureSpec.AT_MOST&&heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(500,500);
}
else if(widthSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(500,heightSpecSize);
}
else if(heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,500);
}
}
现在
效果如下
5.自定义属性
比如:我想把这个圆在没有指定其他颜色的情况下是红色,指定后就是你指定的那个颜色
现在在values目录下,创建**attrs_**开头的文件,比如说:
attrs_circle_view文件夹
<resources>
<declare-styleable name="CustomizationView">
<attr name="circle_color" format="color"/>
</declare-styleable>
</resources>
然后在自定义view那块
public CustomizationView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomizationView);
color = a.getColor(R.styleable.CustomizationView_circle_color,Color.RED);
a.recycle();
init();
}
a.recycle()用来释放资源
然后在xml文件中
<Customization.CustomizationView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:paddingTop="50dp"
android:background="@color/black"
app:circle_color="@color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
设定颜色为黑色
我们运行一下:
但是我试了一下,这个
public CustomizationView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomizationView);
color = a.getColor(R.styleable.CustomizationView_circle_color,Color.RED);
a.recycle();
init();
}
必须得在第二个有参构造里面写,如果不在第二个写的话,在其他的有参构造上写这个没有效果
自定义ViewGroup
比如我要实现一个类似ViewPager的控件,也可以说是类似于水平方向的LinearLayout,子元素可以水平滑动,且子元素的内部可以竖直滑动
1.onMeasure()重写
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = 0;
int measuredHeight = 0;
final int childCount = getChildCount();
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if(childCount==0){
setMeasuredDimension(0,0);
}
else if(widthSpecMode == MeasureSpec.AT_MOST&&heightSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measuredWidth = childView.getMeasuredWidth()*childCount;
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(measuredWidth,measuredHeight);
}
else if(widthSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measuredWidth = childView.getMeasuredWidth()*childCount;
setMeasuredDimension(measuredWidth,heightSpaceSize);
}
else if(heightSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(widthSpaceSize,measuredHeight);
}
}
判断是否有子元素,没有子元素就把宽/高设置为0
有的话,判断哪些为wrap_content
然后自己进行处理
2.onLayout()重写
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int childCount = getChildCount();
for(int i = 0;i<childCount;i++){
final View childView = getChildAt(i);
if(childView.getVisibility()!=View.GONE){
final int childWidth = childView.getMeasuredWidth();
childView.layout(childLeft,0,childLeft+childWidth,childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}