Android 自定义View篇

Android 自定义View篇

一:自定义View分类

类型 定义
自定义组合控件 多个控件组合成为一个新的控件,方便多处复用
继承控件 继承系统View和ViewGroup(TextView,LinearLayout),在系统控件的基础功能上扩展
自绘控件 直接继承View和ViewGroup类,实现自绘控件

二:组合控件
组合控件,就是将系统原有的控件进行组合,构成一个新的控件。这种方式下,不需要开发者自己去绘制图上显示的内容,也不需要开发者重写onMeasure(),onLayout(),onDraw()方法来实现测量,布局以及draw绘制流程。
一般标题栏就是一个例子
新建一个custom_title布局文件




    

根据给定的布局实现自定义View

public class CustomTitleView extends FrameLayout implements View.OnClickListener {

    private Button mBackBtn;
    private TextView mTittleView;
    private View.OnClickListener mLeftOnClickListener;

    public CustomTitleView(@NonNull Context context) {
        this(context, null);
    }

    public CustomTitleView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomTitleView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        LayoutInflater.from(context).inflate(R.layout.custom_title, this);
        init();
    }

    private void init() {
        mBackBtn = findViewById(R.id.btn_left);
        mTittleView = findViewById(R.id.title_tv);
        mBackBtn.setOnClickListener(this::onClick);


    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_left:
                if (mLeftOnClickListener != null) {
                    mLeftOnClickListener.onClick(v);
                }
                break;
        }
    }

    public void setLeftOnClickListener(View.OnClickListener leftOnClickListener) {
        mLeftOnClickListener = leftOnClickListener;
    }

    public void setTittle(String title) {
        mTittleView.setText(title);
    }
}

代码中对外提供了两个接口,一个是动态设置标题,一个是使用者可以自定义返回按钮的点击事件
CustomTitleView自己就是一个容器,完全可以当成容器使用,此时CustomTitleView自身的内容会和其作为父布局添加的子控件,效果会叠加,具体的叠加效果是根据继承的容器特性决定的。

使用:

 

Android 自定义View篇_第1张图片
二:继承控件
1.继承现有的控件,对其控件的功能进行扩展
2.坐标系
View的坐标系
Android 自定义View篇_第2张图片
View获取自身高度
width=getRight()-getLeft()
height=getBottom()-getTop()
View的源码当中提供了getWidth()和getHeight()方法来获取View的宽度和高度,我们可以直接调用来获取View得宽高
3.构造方法相关调用

public class TestView extends View {
    /**
     * 在java代码里new的时候会用到
     * @param context
     */
    public TestView(Context context) {
        super(context);
    }

    /**
     * 在xml布局文件中使用时自动调用
     * @param context
     */
    public TestView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 不会自动调用,如果有默认style时,在第二个构造函数中调用
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    /**
     * 只有在API版本>21时才会用到
     * 不会自动调用,如果有默认style时,在第二个构造函数中调用
     * @param context
     * @param attrs
     * @param defStyleAttr
     * @param defStyleRes
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
}

4.View绘制流程
View绘制的基本有measure(),layout(),draw()这三个函数完成

函数 作用 相关方法
measure() 测量View的宽高 measure(),setMeasuredDimension(),onMeasure()
layout() 计算当前View以及子View的位置 layout(),onLayout(),setFrame()
draw() 视图的绘制工作 draw(),onDraw()

4.1 Measure()
MeasureSpec是View的内部类,它封装了一个View的尺寸,在onMeasure()当中会根据这个MeasureSpec的值来确定View的宽高
MeasureSpec的值保存在一个int值当中。一个int值有32位,前两位表示模式mode后30位表示大小size。即MeasureSpec = mode + size。

在MeasureSpec当中一共存在三种mode:UNSPECIFIED、EXACTLY 和AT_MOST。

模式 意义 对应
EXACTLY 精准模式,View需要一个精确值,这个值即为MeasureSpec当中的Size match_parent
AT_MOST 最大模式,View的尺寸有一个最大值,View不可以超过MeasureSpec当中的Size值 wrap_content
UNSPECIFIED 无限制,View对尺寸没有任何限制,View设置为多大就应当为大 一般系统内部使用
      //获取宽度的测量模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        //获取宽度测量大小(Size)
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        //获取高度的测量模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //获取高度的测量大小(size)
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

其实测量过程的最终目的是:通过调用setMeasuredDimension方法来给mMeasureHeight和mMeasureWidth赋值

  /**
     * 测量
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int size;
        int mode;
        int width;
        int height;
        size = MeasureSpec.getSize(widthMeasureSpec);
        mode = MeasureSpec.getMode(widthMeasureSpec);
        if (mode == MeasureSpec.EXACTLY) {    //确定的值或者MATCH_PARENT
            width = size;
        } else {    //表示WARP_CONTENT
            width = (int) (2 * roundRadius);
        }

        mode = MeasureSpec.getMode(heightMeasureSpec);
        size = MeasureSpec.getSize(heightMeasureSpec);
        if (mode == MeasureSpec.EXACTLY) {    //确定的值或者MATCH_PARENT
            height = size;
        } else {    //表示WARP_CONTENT
            height = (int) (2 * roundRadius);
        }
        setMeasuredDimension(width, height);
    }

有时候还可以直接获取

 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

涉及到了三个方法分析:
setMeasuredDimension(int measuredWidth, int measuredHeight) :该方法用来设置View的宽高,在我们自定义View时也会经常用到。
getDefaultSize(int size, int measureSpec):该方法用来获取View默认的宽高,结合源码来看。

/**
*   有两个参数size和measureSpec
*   1、size表示View的默认大小,它的值是通过`getSuggestedMinimumWidth()方法来获取的,之后我们再分析。
*   2、measureSpec则是我们之前分析的MeasureSpec,里面存储了View的测量值以及测量模式
*/
public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        //从这里我们看出,对于AT_MOST和EXACTLY在View当中的处理是完全相同的。所以在我们自定义View时要对这两种模式做出处理。
        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

getSuggestedMinimumWidth():getHeight和该方法原理是一样的,这里只分析这一个
4.2Layout()
layout()过程,对于View来说用来计算View的位置参数,对于ViewGroup来说,除了要测量自身位置,还需要测量子View的位置。
4.3Draw()
draw流程也就是的View绘制到屏幕上的过程,如果需要

/**
     * 重写draw方法
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //获取各个编剧的padding值
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        //获取绘制的View的宽度
        int width = getWidth()-paddingLeft-paddingRight;
        //获取绘制的View的高度
        int height = getHeight()-paddingTop-paddingBottom;
        //绘制View,左上角坐标(0+paddingLeft,0+paddingTop),右下角坐标(width+paddingLeft,height+paddingTop)
        canvas.drawRect(0+paddingLeft,0+paddingTop,width+paddingLeft,height+paddingTop,mPaint);
    }

onDraw()主要需要了解两个类Paint 和Canvas
Paint类主要是画笔,可以设置画笔的一些功能

    setColor(int color);  
     * 设置绘制的颜色,使用颜色值来表示,该颜色值包括透明度和RGB颜色。  
     *   
    * setAntiAlias(boolean aa);  
     * 设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。  
     *   
     * setDither(boolean dither);  
     * 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰  
等功能

Canvas类简单理解就是表示一块画布,可以在上面画我们想画的东西
Canvas中的方法很多,Canvas可以绘制的对象有:

弧线(arcs) canvas.
填充颜色(argb和color)
Bitmap
圆(circle和oval)
点(point)
线(line)
矩形(Rect)
图片(Picture)
圆角矩形 (RoundRect)
文本(text)
顶点(Vertices)
路径(path)
canvas.save():把当前的绘制的图像保存起来,让后续的操作相当于是在一个新的图层上的操作。
canvas.restore(); 把当前画布返回(调整)到上一个save()状态之前
canvas.translate(dx, dy); //把当前画布的原点移到(dx,dy),后面的操作都以(dx,dy)作为参照点,默认原点为(0,0)

canvas.scale(x,y);扩大。x为水平方向的放大倍数,y为竖直方向的放大倍数
canvas.rotate(angel):旋转.angle指旋转的角度,顺时针旋转。
canvas.transform():切变。所谓切变,其实就是把图像的顶部或底部推到一边。
canvas.saveLayer(bounds, paint, saveFlags);
三:其他
onLayout()
重载该类可以在布局发生改变时作定制处理,这在实现一些特效时非常有用。View中的onLayout不是必须重写的,ViewGroup中的onLayout()是抽象的,自定义ViewGroup必须重写

END:不顾成本,不懂止损。

你可能感兴趣的:(androidjava)