自定义View onMeasure()和onLayout()与onDrow()

onMeasure→onLayout(ViewGroup必须重写)→onDraw

onMeasure()中的一些API
感谢大苞米
感谢易术军

   /*
    一般情况重写onMeasure()方法作用是为了自定义View尺寸的规则,如果你的自定义View的尺寸是根据父控件行为一致,就不需要重写onMeasure()方法
    如果不重写onMeasure方法,那么自定义view的尺寸默认就和父控件一样大小,当然也可以在布局文件里面写死宽高,而重写该方法可以根据自己的需求设置自定义view大小
    onMeasure (int widthMeasureSpec, int heightMeasureSpec)是view自己的方法
    onMeasure 方法简单的理解就是是用于测量视图的大小,主要是用来测量自己和内容的来确定宽度和高度
    onMeasure有两个参数 ( int widthMeasureSpec, int heightMeasureSpec),该参数表示控件可获得的空间以及关于这个空间描述的元数据.
    widthMeasureSpec和heightMeasureSpec这两个值通常情况下都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int exactly = MeasureSpec.EXACTLY; //EXACTLY: 精确模式  当宽高是wrap_connect时使用
        int atMost = MeasureSpec.AT_MOST;   //AT_MOST:最大模式  当宽高是match_parent或者是准确的xxdp的时候使用,让控件获取到准确的大小

        int mode = MeasureSpec.getMode(widthMeasureSpec); //取得测量模式 是 MeasureSpec.EXACTLY?  还是MeasureSpec.AT_MOST?
        int size = MeasureSpec.getSize(widthMeasureSpec); //取得测量数值

        int i = MeasureSpec.makeMeasureSpec(widthMeasureSpec, widthMeasureSpec);  //以上2个方法的 结合版 结果=size + mode(注意:二进制的加法,不是十进制的加法!)
        getChildMeasureSpec(i, 10, 10);//在measureChildren中最难的部分:找出传递给child的MeasureSpec。目的是结合父view的MeasureSpec与子view的LayoutParams信息去找到最好的结果

        int defaultSize = getDefaultSize(widthMeasureSpec, widthMeasureSpec);//第一个参数size为提供的默认大小  第二个参数为测量的大小

        int suggestedMinimumWidth = getSuggestedMinimumWidth(); //最小宽度
        int suggestedMinimumHeight = getSuggestedMinimumHeight(); //最小高度

        measureChildren(widthMeasureSpec, heightMeasureSpec); //跳过GONE类型View) 参数1:widthMeasureSpec 父视图的宽详细测量值 参数2:heightMeasureSpec 父视图的高详细测量值

        measureChild(getChildAt(0), widthMeasureSpec, heightMeasureSpec);//测量单个视图,将宽高和padding加在一起后交给getChildMeasureSpec去获得最终的测量值   参数1:child 需要测量的子视图   参数2:parentWidthMeasureSpec 父视图的宽详细测量值  参数3:parentHeightMeasureSpec 父视图的高详细测量值

        Resources resources = this.getResources();
        DisplayMetrics dm = resources.getDisplayMetrics();
        float density = dm.density;
        int width = dm.widthPixels;     //得到屏幕的宽
        int height = dm.heightPixels;   //得到屏幕的高

        getLeft(); //子View左边界到父view左边界的距离
        getTop(); //子View上边界到父view上边界的距离
        getRight(); // 子View右边界到父view右边界的距离
        getBottom(); // 子View下边界到父view下边界的距离


        setMeasuredDimension(500, 500); //这个方法必须由onMeasure(int, int)来调用,来存储测量的宽,高值。  这个方法可以改变控件的大小

    }

onLayout()方法中的一些API
感谢小试牛刀

 /**
     * onLayout也有个对应的layout方法。先看下layout和onLayout的区别
     * layout方法是View中的方法,用来实现View的摆放,layout传入是个参数left、top、right、bottom是相对于父控件而言的   例如传入(20,20,50,70),该View的左上角位置对应的坐标是(20,20),宽为30,高为50;
     * onLayout方法是ViewGroup中的方法,用来实现子View的摆放.  onLayout传入的参数l、t、r、f是根据父控件的实际可用空间来的(去除margin和padding的控件)
     *
     * @param l:View左边界距离父容器(父控件)的左边界的距离
     * @param t:View上边界距离父容器(父控件)上边界的距离
     * @param r:View右边界距离父容器(父控件)左边界的距离
     * @param b:View下边界距离父容器(父控件)上边界的距离
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        getMeasuredWidth();//是在measure()结束后得到的值 getMeasuredWidth是通过setMeasuredDimension()方法来设置的
        getWidth();//是在layout()结束后得到的值 getWidth()是通过视图右边的坐标减去左边的坐标计算出来的
    }

一些其他API

 
    View提供的获取坐标方法
    getTop:获取到的是View自身的顶边到其父布局顶边的距离
    getLeft:获取到的是View自身的左边到其父布局左边的距离
    getRight:获取到的是View自身的右边到其父布局左边的距离
    getBottom:获取到的是View自身的底边到其父布局顶边的距离
    

    
    MotionEvent提供的方法
    getX:获取点击事件距离控件左边的距离,即视图坐标
    getY:获取点击事件距离控件顶边的距离,即视图坐标
    getRawX:获取点击事件距离整个屏幕左边的距离,即绝对坐标
    getRawY:获取点击事件距离整个屏幕顶边的距离,即绝对坐标
    

获取屏幕宽高

方法一:更多适用于在activity中使用
Display.getHeight(),Display.getWidth() 方法过时


WindowManager wm = (WindowManager) this
                .getSystemService(Context.WINDOW_SERVICE);
int width = wm.getDefaultDisplay().getWidth();
int height = wm.getDefaultDisplay().getHeight();

方法二

WindowManager wm1 = this.getWindowManager();
int width1 = wm1.getDefaultDisplay().getWidth();
int height1 = wm1.getDefaultDisplay().getHeight();

方法三

WindowManager manager = this.getWindowManager();
DisplayMetrics outMetrics = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(outMetrics);
int width = outMetrics.widthPixels;
int height = outMetrics.heightPixels;

方法四:自定义控件中使用

Resources resources = this.getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
float density = dm.density;
int width = dm.widthPixels;
int height = dm.heightPixels;

不为人知的解决事件冲突

.getParent().requestDisallowInterceptTouchEvent(true);//剥夺父view 对touch 事件的处理权

.performClick(); //返回一个布尔类型的值 从而让点击和滑动获得良好的体验 

CSDN链接

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

        /*
        Paint 翻译 : 颜料
         */
        Paint paint = new Paint();

        paint.setFlags(1); //设置一些标志,比如抗锯齿,下划线等等。
        paint.setAntiAlias(true);//设置抗锯齿,如果不设置,加载位图的时候可能会出现锯齿状的边界,如果设置,边界就会变的稍微有点模糊,锯齿就看不到了
        paint.setDither(true);//设置是否抖动,如果不设置感觉就会有一些僵硬的线条,如果设置图像就会看的更柔和一些
        paint.setLinearText(true);//这个是文本缓存,设置线性文本,如果设置为true就不需要缓存
        paint.setSubpixelText(true);//设置亚像素,是对文本的一种优化设置,可以让文字看起来更加清晰明显,可以参考一下PC端的控制面板-外观和个性化-调整ClearType文本
        paint.setUnderlineText(true);//设置文本的下划线
        paint.setStrikeThruText(true);//设置文本的删除线
        paint.setFakeBoldText(true);//设置文本粗体
        paint.setFilterBitmap(true);//对位图进行滤波处理,如果该项设置为true,则图像在动画进行中会滤掉对Bitmap图像的优化操作,加快显示
        paint.setColor(Color.parseColor("#f00"));//设置画笔颜色
        paint.setAlpha(255);//设置画笔的透明度[0-255],0是完全透明,255是完全不透明
        paint.setARGB(0, 10, 10, 10);//设置画笔颜色的argb形式 顺序是 alpha,red,green,blue 每个范围都是[0-255]
        paint.setStrokeWidth(0.1f);//画笔样式为空心时,设置空心画笔的宽度
        paint.setStrokeMiter(0.1f);//当style为 Stroke 或 StrokeAndFill 时设置连接处的倾斜度,这个值必须大于0
        paint.setElegantTextHeight(true);//设置优雅的文字高度
        paint.setTextSize(1);//设置字体大小
        paint.setTextScaleX(1);//设置字体的水平方向的缩放因子,默认值为1,大于1时会沿X轴水平放大,小于1时会沿X轴水平缩小
        paint.setTextSkewX(1);//设置文本在水平方向上的倾斜,默认值为0,推荐的值为-0.25
        paint.setLetterSpacing(0);//设置行的间距,默认值是0,负值行间距会收缩
        paint.setFontFeatureSettings("");//设置字体样式,可以设置CSS样式
        paint.reset();//重置Paint
        paint.setStyle(Paint.Style.FILL);//决定笔触的样式

        //下面这些我没测试过
        /*
        setShader   (Shader shader) 设置着色器,用来给图像着色的,绘制出各种渐变效果,有BitmapShader,ComposeShader,LinearGradient,RadialGradient,SweepGradient几种
        setColorFilter  (ColorFilter filter)    设置画笔颜色过滤器,有ColorMatrixColorFilter,LightingColorFilter,PorterDuffColorFilter几种,这个以后再单独分析
        setXfermode (Xfermode xfermode) 设置图形重叠时的显示方式
        setPathEffect   (PathEffect effect) 设置绘制路径的效果,有ComposePathEffect,CornerPathEffect,DashPathEffect,DiscretePathEffect,PathDashPathEffect,SumPathEffect几种,以后在单独分析
        setMaskFilter   (MaskFilter maskfilter) 对图像进行一定的处理,实现滤镜的效果,如滤化,立体等,有BlurMaskFilter,EmbossMaskFilter几种
        setTypeface (Typeface typeface) 设置字体样式,可以是Typeface设置的样式,也可以通过Typeface的createFromAsset(AssetManager mgr, String path)方法加载样式
        setShadowLayer  (float radius, float dx, float dy, int shadowColor) 设置阴影效果,radius为阴影角度,dx和dy为阴影在x轴和y轴上的距离,color为阴影的颜色。注:在用该方法之前需要调用setLayerType( LAYER_TYPE_SOFTWARE , null);关闭硬件加速
        setTextLocale   (Locale locale) 设置地理位置,比如显示中文,日文,韩文等,默认的显示Locale.getDefault()即可
      
         */


        /*
        measureText (char[] text, int index, int count) 测量字体的长度
                    (String text, int start, int end)
                    (String text)
                    (CharSequence text, int start, int end)

        breakText   (char[] text, int index, int count,float maxWidth, float[] measuredWidth)   剪切显示,就是大于maxWidth的时候只截取指定长度的显示
                    (CharSequence text, int start, int end,boolean measureForwards, float maxWidth, float[] measuredWidth)
                    (String text, boolean measureForwards,float maxWidth, float[] measuredWidth)

        getTextWidths   (char[] text, int index, int count,float[] widths)  提取指定范围内的字符串,保存到widths中
                        (CharSequence text, int start, int end, float[] widths)
                        (String text, int start, int end, float[] widths)
                        (String text, float[] widths)

        getTextPath (char[] text, int index, int count, float x, float y, Path path)    获取文本绘制的路径,提取到Path中
                    (String text, int start, int end, float x, float y, Path path)

        getTextBounds   (String text, int start, int end, Rect bounds)  得到文本的边界,上下左右,提取到bounds中,可以通过这计算文本的宽和高
                        (char[] text, int index, int count, Rect bounds)

         */

    }

Canvas 类的 API

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /*
        平移
        参数1: 向X轴方向移动100距离
        参数2: 向Y轴方向移动50距离
         */
        canvas.translate(100, 50);

         /*
        在X轴方向放大为原来2倍,Y轴方向方大为原来的4倍
        参数1: X轴的放大倍数
        参数2: Y轴的放大倍数
         */
        canvas.scale(2, 4);

        /*
        在X轴方向放大为原来2倍,Y轴方向方大为原来的4倍
        参数1: X轴的放大倍数
        参数2: Y轴的放大倍数
        参数3: 原点X坐标
        参数4: 原点Y坐标
         */
        canvas.scale(2, 4, 100, 100);

        /*
        旋转,原点为中心,旋转30度(顺时针方向为正方向 )
        参数1: 旋转角度
         */
        canvas.rotate(30);

        /*
        为中心,旋转30度,顺时针方向为正方向
        参数1: 旋转角度
         */
        canvas.rotate(30, 100, 100);


        /*
        画文字
        参数1:文本
        参数2:文本的x轴的开始位置
        参数3:文本Y轴的结束位置
        参数4:画笔对象
         */
        canvas.drawText("文字", 50, 50, new Paint());

         /*
        参数1:文本
        参数2:要从第几个字开始绘制
        参数3:要绘制到第几个文字
        参数4:文本的x轴的开始位置
        参数5:文本Y轴的结束位置
        参数6:画笔对象
         */
        canvas.drawText("文字", 2, 5, 50, 50, new Paint());


         /*
        参数1:文本
        参数2:路径
        参数3:距离路径开始位置的偏移量
        参数4:距离路径上下的偏移量(可以为负数)
        参数5:画笔对象
         */
        canvas.drawTextOnPath("文字", new Path(), 0, -50, new Paint());


         /*
        画圆
        参数1:圆心X
        参数2:圆心Y
        参数3:半径R
        参数4:画笔对象
         */
        canvas.drawCircle(200, 200, 100, new Paint());


        /*
        画线
        参数1:startX 起始
        参数2:startY
        参数3:stopX
        参数4:stopY
        参数5:画笔对象
         */
        canvas.drawLine(100, 100, 300, 300, new Paint());


         /*
        同时绘制多条线。
        参数1:float数组:每四个一组为一条线。最后不足四个,就忽略那些值。
        参数2:画笔对象
         */
        canvas.drawLines(new float[]{100, 100, 200, 200, 200, 100, 300, 100}, new Paint());


         /*
        画一个长方形
        参数1:float left
        参数2:float top
        参数3:float right
        参数4:float bottom
         */
        RectF oval = new RectF(150, 200, 500, 400);

        /*
        画椭圆
        参数1:RectF对象
        参数2:画笔对象
         */
        canvas.drawOval(oval, new Paint());


        /*
        画弧度
        参数1:RectF对象
        参数2:开始的角度。(水平向右为0度顺时针反向为正方向)
        参数3:扫过的角度
        参数4:是否和中心连线 参数5:画笔对象
         */
        canvas.drawArc(oval, 20, 180, false, new Paint());


         /*
        画矩形
        参数1:float left
        参数2:float top
        参数3:float right
        参数4:float bottom
         */
        canvas.drawRect(100, 100, 200, 200, new Paint());

         /*
        画圆角矩形
        参数1:RectF对象
        参数2:x半径 参数3:y半径
         */
        canvas.drawRoundRect(new RectF(), 20, 5, new Paint());

         /*
        路径对象    path.moveTo(80, 200)此点为多边形的起点
         */
        Path path = new Path();



        /*
        可以添加多个点。构成多边形
         */
        path.lineTo(120, 250);


         /*
        设置贝塞尔曲线的控制点坐标和终点坐标
         */
        new Path().quadTo(300, 100, 400, 400);



        /*
        使终点和起点链接,构成封闭图形
         */
        new Path().close();



        /*
        画多边形
        参数1:路径
        参数2:画笔对象
         */
        canvas.drawPath(path, new Paint());

         /*
        画一个点
        参数1:X坐标
        参数2:Y坐标
         */
        canvas.drawPoint(60, 390, new Paint());


        /*
        画多个点
        参数1:多个点,每两个值为一个点。最后个数不够两个的值将被忽略
        参数2:画笔对象
         */
        canvas.drawPoints(new float[]{60, 400, 65, 400, 70, 400}, new Paint());

         /*
        创建一个图片
        参数1:get到res目录
        参数2:图片路径
         */
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_background);


        /*
        画图片
        参数1:bitmap对象
        参数2:图像左边坐标点
        参数3:图像上边坐标点
         */
        canvas.drawBitmap(bitmap, 200, 300, new Paint());


      

}
invalidate(); // 在UI主线程中,用invalidate()本质是调用View的onDraw()绘制

postInvalidate(); // 主线程之外,用postInvalidate()。

postInvalidateDelayed(1000); //间隔 定时 刷新(重绘)

postInvalidateOnAnimation(); 


/*draw()
在这里面 有2个 比较容易 混淆的
invalidate() 请求我们的安卓系统 如果视图的 大小没有发生变化 就不会调用 layout()这个放置过程  (这个方法是 重绘)*/

/*requestLayout() 当布局发生变化的 时候 比如 方向?尺寸? 不会调用draw() 方法 也就是说 不会重新绘制  (这个方法是重置)*/

/**
    * 我们知道,我们在Xml写的布局文件最终会在通过Pull解析的方式转成代码的。
    * onFinishInflate的作用,就是在xml加载组件完成后调用的。这个方法一般在自制ViewGroup的时候调用。
    * 这个回调方法是在整个布局文件都实例化结束后每个View才进行回调,或者说是在控件及其子控件都实例化结束后每个View才进行的回调
    */
   @Override
   protected void onFinishInflate() {
       super.onFinishInflate();
   }

   /**
    * 如果旧的宽度和高度任意一个发生了改变都会调用sizeChange方法 
    * @param w
    * @param h
    * @param oldw
    * @param oldh
    */
   @Override
   protected void onSizeChanged(int w, int h, int oldw, int oldh) {
       super.onSizeChanged(w, h, oldw, oldh);
   }



   /**
    * 该方法是焦点改变的回调方法,当某个控件重写了该方法后,当焦点发生变化时,会自动调用该方法来处理焦点改变事件
    * @param gainFocus  参数gainFocus表示触发该事件的View是否获得了焦点,当该控件获得焦点时,gainFocus等于true,否则等于false
    * @param direction  参数direction表示焦点移动的方向,用数值表示
    * @param previouslyFocusedRect  表示触发事件的View的坐标系中,前一个获得焦点的矩形区域,即表示焦点是从哪里来的。如果不可以则为null
    */
   @Override
   protected void onFocusChanged(boolean gainFocus, int direction, @androidx.annotation.Nullable Rect previouslyFocusedRect) {
       super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
   }

为什么要用组合控件

1.执行效率高(假设我们需要一个Button上既有图片又有文字,常规提供的效果满足不了我们.而且这个特殊的Button会有很多页面用到.这个时候,我们就可以将这个Button写成一个自定义组合控件)
2.维护成本低,容易修改(如:上边所举的的例子,这个自定义的组合控件相当于我们写的基类,一旦需求被改变,我们只需要改动这个控件本身就好)
3.简单,上手快,容易使用

自定义布局

引入布局

/*
在动态创建控件的时候调用,也就是JAVA代码
*/
public class Custom extends LinearLayout {/*1.*/
    public Custom(Context context) {
        this(context,null);
    }
/*
在XML文件声明的时候调用
*/
    public Custom(Context context,  AttributeSet attrs) {
        this(context, attrs,-1);
    }
/*
可以不借助XML文件去声明Style资源 这里有个int参数
*/
    public Custom(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        /*2.*/LayoutInflater.from(context).inflate(R.layout.custom,this,true);
        //View.inflate(context,R.layout.custom,this);
    }
}

上述代码标记:

1.自定义组合控件时可以继承RelativeLayout、LinearLayout、FrameLayout 等 控件(这里我们用LinearLayout)
2.LayoutInflater.from().inflate() 这个方法中的inflate()的三个参数依次是:

第二个参数:
这里我们将this 传入进去 父布局自然是ViewGroup,而我们继承了LinearLayout,所以this没有问题
第三个参数:
如果 第二个参数 为null,无论attachToRoot为true或者false,效果都是一样的
如果 第二个参数 不为null,attachToRoot为true,表示将layout布局添加到root布局中
如果 第二个参数 不为null,attachToRoot为false,表示不将layout布局添加到root布局,若要添加则需要手动addView
如果 第二个参数 不为null,不设置attachToRoot(即调用两个参数的方法),情况和(2)中一样

继续讲这个第2条,这里我们还有一个方法可以起到同样的作 View.inflate(context,R.layout.custom,this) 这个方法和上一个方法有什么区别呢?
View的inflate()的方法是静态方法 而我们的 LayoutInflater 中的则是 普通方法

第二个参数若是第三个参数为true,那么第二个参数的意义是从第一个参数填充成的view对象的父控件;若是第三个参数为false,那么第二个参数的意义是可以为第一个参数生成的view对象的根布局提供LayoutParams参数的控件。
第三个参数,从第一个参数填充成的view对象是否要附着到第二个参数指定的控件上作为其子控件。

LayoutInflater 是Android中专门进行布局填充的方法,Android中所有使用布局填充的地方,都会调用这个方法; View类中的inflate方法封装了LayoutInflater类的inflate方法,相比之下用法比上一个简单,但是功能就不及了.
布局文件的根布局不受限制,尽量避免资源浪费,比如LinearLayout



自定义属性

在res下的values中新建一个份attrs的xml文档


自定义View onMeasure()和onLayout()与onDrow()_第1张图片

以下的name=""中的中文是为了方便说明而已!命名须规范



  
    
         
         
         
         
         
         
         
         

        
        


        
        
            
            
            
        

        
        
            
            
            
        

    


在JAVA代码中的引用

 /*
        获取我们自定义的 自定义属性 文件  
         */
        TypedArray mAttrs = context.obtainStyledAttributes(attrs, R.styleable.MyValues);
        if(mAttrs !=null){

            /*
            参数1:xml 文件
            参数2:默认值
             */

            int color = mAttrs.getColor(R.styleable.MyValues_颜色, Color.WHITE);
            //根据你定义的方法 获取到 在xml中填写的值 用这个值对 控件进行操作~
           
        }

你可能感兴趣的:(自定义View onMeasure()和onLayout()与onDrow())