乐趣区

关于android:Android-自定义View篇

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

退出移动版