Android自定义view

  1. 自定义View的属性
    res/values/下建立一个attrs.xml ,在里面定义属性和声明整个样式。


    //format是值该属性的取值类型
    
    
    
        
        
        
    

或者(上面是为了提取共同属性)



    
    
    
    
     

一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag;

  1. 在View的构造方法中,获得自定义的样式
    // 文字
    private String mTitleText;
    // 文字颜色
    private int mTitleTextColor;
    // 字号
    private int mTitleTextSize;
    // 画笔
    private Paint mPaint;
    // 矩形
    private Rect mRect;
    public CustomTitleView(Context context) {
        this(context, null);
    }
    public CustomTitleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public CustomTitleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
         //获取自定义属性
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyleAttr, 0);
        mTitleText = a.getString(R.styleable.CustomTitleView_titleText);
        mTitleTextColor = a.getColor(R.styleable.CustomTitleView_titleTextColor, Color.BLACK);
        mTitleTextSize = a.getDimensionPixelSize(R.styleable.CustomTitleView_titleTextSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
        a.recycle(); //通过recycle方法回收,否则会引起内存泄漏
        init();
    }
    private void init() {
        mPaint = new Paint();
        mPaint.setTextSize(mTitleTextSize);
        mPaint.setColor(mTitleTextColor);
        mPaint.setAntiAlias(true);
        mRect = new Rect();
        //计算了描绘字体需要的范围 
        mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mRect);
    }
  1. 重写onMeasureonDraw方法
    重写onMeasure之前要了解MeasureSpecspecMode,一共分为三种类型:
    EXACTLY:一般表示设置了明确值,或者 match_parent ;
    AT_MOST:表示子控件限制在一个最大值内,一般为 wrap_content;
    UNSPECIFIED:表示子控件想多大就多大,很少使用
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = 0;
        int height = 0;
        /**
         * 设置宽度
         */
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);//获取总宽度,是包含padding值
        switch (specMode) {
            case MeasureSpec.EXACTLY://一般是设置了明确的值或者是MATCH_PARENT
                width = getPaddingLeft() + getPaddingRight() + specSize;
                break;
            case MeasureSpec.AT_MOST://表示子布局限制在一个最大值内,一般为WARP_CONTENT
            case MeasureSpec.UNSPECIFIED:// 表示子布局想要多大就多大,很少使用
                float textWidth = mPaint.measureText(mTitleText); // int textWidth = mRect .width();直接计算出来的会有误差
                width = (int) (getPaddingLeft() + getPaddingRight() + textWidth);
                break;
        }
        /**
         * 设置高度
         */
        specMode = MeasureSpec.getMode(heightMeasureSpec);
        specSize = MeasureSpec.getSize(heightMeasureSpec);
        switch (specMode) {
            case MeasureSpec.EXACTLY://明确指定了
                height = getPaddingTop() + getPaddingBottom() + specSize;
                break;
            case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT
            case MeasureSpec.UNSPECIFIED:
                Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
                float textHeight = Math.abs((fontMetrics.bottom - fontMetrics.top));//int textHeight = mRect .height(); 直接计算出来的会有误差
                height = (int) (getPaddingTop() + getPaddingBottom() + textHeight);
                break;
        }
        setMeasuredDimension(width, height);
    }

注意

  • 是否你的宽度偏小,左边有空白,右边显示不全???那是因为onMeasure你使用了上面注释中的int textWidth = mRect.width()(有误差的),而不是使用float textWidth = mPaint.measureText(mTitleText);(准确的)
  • 是否你的高度偏小???那是因为onMeasure你使用了上面注释中的int textHeight = mRect .height()(有误差的),而不是float textHeight = Math.abs((mPaint.getFontMetrics().bottom - mPaint.getFontMetrics().top))(准确的)
  • 为什么计算宽时是getWidth() / 2 - mBound.width() / 2,计算高时是getHeight() / 2 +mBound.height() / 2???canvas.drawText(text, x, y, paint),第一个参数和第四个参数不比多说,主要是第二和第三个参数的含义,这两个参数在不同的情况下的值还是不一样的,x默认是这个字符串的左边在屏幕的位置,如果设置了paint.setTextAlign(Paint.Align.CENTER);那就是字符的中心,y是指定这个字符baseline在屏幕上的位置,一定要注意,y不是这个字符中心在屏幕上的位置,而是baseline在屏幕上的位置。那是因为在android中,文字的绘制都是从Baseline处开始的(Baseline以上是负值,以下是正值,因此 ascent,top是负值, descentbottom是正值)
   @Override
   protected void onDraw(Canvas canvas) {
       mPaint.setColor(Color.BLACK);
       mPaint.setAntiAlias(true);
       mPaint.setTextAlign(Paint.Align.LEFT);
       canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
       mPaint.setColor(mTitleTextColor);
       Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
       float startY = getHeight() / 2 + Math.abs((fontMetrics.bottom - fontMetrics.top)) / 2- fontMetrics.descent ;
       canvas.drawText(mTitleText, getWidth() / 2 - mPaint.measureText(mTitleText) / 2, startY, mPaint);
   }

注意

onDrawlayout的过程有可能是一个频繁重复执行的过程,我们知道new是需要分配内存空间的,所以不建议在这两个过程中去实例化对象,一般在构造函数中实例化对象

  1. 在布局中声明自定义View
    一定要引入xmlns:lazyxu="http://schemas.android.com/apk/res-auto"(其中lazyxu由自己随意取)


    

小知识

  • getMeasuredWidth()getWidth()区别

1.定义概念不同getMeasuredWidth()是View视图实际的宽高(包括被隐藏的部分);getWidth()指的是视图显示的宽高。实际上在当屏幕可以包裹内容的时候,他们的值是相等的,只有当view超出屏幕后,才能看出他们的区别。当超出屏幕后,getMeasuredHeight()等于getHeight()加上屏幕之外没有显示的高度。
getWidth()方法中的返回值是通过View的右坐标减去其左坐标(right-left)计算出来
2.赋值时机不同getMeasureWidth()值是通过setMeasuredDimension()方法来进行设置的,在onMeasure()结束后获取到,getWidth()值是通过layout(left,top,right,bottom)方法设置的,在layout()结束后获取到。

  1. Paint的measureText()和getTextBound()
    getTextBounds() 得到的宽度总是比 measureText() 得到的宽度要小一点。

参考
http://blog.csdn.net/lmj623565791/article/details/24252901/
http://blog.csdn.net/u014702653/article/details/51985821

你可能感兴趣的:(Android自定义view)