1.构造函数
无论是我们继承系统View还是直接继承View,都需要对构造函数进行重写,构造函数有多个,至少要重写其中一个才行。如我们新建的CircleView,
public class CircleView extends View { //在java代码里new的时候会用到 public CircleView(Context context) { super(context); } // 在xml布局文件中使用时自动调用 public CircleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } //不会自动调用,如果有默认style时,在第二个构造函数中调用 public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } /** * 只有在API版本>21时才会用到 * 不会自动调用,如果有默认style时,在第二个构造函数中调用 */ public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); }
2.继承View重写onDraw方法
解决问题:直接继承自View和ViewGroup的控件,padding是默认无法生效的,需要自己处理。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); final int paddingLeft=getPaddingLeft(); final int paddingRight=getPaddingRight(); final int paddingTop=getPaddingTop(); final int paddingBottom=getPaddingBottom(); 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); }
中心思想就是在绘制的时候考虑到View四周的空白即可。
3.继承View重写onMeasure方法
解决问题:直接继承自View的控件,如果不对wrap_content做特殊处理,那么使用wrap_content就相当于使用match_parent。
@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&&heightSpecSize==MeasureSpec.AT_MOST){ setMeasuredDimension(200,200); }else if(widthSpecMode==MeasureSpec.AT_MOST){ setMeasuredDimension(200,heightSpecSize); } else if (heightSpecMode==MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize,200); } }
4.添加自定义属性
- 在values目录下面创建自定义属性的XML
- 在View的构造方法中解析自定义属性的值并做出相应的处理。
- 在布局文件中使用自定义属性
第一步:在values目录下面创建自定义属性的XML,比如attrs.xml,文件内容如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="CircleView"> <attr name="circle_color" format="color"/> </declare-styleable> </resources>
在XML中声明了一个自定义属性集合“CircleView”,在这个集合里面可以有很大自定义属性,这里只定义了一个格式为“Color”的属性“circle_color”,这里的格式color指的是颜色。
第二步:在View的构造方法中解析自定义属性的值并做出相应的处理。
public CircleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); TypedArray a=context.obtainStyledAttributes(attrs,R.styleable.CircleView); mColor=a.getColor(R.styleable.CircleView_circle_color,Color.RED); a.recycle(); init(); }
首先加载自定义属性集合CircleView,接着解析CircleView属性集合中的circle_color属性,它的id为R.styleable.CircleView_circle_color。如果在使用时没有指定circle_color这个属性,那么就会选择红色作为默认的颜色值,解析完自定义属性后,通过recycle方法来释放资源,这样CircleView中所做的工作就完成了。
第三步:在布局文件中使用自定义属性
<?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="match_parent" tools:context=".MainActivity" android:background="#ffffff" android:orientation="vertical"> <com.example.myview.CircleView android:id="@+id/circleView1" android:layout_width="wrap_content" android:layout_height="100dp" android:background="#000000" android:layout_margin="20dp" android:padding="20dp" app:circle_color="@color/green" /> </LinearLayout>
运行:
5.完整代码
CircleView类:
public class CircleView extends View { private int mColor= Color.RED; private Paint mPaint=new Paint(Paint.ANTI_ALIAS_FLAG); public CircleView(Context context) { super(context); init(); } public CircleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); TypedArray a=context.obtainStyledAttributes(attrs,R.styleable.CircleView); mColor=a.getColor(R.styleable.CircleView_circle_color,Color.RED); a.recycle(); init(); } public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init(){ mPaint.setColor(mColor); } @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&&heightSpecSize==MeasureSpec.AT_MOST){ setMeasuredDimension(200,200); }else if(widthSpecMode==MeasureSpec.AT_MOST){ setMeasuredDimension(200,heightSpecSize); } else if (heightSpecMode==MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize,200); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); final int paddingLeft=getPaddingLeft(); final int paddingRight=getPaddingRight(); final int paddingTop=getPaddingTop(); final int paddingBottom=getPaddingBottom(); 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); } }
布局文件:
<?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="match_parent" tools:context=".MainActivity" android:background="#ffffff" android:orientation="vertical"> <com.example.myview.CircleView android:id="@+id/circleView1" android:layout_width="wrap_content" android:layout_height="100dp" android:background="#000000" android:layout_margin="20dp" android:padding="20dp" app:circle_color="@color/green" /> </LinearLayout>