Android 自定义View篇

一:自定义View分类

类型定义
自定义组合控件多个控件组合成为一个新的控件,不便多处复用
继承控件继承零碎View和ViewGroup(TextView,LinearLayout),在零碎控件的根底性能上扩大
自绘控件间接继承View和ViewGroup类,实现自绘控件

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

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:background="@android:color/holo_orange_dark">    <Button        android:id="@+id/btn_left"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerVertical="true"        android:textColor="@android:color/white"        android:layout_marginLeft="5dp"        android:text="返回" />    <TextView        android:id="@+id/title_tv"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerInParent="true"        android:text="题目"        android:textColor="@android:color/white"        android:textSize="20sp" /></RelativeLayout>

依据给定的布局实现自定义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本身的内容会和其作为父布局增加的子控件,成果会叠加,具体的叠加成果是依据继承的容器个性决定的。

应用:

 <com.ruan.mygitignore.CustomTitleView        android:layout_width="match_parent"        android:layout_height="wrap_content"/>


二:继承控件
1.继承现有的控件,对其控件的性能进行扩大
2.坐标系
View的坐标系

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当中的Sizematch_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:不顾老本,不懂止损。