源码地址
1.继承系统控件的自定义view
在这里以TextView为例,自定义一个斜划线的TextView,效果如图
这个比较简单,主要是重写onDraw()方法,直接上代码了
package com.test.ck.customview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.TextView;
/**
* Created by ck on 2019/4/10.
* 继承系统控件
*/
public class CustomTextView extends TextView {
private Paint paint;
public CustomTextView(Context context) {
super(context);
init();
}
public CustomTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
invalidate();
}
public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// Paint.ANTI_ALIAS_FLAG :抗锯齿标志
// Paint.FILTER_BITMAP_FLAG : 使位图过滤的位掩码标志
// Paint.DITHER_FLAG : 使位图进行有利的抖动的位掩码标志
// Paint.UNDERLINE_TEXT_FLAG : 下划线
// Paint.STRIKE_THRU_TEXT_FLAG : 中划线
// Paint.FAKE_BOLD_TEXT_FLAG : 加粗
// Paint.LINEAR_TEXT_FLAG : 使文本平滑线性扩展的油漆标志
// Paint.SUBPIXEL_TEXT_FLAG : 使文本的亚像素定位的绘图标志
// Paint.EMBEDDED_BITMAP_TEXT_FLAG : 绘制文本时允许使用位图字体的绘图标志
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(getResources().getColor(R.color.colorAccent));
paint.setStrokeWidth(2);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
//画斜线
canvas.drawLine(0,0,width,height,paint);
}
}
然后在xml文件中直接使用即可
2.继承View的自定义view
- 这种自定义相对于继承系统控件来说,就会复杂一点,一般情况下,不止要重写onDraw()方法,还要考虑到wrap_content属性以及padding属性以及自定义属性.
- 这次我们自定义一个蓝色背景的粉色矩形,如图
先看下自定义MyCustomView的代码
package com.test.ck.customview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by ck on 2019/4/10.
* 继承view
*/
public class MyCustomView extends View {
private Paint paint;
private int color;
public MyCustomView(Context context) {
super(context);
init();
}
public MyCustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//获取自定义的属性
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView);
color = typedArray.getColor(R.styleable.MyCustomView_viewColor, Color.BLACK);
typedArray.recycle();
init();
}
public MyCustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//获取自定义属性值
// Paint.ANTI_ALIAS_FLAG :抗锯齿标志
// Paint.FILTER_BITMAP_FLAG : 使位图过滤的位掩码标志
// Paint.DITHER_FLAG : 使位图进行有利的抖动的位掩码标志
// Paint.UNDERLINE_TEXT_FLAG : 下划线
// Paint.STRIKE_THRU_TEXT_FLAG : 中划线
// Paint.FAKE_BOLD_TEXT_FLAG : 加粗
// Paint.LINEAR_TEXT_FLAG : 使文本平滑线性扩展的油漆标志
// Paint.SUBPIXEL_TEXT_FLAG : 使文本的亚像素定位的绘图标志
// Paint.EMBEDDED_BITMAP_TEXT_FLAG : 绘制文本时允许使用位图字体的绘图标志
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
// paint.setColor(getResources().getColor(R.color.colorAccent));
paint.setColor(color);
paint.setStrokeWidth(2);
}
//直接继承view,在xml中的wrap_content和match_content效果一样,
// wrap_content若要生效,需要重写onmeasure方法,指定一个默认的宽和高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//测量大小
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//AT_MOST:最大模式,对应于wrap_comtent属性,子View的最终大小是父View指定的SpecSize值,并且子View的大小不能大于这个值
//EXACTLY:精确模式,对应于 match_parent 属性和具体的数值,父容器测量出 View所需要的大小,也就是SpecSize的值
if (MeasureSpec.AT_MOST == widthMode && MeasureSpec.AT_MOST == heightMode){
//宽高均是wrap_content,设置默认宽高
setMeasuredDimension(500,500);
}else if (MeasureSpec.AT_MOST == widthMode){
//宽是wrap_content,设置默认宽度
setMeasuredDimension(500,heightSize);
}else if (MeasureSpec.AT_MOST == heightMode){
//高是wrap_content,设置默认高度
setMeasuredDimension(widthSize,500);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//整个控件的宽和高(包含padding)
int width = getWidth();
int height = getHeight();
//若要padding生效,需要做以下处理
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
//根据padding值画正方形
canvas.drawRect(paddingLeft,paddingTop,width - paddingRight,height - paddingBottom,paint);
}
}
然后是在xml中使用
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="wrap_content"
android:orientation="vertical"
tools:context="com.test.ck.customview.MainActivity">
<com.test.ck.customview.MyCustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:padding="20dp"
app:viewColor="@color/colorAccent" />
</LinearLayout>
其中主要注意有对padding属性的处理,对wrap_content属性的处理以及自定义属性
- 对padding属性的处理
如果只是在xml文件中设置了padding值,而在自定义的MyCustomView中不做任何处理的话,会发现padding完全无效,要使padding生效需要在onDraw方法中做如下处理
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//整个控件的宽和高(包含padding)
int width = getWidth();
int height = getHeight();
//若要padding生效,需要做以下处理
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
//根据padding值画正方形
canvas.drawRect(paddingLeft,paddingTop,width - paddingRight,height - paddingBottom,paint);
}
- 对wrap_content属性处理
在未处理之前效果是和match_parent是一样的,产生的原因在view的measure流程一文中已经介绍过,在这儿就不在多做赘述.其实对这种情况,只需要在onMeasure的时候做下处理就好了,当设置为wrap_content时,设置一个默认的宽和高即可
//直接继承view,在xml中的wrap_content和match_content效果一样,
// wrap_content若要生效,需要重写onmeasure方法,指定一个默认的宽和高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//测量大小
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//AT_MOST:最大模式,对应于wrap_comtent属性,子View的最终大小是父View指定的SpecSize值,并且子View的大小不能大于这个值
//EXACTLY:精确模式,对应于 match_parent 属性和具体的数值,父容器测量出 View所需要的大小,也就是SpecSize的值
if (MeasureSpec.AT_MOST == widthMode && MeasureSpec.AT_MOST == heightMode){
//宽高均是wrap_content,设置默认宽高
setMeasuredDimension(500,500);
}else if (MeasureSpec.AT_MOST == widthMode){
//宽是wrap_content,设置默认宽度
setMeasuredDimension(500,heightSize);
}else if (MeasureSpec.AT_MOST == heightMode){
//高是wrap_content,设置默认高度
setMeasuredDimension(widthSize,500);
}
}
- 定义属性
这里我们以自定义一个viewColor属性为例(改变view的颜色)
首先在values目录下创建 attrs.xml文件,定义name和format
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyCustomView">
<attr name="viewColor" format="color" />
</declare-styleable>
</resources>
然后是在自定义MyCustomView中解析自定义的属性,并在init中设置画笔的颜色
public MyCustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView);
color = typedArray.getColor(R.styleable.MyCustomView_viewColor, Color.BLACK);
typedArray.recycle();
init();
}
private void init() {
//获取自定义属性值
// Paint.ANTI_ALIAS_FLAG :抗锯齿标志
// Paint.FILTER_BITMAP_FLAG : 使位图过滤的位掩码标志
// Paint.DITHER_FLAG : 使位图进行有利的抖动的位掩码标志
// Paint.UNDERLINE_TEXT_FLAG : 下划线
// Paint.STRIKE_THRU_TEXT_FLAG : 中划线
// Paint.FAKE_BOLD_TEXT_FLAG : 加粗
// Paint.LINEAR_TEXT_FLAG : 使文本平滑线性扩展的油漆标志
// Paint.SUBPIXEL_TEXT_FLAG : 使文本的亚像素定位的绘图标志
// Paint.EMBEDDED_BITMAP_TEXT_FLAG : 绘制文本时允许使用位图字体的绘图标志
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
// paint.setColor(getResources().getColor(R.color.colorAccent));
paint.setColor(color);
paint.setStrokeWidth(2);
}
需要注意的是在xml中使用自定义属性的的时候,需要添加 xmlns:app=“http://schemas.android.com/apk/res-auto”,然后就可使用viewColor属性了(app是自己定义的,可以是任何的名字)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="wrap_content"
android:orientation="vertical"
tools:context="com.test.ck.customview.MainActivity">
<com.test.ck.customview.MyCustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:padding="20dp"
app:viewColor="@color/colorAccent" />
</LinearLayout>