关于android自定义控件的学习心得小结

前些天去面试,被考官为了一个问题,
如果我想自定义一个viewgroup,并且自带一条线,我们需要调用哪个方法?
关于自定义view之前我也看过一些文章,但是其实不能理解里面真正的东西,大致只是知道三个方法measure、layout、draw,所以没有答上来。
这里做个总结:
1、 onmeasure
如果控件是viewgroup类型,也就是 容器类控件,比如相对布局、线性布局、viewpager 等。
那么onmeasure方法里面重写的代码就是与 布局里面的孩子 设置view 尺寸大小相关的代码,这里设置的大小不一定是死的,只是给一个期望的大小,因为子view的大小最终还是以自己设置为主!

容器类控件
这个方法作用是:
(1)给childView计算出建议的宽和高和测量模式;

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

int widthMeasureSpec, int heightMeasureSpec
看出2个参数就是从父容器传过来的。
子View的测量规格如何获取???
viewgroup的measureChild( )方法会根据ViewGroup的测量规格和子View的LayoutParams生成子View的测量规格,然后传递给子View的measure方法进行测量。

注:MeasureSpec代表一个32位的int值,高两位代表SpecMode,低30位表示SpecSize。SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。
UNSPECIFIED :父容器没有给当前View强加任何约束,View想多大就多大,此种情况一般用于系统内部。

EXACTLY : 父容器已经决定了当前View的准确的大小。这个时候View的最终大小就是SpecSize所指定的值,它对应于LayoutParams中的match_parent和具体的数值。

AT_MOST :父容器指定了一个SpecSize,View的大小不能大于这个值,它对应于LayoutParams中的wrap_content。

(2)遍历测量了所有子view之后,完成自己宽和高的确定。

如果控件是
非容器类控件,比如textview,button,edittext 等,
这个方法实现的是给由父容器传递过来的viewspec 测量规格(期望的模式、和size尺寸)
来计算view控件自己的大小(长和宽)。

2、onLayout
这个方法是设置控件的位置,一般只有容器类控件才会重写这个方法,
用来控制孩子view在容器里面显示的位置。
比如自定义布局控件 CustomLayout ,要实现添加4个textview,会自动显示在在4个角,那个你的CustomLayout 需要重写这个方法。

3、ondraw
对于viewgroup 容器类控件:
1)容器自带背景,不是透明的
容器有背景时,才会调用ondraw( )方法画自己的content
也可以在自定义容器时候:

public GridRelativeLayout(Context context) {
        super(context);
        setWillNotDraw(false);
    }

    public GridRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setWillNotDraw(false);
    }

setWillNotDraw(false);调用这个方法,也会执行ondraw()方法。

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
所以考官问的问题答案就是这里,画线在自定义viewgroup的 ondraw方法

package com.example.viewdemo;

import android.R.integer;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

public class GridRelativeLayout extends RelativeLayout {
    private Canvas myCanvas;
    private int horGrid = 108, verGrid = 177;// 水平网格和竖直网格
    private int screenW = 1080, screenH = 1776;// 屏幕宽和高
    private boolean initOver = true;// 初始化标签

    public GridRelativeLayout(Context context) {
        super(context);
        setWillNotDraw(false);
    }

    public GridRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setWillNotDraw(false);
    }

    public GridRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // /setWillNotDraw(false);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        this.myCanvas = canvas;
        Paint paint = new Paint();
        paint.setColor(android.graphics.Color.YELLOW);// 颜色
        paint.setStrokeWidth(3);// 线宽
        int verNum = (int) (screenH / verGrid);// 1776/177
        int horNum = (int) (screenW / horGrid);
        canvas.drawColor(Color.GREEN);

        if (initOver) {
            canvas.drawLine(0, 1 * verGrid - 1, screenW, 1 * verGrid - 1, paint);
            for (int i = 0; i < verNum; i++) {
                canvas.drawLine(0, i * verGrid, screenW, i * verGrid, paint);
            }// 画横线
                // 画竖线
            for (int i = 0; i < horNum; i++) {
                canvas.drawLine(i * horGrid, 0, i * horGrid, screenH, paint);
            }
            // 画圆圈
            canvas.drawCircle(screenH / 2, screenW / 2, 100, paint);

        }
    }

    /** 设置网格线参数 **/
    public void setInf(int vergrid, int horgrid, int screenW, int screenH) {
        this.verGrid = vergrid;
        this.horGrid = horgrid;
        this.screenW = screenW;
        this.screenH = screenH;
        initOver = true;
        postInvalidate();
    }

    /** 擦除网格线 **/
    public void clearLine() {
        initOver = false;
        postInvalidate();
    }
}

2)容器没有背景,是透明的
看源码分析,这里不做细说,直接给结论。
透明背景,就是说viewgroup 自身不需要绘制任何东西,不会调用ondraw( )方法。
直接调用dispatchDraw( )来绘制孩子view。

针对非容器类控件:
通常只需要重写ondraw方法即可,来绘制满足项目的自定义视图。
比如圆角的imageview,就是通过重写ondraw方法绘制4个角。

还有一点关于自定义view重写onmeasure方法:
onMeasure
从源码getDefaultSize方法看,View的宽高由specSize决定。直接继承View需要重写onMeasure方法,并设置wrap_content时的自身大小,因为在getDefultSize方法中,AT_MOST模式下返回的是specSize。根据上面的表格可以看出,在AT_MOST模式下,specSize就是parentSize,也就是父容器最大可用空间。这种效果跟使用match_parent效果一样。
也即是说,对于一个直接继承自View的自定义View来说,比如:
CustomView extend View{
。。。
}
它的wrap_content和match_parent属性的效果是一样的,因此如果要实现自定义View的wrap_content,则要重写onMeasure方法,对wrap_content属性进行处理。
可以看这篇文章:
http://www.2cto.com/kf/201704/632345.html

正常情况我们是 extends Textview 吧
好了,就这些。源码大家可以自己研究~~~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值