何止学习Android自定义view

2020-07-08  本文已影响0人  何止搬砖工

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
上一篇下一篇

猜你喜欢

热点阅读