Android 自定义控件

自定义View的三种方式:

1)继承控件:继承原生原生控件,通过添加新方法或者重新原生方法来扩展原生控件
2)组合控件:创建一个.xml布局文件,然后将该布局与该控件绑定,一般是该控件可以复用,或者页面太大,可以把某块布局抽取成一个控件,来简化activity的代码。
3)绘制控件:继承View,重写onMeasure、onSizeChanged、onLayout、onDraw等方法来完成控制的绘制,后面会重点讲解。

继承控件举例,如常用的圆形图片:

public class CircleImageView extends ImageView {


//构造函数
    public CircleImageView(Context context) {
        super(context);
        init();
    }
    //构造函数
    public CircleImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    /**
     * 构造函数
     */
    public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        //通过obtainStyledAttributes 获得一组值赋给 TypedArray(数组) , 这一组值来自于res/values/attrs.xml中的name="CircleImageView"的declare-styleable中。
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
        //通过TypedArray提供的一系列方法getXXXX取得我们在xml里定义的参数值;
        // 获取边界的宽度
        mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
        // 获取边界的颜色
        mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);
        mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_border_overlay, DEFAULT_BORDER_OVERLAY);
        //调用 recycle() 回收TypedArray,以便后面重用
        a.recycle();
        init();
    }

组合控件简单举例,这个比较简单不细讲


public class TestView extends LinearLayout{
 
    private ImageView iv;
    private TextView tv;
 
    public MyView1(Context context) {
        this(context,null);
    }
 
    public MyView1(Context context, AttributeSet attrs) {
        super(context, attrs);
        //R.layout.item_lay是自定义的布局,通过LayoutInflater把布局和View绑定
        LayoutInflater.from(context).inflate(R.layout.item_lay,this,true);
        iv = (ImageView) findViewById(R.id.iv);
        tv = (TextView) findViewById(R.id.tv);
    }

下图是自定义控件的绘制流程:

在这里插入图片描述

自定义属性:

1.在attrs.xml文件中定义自定义属性

<declare-styleable name="WaterfallView">
        <attr name="lineColor" format="color" />
        <attr name="lineSize" format="dimension" />
        <attr name="hasLineTop" format="boolean" />
        <attr name="hasNode" format="boolean" />
        <attr name="hasLineBottom" format="boolean" />
        <attr name="nodeOffset" format="dimension" />
        <attr name="nodeRadius" format="dimension" />
        <attr name="nodeDrawable" format="reference" />
    </declare-styleable>

2.解析自定义属性

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.WaterfallView);
        int lineColor = ContextCompat.getColor(getContext(), R.color.progress_bg);
        int lineSize = context.getResources().getDimensionPixelSize(R.dimen.tiny_margin);
        nodeOffset = context.getResources().getDimensionPixelSize(R.dimen.super_larger_margin);
        nodeRadius = context.getResources().getDimensionPixelSize(R.dimen.dip_6);
        for (int i = 0, len = array.getIndexCount(); i < len; i++) {
            int attr = array.getIndex(i);
            switch (attr) {
                case R.styleable.WaterfallView_lineColor:
                    lineColor = array.getColor(attr, lineColor);
                    break;
                case R.styleable.WaterfallView_lineSize:
                    lineSize = array.getDimensionPixelSize(attr, lineSize);
                    break;
                case R.styleable.WaterfallView_nodeOffset:
                    nodeOffset = array.getDimensionPixelSize(attr, nodeOffset);
                    break;
                case R.styleable.WaterfallView_nodeRadius:
                    nodeRadius = array.getDimensionPixelSize(attr, nodeRadius);
                    break;
                case R.styleable.WaterfallView_nodeDrawable:
                    nodeDrawable = array.getDrawable(attr);
                    break;
                case R.styleable.WaterfallView_hasNode:
                    hasNode = array.getBoolean(attr, hasNode);
                    break;
                case R.styleable.WaterfallView_hasLineTop:
                    hasLineTop = array.getBoolean(attr, hasLineTop);
                    break;
                case R.styleable.WaterfallView_hasLineBottom:
                    hasLineBottom = array.getBoolean(attr, hasLineBottom);
                    break;
            }
        }
        linePaint = new Paint();
        linePaint.setColor(lineColor);
        linePaint.setStrokeWidth(lineSize);
        array.recycle();

onMeasure方法说明:

UNSPECIFIED:父视图对子视图没有任何约束限制,子视图可以是任意大小,一般在scrollView或者listview中,要多大就多大。
EXACTLY:父视图对子视图给出明确的大小,子视图将按给出的要求展示大小,忽略自身的大小,一般是match_parent或者指定dp。
AT_MOST:子视图大小最多达到指定的大小,一般对应wrap_content,最大值不能超过父控件宽高

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST) {
        //当布局未指定控件的宽度,指定控件的宽度
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_WIDTH, MeasureSpec.EXACTLY);
        }
        //指定控件的高度
        if (heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_HEIGHT, MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

onLayout()用于摆放子控件在父控件中的位置,只有ViewGroup才能让子控件显示在自己的什么位置.只会触发,执行一次。

	1getWidth()方法和getMeasureWidth()的值基本相同。
    2,但getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。
    3,另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。
    4,我们在ViewGroup中重写onLayout的目的: 就是设置当前View与其所有的子View,在ViewGroup(或其继承ViewGroup的Layout)父布局当中的位置。
    5,childView.getMeasuredWidth();//在onMeasure()方法之后取得View的实际宽
    6.onMeasure()会执行多次,ViewRootImpl类中的performTraversals()会调用View的onMeasure()方法,当第一次测量方法执行之后,如果子view需要的控件大于父容器为它测量的控件,就会再测量一次
    7.onlayout会执行两次

onDraw() 用于绘制需要的图形

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

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

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

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

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

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

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

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

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

paint的属性介绍

	1.Paint.setStyle(Style style)//设置绘制模式
	2.Paint.setColor(int color) //设置颜色
	3.Paint.setStrokeWidth(float width) //设置线条宽度,画笔样式为空心时,设置空心画笔的宽度
	4.Paint.setTextSize(float textSize) //设置文字大小
	5.Paint.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(结合处为直线)

其他说明:

1.onMeasure()和onLayout()最后都要调用requestLayout()才能让改变生效;
2.onDraw()要调用invalidate()才能让改变生效,postInvalidate刷新界面;
onAttachedToWindow()//当View附加到窗体的时候调用该方法,
3.onFinishInflate() 是当所有的孩子都解析完后的一个调用
4.requestLayout() 会触发measure过程和layout过程
5.canvas.save(),画布将当前的状态保存;canvas.restore(),画布取出原来所保存的状态

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值