自定义View系列(三)初始自定义TextView

自定义view,自认为不是一步登天,要循序渐进,所以从最简单的的TextView开始,小试牛刀,对自定义view有一个全面认识。

1.创建

继承自view

public class TextView extends View {
// 构造函数会在代码里面new的时候调用
// TextView tv = new TextView(this);
public TextView(Context context) {
    this(context, null);
}
// 在布局layout中使用(调用)
public TextView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}
// 在布局layout中使用(调用),但是会有style
public TextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

2.自定义属性

在res-->valus下创建attrs.xml文件

 

    
    
    
    
    
    
    
    
    
        
        
        
    

在构造中获取属性 ,并初始化

       private String mText;
       private int mTextSize = 15;
       private int mTextColor = Color.BLACK;

    // 获取自定义属性
    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextView);

    mText = array.getString(R.styleable.TextView_mText);
    mTextColor = array.getColor(R.styleable.TextView_mTextColor, mTextColor);
    // 15 15px 15sp
    mTextSize = array.getDimensionPixelSize(R.styleable.TextView_mTextSize,sp2px(mTextSize));

    // 回收
    array.recycle();

3.创建画笔

private Paint mPaint;

在构造中创建

    //创建画笔
    mPaint=new Paint();
    //抗锯齿
    mPaint.setAntiAlias(true);
    //画笔大小
    mPaint.setTextSize(mTextSize);
    //画笔颜色
    mPaint.setColor(mTextColor);

4.测量

  /**
 * 自定义View的测量方法
 * @param widthMeasureSpec
 * @param heightMeasureSpec
 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    // 布局的宽高都是由这个方法指定
    // 指定控件的宽高,需要测量
    // 获取宽高的模式
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    //1.确定的值,不需要计算
    int width = MeasureSpec.getSize(widthMeasureSpec);

    //2.给的wrap_content 需要计算
    if (widthMode==MeasureSpec.AT_MOST){
        //计算的宽度与字体长度有关 与字体的大小 用画笔来测量
       Rect bounds= new Rect();
        mPaint.getTextBounds(mText,0,mText.length(),bounds);
        width=bounds.width()+getPaddingLeft()+getPaddingRight();
    }

    //1.确定的值,不需要计算
    int height = MeasureSpec.getSize(heightMeasureSpec);

    //2.给的wrap_content 需要计算
    if (heightMode==MeasureSpec.AT_MOST){
        //计算的宽度与字体长度有关 与字体的大小 用画笔来测量
        Rect bounds= new Rect();
        mPaint.getTextBounds(mText,0,mText.length(),bounds);
        height=  bounds.height()+getPaddingTop()+getPaddingBottom();

    }

   //设置控件的宽高
    setMeasuredDimension(width,height);
}

5.绘制

/**
 * 用于绘制
 * @param canvas
 */
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //画文字 text ,x,y,paint
    //求y值基线  bottom是个正值 top是个负值
    Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
    int dy=(fontMetrics.bottom-fontMetrics.top)/2-fontMetrics.bottom;
    int baseLine=getHeight()/2+dy;
    int x=getPaddingLeft();
    canvas.drawText(mText,x,baseLine,mPaint);
}

6.问题讲解

自定义TextView能否继承LinearLayout,效果能否出来?
答案是可以出来的,但是不会走onDraw()方法
view中得知要中onDraw()方法,有下面代码

final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&2.               (mAttachInfo     == null || !mAttachInfo.mIgnoreDirtyState);

  if (!dirtyOpaque) onDraw(canvas);

也就是dirtyOpaque为false时会调用,mPrivateFlags 到底是怎么赋值的 在View的构造函数中调用 computeOpaqueFlags

 /**
 * @hide
 */
protected void computeOpaqueFlags() {
    // Opaque if:
    //   - Has a background
    //   - Background is opaque
    //   - Doesn't have scrollbars or scrollbars overlay

    if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) {
        mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND;
    } else {
        mPrivateFlags &= ~PFLAG_OPAQUE_BACKGROUND;
    }

    final int flags = mViewFlags;
    if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||
            (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY ||
            (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_OUTSIDE_OVERLAY) {
        mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS;
    } else {
        mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS;
    }
}

在ViewGroup 中构造方法会走 initViewGroup();

  private void initViewGroup() {
    // ViewGroup doesn't draw by default
    if (!debugDraw()) {
        setFlags(WILL_NOT_DRAW, DRAW_MASK);
    }
    mGroupFlags |= FLAG_CLIP_CHILDREN;
    mGroupFlags |= FLAG_CLIP_TO_PADDING;
    mGroupFlags |= FLAG_ANIMATION_DONE;
    mGroupFlags |= FLAG_ANIMATION_CACHE;
    mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;

    if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
        mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
    }

    setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);

    mChildren = new View[ARRAY_INITIAL_CAPACITY];
    mChildrenCount = 0;

    mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
}

其中

   if (!debugDraw()) {
        setFlags(WILL_NOT_DRAW, DRAW_MASK);
    }

导致 mPrivateFlags 会重新赋值

解决思路: 目的就是改变 mPrivateFlags

1.重写dispatchDraw(),不用重写onDraw();
2.可以背景透明的背景
3.setWillNotDraw()

你可能感兴趣的:(自定义View系列(三)初始自定义TextView)