[Android] Simple custom control process

Android custom components generally have three implementation methods:
1. Combination controls: Combination controls, as the name implies, combine some small controls to form a new control. Most of these small controls are built-in controls.
Second, self-drawing control: What is a self-drawing control, it is completely drawn with Paint and canvas, which is to draw in the onDraw() method, and measure in the onMeasure() method. If the container is positioned in the onLayout() method Each sub-component.
3. Inherited controls: it means to inherit existing controls, create new controls, retain the characteristics of the inherited parent controls, and introduce new characteristics.
There are also two types of self-drawing controls, custom components and custom containers . Custom components inherit the View class, and when custom containers inherit ViewGrounp; today, the main analysis of custom components is to give an example to be more practical. If we To draw the simplest TextView, the first thing that comes to mind is the canvas.drawText() method. How did you draw it? Still have to step by step:
(1)) Write a MyTextView class inherits View, rewrite three construction methods, of course, there are onDraw() and onMeasure() methods, the following code:

public class MyTextView extends View{
    
    
/**
* 文本颜色
*/
private int textColor;
/**
* 字体大小
*/
private float textSize;
/**
* 文本
*/
private String text;

/**
* 绘制时控制
*/
private Paint mPaint;
private Rect mBoud;

public MyTextView(Context context) {
    
    
this(context, null);
}

public MyTextView(Context context, AttributeSet attrs) {
    
    
this(context, attrs, 0);
}

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

@Override
protected void onDraw(Canvas canvas) {
    
    
super.onDraw(canvas);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

(2) Next, we must think that since it is a TextView, it must have attributes such as text, color, and textSize. Define these attributes in attrs.xml in the values ​​directory, so that the introduction of MyTextView into xml can directly manipulate these values, and then associate these attributes with the control in the construction method with three parameters, as follows:

attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyTextView">
<attr name="textColor" format="color|reference"/>
<attr name="textSize" format="dimension|reference"/>
<attr name="text" format="string|reference"/>
</declare-styleable>
</resources>
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    
    
	super(context, attrs, defStyleAttr);
	TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs,R.styleable.MyTextView, 0, 0);
	textColor = typedArray.getColor(R.styleable.MyTextView_textColor, 0);
	textSize = typedArray.getDimensionPixelSize(R.styleable.MyTextView_textSize, 0);
	text = typedArray.getString(R.styleable.MyTextView_text);
	typedArray.recycle();
}

(3) The properties have been defined, then use the Paint brush to set these properties in the construction method, and then use the canvas in the onDraw method to draw a textview, as follows:

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    
    
	super(context, attrs, defStyleAttr);
	TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyTextView, 0, 0);
	textColor = typedArray.getColor(R.styleable.MyTextView_textColor, 0);
	textSize = typedArray.getDimensionPixelSize(R.styleable.MyTextView_textSize, 0);
	text = typedArray.getString(R.styleable.MyTextView_text);
	typedArray.recycle();

 

	mPaint = new Paint();
	// 设置画笔为抗锯齿
	mPaint.setAntiAlias(true);
	mPaint.setColor(textColor);
	mPaint.setTextSize(textSize);
	//获取绘制文本的宽和高
	mBoud = new Rect();
	mPaint.getTextBounds(text, 0, text.length(), mBoud);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    
	int width = measureWidth(widthMeasureSpec);
	int height = measureHeight(heightMeasureSpec);
	setMeasuredDimension(width, height);
}

private int measureWidth(int widthMeasureSpec){
    
    
	int mode = MeasureSpec.getMode(widthMeasureSpec);
	int size = MeasureSpec.getSize(widthMeasureSpec);
	int width = 0;

	if(mode == MeasureSpec.EXACTLY){
    
    
	//如果是math_parent或确定尺寸
		width = size;
	}else if(mode == MeasureSpec.AT_MOST){
    
    
	//如果是wrap_parent
		width = getPaddingLeft() + mBound.width() + getPaddingRight();
	}
	return width;
}

private int measureHeight(int heightMeasureSpec){
    
    
	int mode = MeasureSpec.getMode(heightMeasureSpec);
	int size = MeasureSpec.getSize(heightMeasureSpec);
	int height = 0;
	if(mode == MeasureSpec.EXACTLY){
    
    
	//如果是math_parent或确定尺寸
		height = size;
	}else if(mode == MeasureSpec.AT_MOST){
    
    
		//如果是wrap_parent
		height = getPaddingTop() + mBound.height() + getPaddingBottom();
	}
	return height;
}

@Override
protected void onDraw(Canvas canvas) {
    
    
	super.onDraw(canvas);
	//canvas.drawText(text, x, y, paint); //第一个参数是文本,第四个参数是画笔,第二个参数x默认是字符串的左边在屏幕的位置, 第三个参数y是这个字符文本baseline基线在屏幕上的位置,不是这个字符的中心在屏幕的位置
	Log.v("MyTextView", "getWidth:"+getWidth()/2);
	Log.v("MyTextView", "mBoud.width:"+mBoud.width()/2);
	Log.v("MyTextView", "getHeight"+getHeight()/2);
	Log.v("MyTextView", "mBoud.height:"+mBoud.height()/2);
	Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
	int baseline = (getMeasuredHeight() / 2) - ((fontMetrics.bottom + fontMetrics.top) / 2);
	Log.v("MyTextView", "baseline:"+baseline);
	Log.v("MyTextView", "getMeasuredHeight:"+getMeasuredHeight());
	Log.v("MyTextView", "fontMetrics.top:"+fontMetrics.top);
	Log.v("MyTextView", "fontMetrics.bottom:"+fontMetrics.bottom);
	Log.v("MyTextView", "fontMetrics.ascent:"+fontMetrics.ascent);
	Log.v("MyTextView", "fontMetrics.descent:"+fontMetrics.descent);
	canvas.drawText(text, getWidth()/2 - mBoud.width()/2, baseline, mPaint);
}

Explain the onMeasure method

1. First of all, when measuring the width and height of the component, it is called by the parent container of the component. First, the container will traverse all the child components in the container and measure the width and height one by one. Here, the parent container of our custom textView is The outermost RelativeLayout
2. Then we need to understand the MeasureSpec class. This class provides us with two methods, getMode (int measureSpec) and getSize (int measureSpec) to obtain the width and height dimensions and modes. This class also provides us with Three constants MeasureSpec.EXACTLY, MeasureSpec.AT_MOST, MeasureSpec.UNSPECIFIED;
MeasureSpec.EXACTLY: when a certain size has been specified for the TextView or math_parent
MeasureSpec.AT_MOST: when the TextView is wrap_content
MeasureSpec.UNSPECIFIED: TextView has no width and height

Guess you like

Origin blog.csdn.net/sinat_36955332/article/details/108658471