Android自定义View

自定义View

一、自定义View的分类

  • 继承View或者ViewGroup类,重写onDraw方法进行绘制,调用invalidate方法重新绘制,常见的有绘制钟表,绘制圆形进度条

  • 自定义组合控件,即将几种控件组合起来形成一个新的控件,这个新的组合控件就会整合了原来每一个控件的功能,常用的有自定义页面的ToolBar,自定义输入框

  • 继承某一个控件,在该控件的基础之上添加新的功能。listview上下滚动定位功能

二、构造函数

public class MyCustomView extends View {

    /**
     *   第一个构造函数
     */    
    public MyCustomView(Context context) {
          super(context, null);    
    } 
    	
    /**
     *   第二个构造函数
     */    
    public MyCustomView(Context context, AttributeSet attrs) {
          super(context, attrs, 0);
           // TODO:获取自定义属性    
    } 
    
    /**
    *   第三个构造函数
    */
    public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs, defStyle);
           // TODO:获取自定义属性    
    }
    
    /**
    *  第四个构造函数
    */
    public MyCustomView(Context context,  AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    	super(context, attrs, defStyleAttr, defStyleRes); 
    }

自定义View通常有四个构造函数,需要重写哪个函数,具体看如下:

  • 第一个构造函数:是在java文件中通过new对象,创建视图的时候调用,如果从中填充,则不会调用这个构造函数,参数需要一个上下文对象

  • 第二个构造函数:用于XML文件中通过ayout文件实例化使用,会把xml中的参数通过attrs带入,方法的参数除了上下文对象,还需要一个attrs(主要是获取布局文件中自定义的属性)

  • 第三个构造函数:也适用于XML文件中通过ayout文件实例化使用,与第二个构造函数的区别在于有第三个参数defStyle,它是Theme中的默认样式;

  • 第四个构造函数:只有当第三个参数为0或者没有定义defStyleAttr时,第四个参数才起作用,它是style的引用,高版本才支持,所以一般不会用到。R.style中系统为view定义了很多默认主题Theme,主题中有对某些属性的默认赋值。

在实际使用中,系统自己调用的构造函数只有一个参数和两个参数的构造函数, 三个,四个参数的构造函数,通常由我们自己去主动调用。

一般定义view时,View的四个构造函数时相互调用的,调用其中的一个内部会调用相应的其他构造函数,例如Button,Button内部指定了默认的com.android.internal.R.attr.buttonStyle

三、自定义属性

添加自定义属性主要是通过declare-styleable标签为其配置自定义属性。

具体做法是: 在res/values/目录下增加一个resources xml文件,示例如下(res/values/attrs_my_custom_view.xml):

<resources>
    <declare-styleable name="MyCustomView">
        <attr name="TextColor" format="color" />
        <attr name="Count" format="integer" />
        <attr name="bgColor" format="color" />
        <attr name="custom_attr" format="string" />
        <attr name="custom_attr1" format="string1" />
        <attr name="custom_attr2" format="string2" />
    </declare-styleable>
    <attr name="custom_attr5" format="string" />
</resources

所有resources文件中声明的属性都会在R.attr类中生成对应的成员变量:

public final class R {
    public static final class attr {
        public static final int custom_attr1=0x7f012538;
        public static final int custom_attr2=0x7f016539;
        public static final int custom_attr3=0x7f26303a;
    }
}

但是声明在标签中的属性,系统还会在R.styleable类中生成相关的成员变量:

public static final class styleable {
        public static final int[] MyCustomView = {
            0x7f012538, 0x7f016539, 0x7f26303a
        };
        public static final int MyCustomView_custom_attr1 = 0;
        public static final int MyCustomView_custom_attr2 = 1;
        public static final int MyCustomView_custom_attr3 = 2;
}

可以看出,R.styleable.MyCustomView是一个数组,其中的元素值恰好就是R.attr.custom_attr1~R.attr.custom_attr4的值.而下面的MyCustomView_custom_attr1~MyCustomView_custom_attr4正好就是其对应的索引。

四、获取自定义属性

对于在上面定义的自定义属性,我们一般在第二个构造函数或第三个构造函数中获取,下面以三个参数的构造函数为例:

public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView);

    Color textColor= ta.getColor(R.styleable.MyCustomView_TextColor,Color.parseColor("#fff"));
    int count= ta.getInteger(R.styleable.MyCustomView_TextColor,0);
    Color bgColor = ta.getColor(R.styleable.MyCustomView_bgColor,Color.parseColor("#000"));
    String attr1 = ta.getString(R.styleable.MyCustomView_custom_attr1);
    String attr2 = ta.getString(R.styleable.MyCustomView_custom_attr2);
    String attr3 = ta.getString(R.styleable.MyCustomView_custom_attr3);
}

在上面的代码中,用到了context.obtainStyledAttributes()这个函数,可能有些人对这个函数不太了解
下面让我们去看看它的源码:

/**
 * Retrieve styled attribute information in this Context's theme.  See
 * {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
 * for more information.
 *
 * @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
 */
public final TypedArray obtainStyledAttributes(
        AttributeSet set, @StyleableRes int[] attrs) {
    return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
}

注释的翻译:检索上下文主题中的样式化属性信息。看到 {@link android.content.res.Resources。主题#obtainStyledAttributes(AttributeSet, int[], int, int)}了解更多信息。@see android.content.res.Resources。主题#obtainStyledAttributes(AttributeSet, int[], int, int)

通过对源码的追踪,我们发现context的两个参数的obtainStyledAttributes方法最终是调用了Theme的4个参数的obtainStyledAttributes方法。让我们来看一下这个函数的源码实现:

public TypedArray obtainStyledAttributes(AttributeSet set,
        @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
    return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr, defStyleRes);
}

TypedArray obtainStyledAttributes( Resources.Theme wrapper,
        AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
    synchronized (mKey) {
        final int len = attrs.length;
        final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);

        // XXX note that for now we only work with compiled XML files.
        // To support generic XML files we will need to manually parse
        // out the attributes from the XML file (applying type information
        // contained in the resources and such).
        final XmlBlock.Parser parser = (XmlBlock.Parser) set;
        mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
                array.mDataAddress, array.mIndicesAddress);
        array.mTheme = wrapper;
        array.mXml = parser;
        return array;
    }
}

注释太长,就不给大家往出粘贴了,有兴趣的可以去看一看,

这里就不做过多的源码讲解,而是把这四个参数的含义解释给大家:

  1. AttributeSet set: 属性值的集合。

  2. int[] attrs: 我们自定义属性集合在R类中生成的int型数组.这个数组中包含了自定义属性的资源ID。

  3. int defStyleAttr: 这是当前Theme中的包含的一个指向style的引用.当我们没有给自定义View设置declare-styleable资源集合时,默认从这个集合里面查找布局文件中配置属性值.传入0表示不向该defStyleAttr中查找默认值。

  4. int defStyleRes: 这个也是一个指向Style的资源ID,但是仅在defStyleAttr为0或者defStyleAttr不为0但Theme中没有为defStyleAttr属性赋值时起作用。

由于一个属性可以在很多地方对其进行赋值,包括: XML布局文件中、decalare-styleable、theme中等,它们之间是有优先级次序的,按照优先级从高到低排序如下:

属性赋值优先级次序表:
在布局xml中直接定义 > 在布局xml中通过style定义 > 自定义View所在的Activity的Theme中指定style引用 > 构造函数中defStyleRes指定的默认值

五、主要方法

(一)、onMeasure():用于测量子控件的宽高

普通View的MeasureSpec的创建规则
MeasureSpec在很大程度上决定了一个View的尺寸规格,大小
1、模式:exactly, at_most, unspecified:

  • Exactly :是写出具体的dp值
  • At_most :一般对应wrap_content,最大值不能超过父控件宽高
  • Unspecified :一般在scrollView或者listview中,要多大就多大

2、常见的三个方法:

  • makeMeasureSpec(int size ,int mode) :makeMeasureSpec()方法的作用将size 和 mode 打包成一个32位的int值,之所以这样做就是为了减少内存的分配。返回值为打包成的int类型值measureSpec 。

  • getMode(int measureSpec) :getMode是根据传入的int 类型值,解析获得模式

  • getSize(int measureSpec) : getSize 是根据传入的int 类型值,解包成为size

注意只处理AT_MOST情况也就是wrap_content,其他情况则沿用系统的测量值即可。

setMeasuredDimension会设置View宽高的测量值,只有setMeasuredDimension调用之后,才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。

如果我们不处理AT_MOST情况,那么即使设置了wrap_content,最终的效果也和match_parent一样,这是因为这种情况下,view的SpecSize就是父容器测量出来可用的大小。

(二)、onLayout():用于摆放子控件在父控件中的位置

只有ViewGroup才能让子控件显示在自己的什么位置,也就是说,这个方法是ViewGroup和ViewGroup的子类才会有的方法:

1、getWidth()方法和getMeasureWidth()的值基本相同。
2、但getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。
3、另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。
4、我们在ViewGroup中重写onLayout的目的:
就是设置当前View与其所有的子View,在ViewGroup(或其继承ViewGroup的Layout)父布局当中的位置。
5、childView.getMeasuredWidth();//在onMeasure()方法之后取得View的实际宽、高
childView.getMeasuredHeight();

(三)、onDraw(): 用于绘制需要的图形
1、ondraw函数有一个参数,为canvas,是一个绘制工具 ,canvas常用的方法有:

  • drawPoint(float x, float y, Paint paint) //画点,参数一水平x轴,参数二垂直y轴,第三个参数为Paint对象。

  • drawLine(float startX, float startY, float stopX, float stopY, Paintpaint) //画线,参数一起始点的x轴位置,参数二起始点的y轴位置,参数三终点的x轴水平位置,参数四y轴垂直位置,最后一个参数为Paint 画笔对象。

  • drawRect(RectF rect, Paint paint) //绘制区域,参数一为RectF一个区域

  • drawPath(Path path, Paint paint) //绘制一个路径,参数一为Path路径对象

  • drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) //贴图,参数一就是我们常规的Bitmap对象,参数二是源区域(这里是bitmap),参数三是目标区域(应该在canvas的位置和大小),参数四是Paint画刷对象,因为用到了缩放和拉伸的可能,当原始Rect不等于目标Rect时性能将会有大幅损失。

  • drawOval(RectF oval, Paint paint)//画椭圆,参数一是扫描区域,参数二为paint对象;

  • drawCircle(float cx, float cy, float radius,Paint paint)// 绘制圆,参数一是中心点的x轴,参数二是中心点的y轴,参数三是半径,参数四是paint对象;

  • drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧,参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧,参数二是起始角(度)在电弧的开始,参数三扫描角(度)开始顺时针测量的,参数四是如果这是真的话,包括椭圆中心的电弧,并关闭它,如果它是假这将是一个弧线,参数五是Paint对象;

  • drawText(String text, float x, floaty, Paint paint) //渲染文本,Canvas类除了上面的还可以描绘文字,参数一是String类型的文本,参数二x轴,参数三y轴,参数四是Paint对象。

2、Paint 画笔,paint 常用方法如下:

  1. setStyle(Style style) :设置绘制模式
  2. setColor(int color) :设置颜色
  3. setStrokeWidth(float width) :设置线条宽度,画笔样式为空心时,设置空心画笔的宽度
  4. setTextSize(float textSize) :设置文字大小
  5. setAntiAlias(boolean aa) :设置抗锯齿开关
  6. setAlpha(int a) :设置画笔的透明度[0-255],0是完全透明,255是完全不透明
  7. setColorFilter(ColorFilter filter) :设置图形重叠时的显示方式,下面来演示一下
  8. setARGB(int a, int r, int g, int b) :设置画笔颜色,argb形式alpha,red,green,blue每个范围都是[0-255]
  9. setTextScaleX(float scaleX) :设置字体的水平方向的缩放因子,默认值为1,大于1时会沿X轴水平放大,小于1时会沿X轴水平缩小
  10. setTypeface(Typeface typeface) :设置字体样式
  11. setFakeBoldText(boolean fakeBoldText) :设置文本粗体
  12. setStrikeThruText(boolean strikeThruText) :设置文本的删除线
  13. setUnderlineText(boolean underlineText) :设置文本的下划线
  14. reset() : 重置Paint
  15. setFlags(int flags) :设置一些标志,比如抗锯齿,下划线等等
  16. setLetterSpacing(float letterSpacing) :设置行的间距,默认值是0,负值行间距会收缩
  17. setStrokeMiter(float miter) :当style为Stroke或StrokeAndFill时设置连接处的倾斜度,这个值必须大于0,看一下演示结果
  18. setDither(boolean dither) :设置是否抖动,如果不设置感觉就会有一些僵硬的线条,如果设置图像就会看的更柔和一些
  19. setStrokeCap(Paint.Cap cap) :设置线冒样式,取值有Cap.ROUND(圆形线冒)、Cap.SQUARE(方形线冒)、Paint.Cap.BUTT(无线冒)
  20. setStrokeJoin(Paint.Join join) :设置线段连接处样式,取值有:Join.MITER(结合处为锐角)、Join.Round(结合处为圆弧)、Join.BEVEL(结合处为直线)

推荐文章:
HenCoder Android 开发进阶: 自定义 View 绘制基础
HenCoder Android 开发进阶: 自定义 View Paint 详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值