为什么要自定义View
android提供了很多控件供我们使用 但有些功能是系统所提供的实现不 了的 这时候我们就需要自定义一个View来实现我们所需要的效果. 在Android中所有的控件都直接或间接的继承自View,分View和ViewGroup两部分. 我们常用的一些View比如TextView,ImageView都是继承自View并添加了一些各自的特性,ViewGroup也是继承View但是它可以包含子View也就是我们经常用到的各种布局比如LinearLayout,RelativeLayout等。
如何自定义布局
所以我们要是实现一个自己的View那就集成View并添加一些自己需要的特性即可. 以下是一个最简单的自定义View
public class MVIew extends View {
/**
* 创建一个继承View的class
*View一共有四个构造器 这里先说两个
* 第一个构造器的参数就是context,这是在Activity实例化的时候所需的,比如Button button = new Button(context);
* 第二个构造器有两个参数 第一个同样是context 第二个参数AttributeSet放入了一些属性,这是我们在写XML文件时用到的比如
* android:layout_width="match_parent"
* android:layout_height="match_parent"如果不写函数的话是无法通过XML添加View
*/
public MVIew(Context context) {
super(context);
}
public MVIew(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.RED);
float radius = 50;
float cx = 50;
float cy = 50;
Paint paint = new Paint();
paint.setColor(Color.BLUE);
canvas.drawCircle(cx, cy, radius, paint);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
这个自定义的View就是画了一个圆圈,接下来就是让这个view加载到activity上了.第一种就是直接在Activity上实例化一个并通过addView(View)来添加
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MVIew mvIew = new MVIew(getApplicationContext());
RelativeLayout main_layout = (RelativeLayout) findViewById(R.id.main_layout);
main_layout.addView(mvIew);
}
}
第二种就是最常用的通过XML文件去添加
<cn.lipengfei.myview.MVIew
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
要注意的是标签名要写自定义的View的完整类名既包名.类名 以下是效果 效果很简单 就是一个红色的画板上有一个蓝色的半径为50的圆圈. 这里有一个问题 android:layout_width=”wrap_content” android:layout_height=”wrap_content” 明明width和height都是wrap_content为什么出来的辣么大.
测量
这涉及到View的测量. Android由一个MeasureSpec的类,可以通过它来说实现测量. MeasureSpec是一个int型的值,并且采用了位运算,高两位为测量的模式,低30位是具体的值. 由三种测量模式
- EXACTLY 精确模式 例如layout_height=”50dp”或是=”match_parent”
- AT_MOST 最大值模式 就是warp_content
- UNSPECIFIED 通过字面意思就是没有指定尺寸
Android通过onMeasure()测量View的大小,默认情况下是EXACTLY模式,所以刚才的例子虽然写了warp_concent依然没有起作用,如果想实现AT_MOST模式那就需要我们重写onMeasure()这个方法.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width_size = 100;
int height_size = 100;
int HEIGHT_MODE = MeasureSpec.getMode(heightMeasureSpec);
int HEIGHT_SIZE = MeasureSpec.getSize(heightMeasureSpec);
int WIDTH_MODE = MeasureSpec.getMode(widthMeasureSpec);
int WIDTH_SIZE = MeasureSpec.getSize(widthMeasureSpec);
if (HEIGHT_MODE == MeasureSpec.EXACTLY) {
height_size = HEIGHT_SIZE;
}else if (HEIGHT_MODE == MeasureSpec.AT_MOST){
height_size = Math.min(HEIGHT_SIZE,height_size);
}
if (WIDTH_MODE == MeasureSpec.EXACTLY){
width_size = WIDTH_SIZE;
}else if (WIDTH_MODE == MeasureSpec.AT_MOST){
width_size = Math.min(WIDTH_SIZE,width_size);
}
setMeasuredDimension(width_size,height_size);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
效果如下 可以看到这个时候控件的大小已经变成所设定的默认值了.
onDraw(Canvas canvas)
测量完成后就有了大小接下来就是内容了,我们需要在view上显示什么就重写onDraw来实现,以上例子是通过onDraw(Canvas canvas)绘制了一个圆圈. 首先要说的是Canvas,刚才的例子就是通过 canvas.drawCircle(cx, cy, radius, paint);这样一个方法来画了一个圆.这里不对Canvas展开来说,只是说一些Canvas的简单用法. Canvas就是一个画板,可以在这个画板上话各种各样的东西
Paint paint = new Paint()
paint.setColor(Color.BLACK)
paint.setStrokeWidth(10)
canvas.drawLine(0,0,100,100,paint)
这就是在画了一条宽度为10的黑色的线drawLine()的前两个参数为开始点的坐标后两个为结束点的坐标,最后一个参数就是paint; 还有很多方法可以调这里就不一一列举了,可以根据canvas.的提示来试试 晚些时候我会把我总结的一些方法写出来方便初学者来看看. 除了Canvas外还有一个Paint的对象也总用到 这个是画笔的意思,比如化园的例子就是通过这个画笔来设定的园的颜色,还有画线的例子也是通过画笔来设定宽度值,如果我们吧之前的代码改一下
canvas.drawColor(Color.RED);
float radius = 50;
float cx = 50;
float cy = 50;
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
canvas.drawCircle(cx, cy, radius, paint);
其他的都没有变 只是添加了三个
paint.setAntiAlias(true)
paint.setStyle(Paint.Style.STROKE)
paint.setStrokeWidth(3)
效果就是这个样子 在样式里面设置了STROKE表示只描边也就是空心效果与之相对的还有两个属性分别是FILL_AND_STROKE,FILL,刚开始有些人搞不清这俩有啥区别,譬如我.FILL就是填充上也就是一个实心的圆圈,FILL_AND_STROKE是不仅填充成一个实心圆而且还有边框,Paint并没有单独给STROKE设置颜色的方法(至少我没发现) 这俩的区别我举个例子就很清楚了
paint.setStyle(Paint.Style.FILL)
paint.setStrokeWidth(50)
这是设置成FILL并添加边框出来的效果
大家可以试试无论setStrokeWidth(50)填入多少其效果是没有任何区别的因为他就没有边框.
paint.setStyle(Paint.Style.FILL_AND_STROKE)
paint.setStrokeWidth(50)
改成FILL_AND_STROKE后 这样的效果圆已经超出去一部分了,设定了填充+边框 不仅由填充效果还有边框,给边框设定了宽度值,再加上我设定的圆心坐标正好是半径值 所以边框的部分就出去了.但如果我们不设setStrokeWidth的话 这两者其实是没有什么区别的.另外要注意的是StrokeWidth的值是在园外面的,也就是说它并不占用园的实际大小,比如园的半径是100,这个半径指的是填充的部分,当把StrokeWidth设定为100时 这个圆会变大 刚才我提到Paint并没有设置STROKE的颜色的方法,所以我是通过两个画笔来实现的,通过多个画笔多个画布叠加图层来实现我们想要的各种效果 比如我现在想要一个边框为蓝色的填充为黑色的圆圈.
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.RED);
float radius = 200;
float cx = 500;
float cy = 500;
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
Paint paint2 = new Paint();
paint2.setColor(Color.BLUE);
paint2.setStyle(Paint.Style.STROKE);
paint2.setStrokeWidth(30);
canvas.drawCircle(cx, cy, radius, paint);
canvas.drawCircle(cx, cy, radius, paint2);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
需要注意的是canvas.draw的先后顺序 因为这两个圆是一样大小的 只是一个多一个边框而已 所以先后是无所谓的 |