View绘制流程
View的绘制基本由measure()、layout()、draw()这个三个函数完成。
一、自定义view类型
1.继承View的自定义view
在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View,不包含子View。
2.继承ViewGroup的自定义view
继承ViewGroup的自定义view一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout,包含有子View。
二、举个栗子
自定义view
先上图:主体是一个正方形,中间区域为文字。四个角为圆形,并且为四个圆形区域添加点击事件监听器。
image.png
结合代码说明:
1.新建HezhiView类,继承View,实现构造方法,并且初始化画笔等(其中两个参数的构造法是在xml布局初始化会用到)
public HezhiView(Context context) {
super(context);
init();
}
public HezhiView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public HezhiView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.STROKE);
mPath = new Path();
}
2.重写onDraw方法,执行画正方形、画圆、画文字操作。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(getResources().getColor(R.color.colorPrimary));
centerX = getWidth() / 2;
centerY = getHeight() / 2;
drawSquare(canvas);
drawCircle(canvas);
drawText(canvas);
}
private void drawSquare(Canvas canvas) {
mPath.moveTo(centerX - 200, centerY - 200);
mPath.lineTo(centerX - 200, centerY + 200);
mPath.lineTo(centerX + 200, centerY + 200);
mPath.lineTo(centerX + 200, centerY - 200);
mPath.close();
canvas.drawPath(mPath, mPaint);
}
private void drawCircle(Canvas canvas) {
canvas.drawCircle(centerX - 200, centerY - 200,50,mPaint);
canvas.drawCircle(centerX - 200, centerY + 200,50,mPaint);
canvas.drawCircle(centerX + 200, centerY + 200,50,mPaint);
canvas.drawCircle(centerX + 200, centerY - 200,50,mPaint);
}
private void drawText(Canvas canvas) {
Paint.FontMetrics fm = mPaint.getFontMetrics();//用于计算文字的高度
String text = "何止";
mPaint.setTextSize(100);
canvas.drawText(text, centerX - mPaint.measureText(text) / 2,
(centerY + (int) Math.ceil(fm.descent - fm.ascent) / 2), mPaint);
}
3、为四个圆形添加事件监听器,重写onTouchEvent方法。
通过坐标判断是否在圆形区域内,进行回调。
public boolean onTouchEvent(MotionEvent event){
int x = (int) event.getX();
int y = (int) event.getY();
if (event.getAction() == MotionEvent.ACTION_UP){
if (isInner(x,y,centerX - 200,centerY - 200,50)){
if (null != this.listenr){
this.listenr.callOnClick(0);
}
return true;
}
if (isInner(x,y,centerX - 200,centerY + 200,50)){
if (null != this.listenr){
this.listenr.callOnClick(1);
}
return true;
}
if (isInner(x,y,centerX + 200,centerY + 200,50)){
if (null != this.listenr){
this.listenr.callOnClick(2);
}
return true;
}
if (isInner(x,y,centerX + 200,centerY - 200,50)){
if (null != this.listenr){
this.listenr.callOnClick(3);
}
return true;
}
return false;
}else{
return true;
}
}
private boolean isInner(int x,int y ,int rx,int ry,int r){
return Math.pow(x-rx,2) + Math.pow(y-ry,2) <= Math.pow(r,2);
}
public void setListenr(Listenr listenr){
this.listenr = listenr;
}
public interface Listenr{
void callOnClick(int index);
}
注意:在onTouchEvent方法内,需要判断MotionEvent的类型,点击事件会先执行MotionEvent.ACTION_DOWN。若事件没有被消费,则不再执行MotionEvent.ACTION_UP事件。因此,在MotionEvent.ACTION_DOWN时需要return true。这涉及到事件分发机制,下次学习。
自定义属性
在values文件夹中创建attrs.xml文件,设置相关属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="HezhiView"> // 自定义属性集合
<attr name="hezhi_color" format="color" />
<attr name="hezhi_width" format="dimension" />
<attr name="hezhi_height" format="dimension" />
</declare-styleable>
</resources>
自定义属性应用
需要引入xmlns:app="http://schemas.android.com/apk/res-auto"命名空间
<com.example.customer.view.PentagonView.HezhiView
android:id="@+id/hezhi_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:padding="10dp"
app:hezhi_color="#d0d0d0"
app:hezhi_height="300dp"
app:hezhi_width="300dp">
</com.example.customer.view.PentagonView.HezhiView>
重写onMeasure,对view进行测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, mHeight);
}
}
最终效果
image.png
网友评论