一、官方文档 custom-components
- 样品工具库:kirikaTowa/AndroidUtils: Common tool library (github.com)
- 分支:develop
1、分类
- 可选:将一个xml转成view
1、继承
- 继承View
- 扩展
onDraw()和onMeasure()
2、按重写分
1、自定义View:只需要重写onMeasure()和onDraw()
2、自定义ViewGroup:则只需要重写onMeasure()和onLayout()
2、几个主要方法说明
- 生命周期:init->onFinishInflate->onMeasure..->onSizeChanged->onLayout->post()->onWindowFocusChanged->onMeasure->onLayout
- onMeasure和onLayout会被多次调用.
- onSizeChanged实测初始化的时候会执行一次;
- onLayout->onDraw ,当View变化时onLayout不执行,onDraw会多次执行;
1、onMeasure(int, int): 调用以确定此视图及其所有子级的大小要求;
1、MeasureSpec
- 定位:内部类,封装了View的尺寸,确定View的宽高;
- 存储形式-int-32位
- 前两位表示
模式mode后30位表示大小siz
- mode
- UNSPECIFIED:【match_parent】精准模式,View需要一个精确值,这个值即为MeasureSpec当中的Size
- EXACTLY:【wrap_content】最大模式,View的尺寸有一个最大值,View不可以超过MeasureSpec当中的Size值
- AT_MOST:【一般系统内部使用】无限制,View对尺寸没有任何限制,View设置为多大就应当为多大
2、onDraw(Canvas): 在视图应渲染其内容时调用;
3、onLayout(boolean, int, int, int, int): 在此视图应为其所有子级分配大小和位置时调用。
4、onSizeChanged(int, int, int, int):在此视图的大小发生变化时调用。
1、新的宽度、新的高度、旧的宽度、旧的高度
5、onTouchEvent(MotionEvent) 在发生触屏动作事件时调用。
6、invalidate()
1、View的外观发生变化时需要调用invalidate()方法使当前的视图失效,进而触发onDraw()方法重绘视图; 2、需在主线程调用。
7、dispatchTouchEvent有意思,返回true拦截所有点击事件,onTouchEvent监听失效
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return true;
}
二、参考文章
1、 建立自定义view
2、@JvmOverloads 自动重载
- 用这种方式实现自动重载可以实现
1、参考使用:有点意思的Kotlin的默认参数与JVMOverloads

4、 时序:Kotlin中init代码块和构造方法以及伴生对象中代码的调用时机及执行顺序_XeonYu的博客-CSDN博客_kotlin 的init
- kotlin中init作用域>constructor次级构造方法>companion object
- 可以在init里inflate view和执行initview
三、踩坑
1、若自定义View中调用父布局view的ondraw方法。会导致死循环,必要情况下可类似该方法中的处理
四、实践-阶段一(简单使用)
- 使用继承的方式自定义View
一、继承系统View
1、继承 ViewAnimator
- 优势:ViewAnimator是FrameLayout容器的基类,用于在其视图之间切换时执行动画。FrameLayout中添加的View都默认位于左上角,按照添加的顺序,最后添加的View位置最上层。
- ViewAnimator的使用就是调整ViewAnimator包裹的子View的显示层次,可以通过setDisplayedChild(int whichChild)方法,设置哪一个子View将被显示。
2、继承ScrollView
1、支持maxHeight
1、背景:
- ScrollView不支持设置maxHeight,可通过自定义View实现
- 其实外界套一个View,设置maxHeight,做起来更快;
2、调用:
- 自定义view完成后代码中使用:app:maxHeight="500dp"即可
3、代码说明
- 这边思路就很明确了,采用EXACTLY最大模式,把mMaxHeight和AT_MOST设置给他即可;
4、自定义View代码展示
- 类代码
- typedArray 获取对应属性组
- 为自身设置大小限制,复写onMeasure即可;
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
class HeightMaxScrollView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : ScrollView(context, attrs) {
var mMaxHeight = 0 //提供设置最大高度的属性
init {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.HeightMaxScrollView)
mMaxHeight =
typedArray.getLayoutDimension(R.styleable.HeightMaxScrollView_maxHeight, mMaxHeight)
typedArray.recycle()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var heightSpec = heightMeasureSpec
if (mMaxHeight > 0) {//如果最大属性存在则将高度设置为最大
heightSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.AT_MOST)
}
super.onMeasure(widthMeasureSpec, heightSpec)
}
}
- attrs配置属性
<declare-styleable name="HeightMaxScrollView">
<attr name="maxHeight" format="dimension" />
</declare-styleable>
- xml调用自定义属性:app:scrollX="600"
二、完全自定义
1、可打开拖动功能的数字进度条
1、参考文章1:android 自定义view实现数字进度条_android 自定义thumb进度条下方带数字
- 基本思路:左边画条线,中间画个Text,右边画条线;
2、参考文章:Android自定义View实现可拖拽的进度条
- 基本思路,底下画条线,上头画条线,线右边画个指示器;
3、类名:ProgressDigitalSeekBar
- 封进工具类里了
- 支持点击与拖动
- 支持按下状态
- 数字进度条
五、实践-阶段二(使用画笔/画布)
一、简述
1、 使用Canvas和Paint:
左上右下定位,左上角和右下角两个点定位: RectF oval = new RectF( x, y,getWidth() - x, getHeight() - y);
1、canvas:背景,画布,你可以绑定一个画笔来在这个画布上作画,当然你也可以设置这个画布的背景,android中canvas画图利用的是bitmap
- Canvas 的绘制类方法: drawXXX() (关键参数:Paint)
- Canvas 的辅助类方法:范围裁切和几何变换
- 配合上 Paint 的一些常见方法来对绘制内容的颜色和风格进行简单的配置
2、paint:前景,画笔,你可以设置这个画笔的实心空心、线条粗细、有没有阴,颜色,轨迹的STYLE等等,
3、Matrix:对画布进行变换
Canvas来做常见的二维变换- 使用
Matrix来做常见和不常见的二维变换; - 使用
Camera视角, 来做三维变换。
- 样例代码:
Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.bg_cell);
canvas.drawBitmap(bmp,null,rect,null);
Paint paint = new Paint();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制一个圆
canvas.drawCircle(300, 300, 200, paint);
}
2、结合Path() 画线
1、Android自定义View-Path的详细介绍_流星雨^-^的博客-CSDN博客
- setAntiAlias(true) 抗锯齿
- measureText() VS .getTextBounds()
- 参考文章:Android Paint: .measureText() VS .getTextBounds()
- 两个方法可以用来测量文字宽高信息的,只不过 .getTextBounds() 还可以获得高度信息,因为其使用一个 Rect 对象对宽高信息进行存储;而 .measureText() 则只是返回宽度信息。
- PathMeasureAndroid动画进阶PathMeasure
- 作用:测量并获取Path的信息,用于绘制Path路径实现动画效果。
3、bitmap
六、xml矢量图资源相关
一、基本知识
1、关于项目中矢量图的编写与应用:selector和shape的结合使用
- 选取逻辑是自顶向下,有满足则取用,然后break,Ps:enabled这个属性只改父布局是不生效的,一定要切实落实到需要改变的控件上。
- ViewGroup,父布局设置了点击响应,不设置select。子View不用设置点击,也是可以相应的变化。可以说是非常好用了。此处可以引申到View的事件分发机制。其选中状态可通过isSelected属性控制。
2、drawable类型与color类型对应两种写法介绍
- 文字设置textcolor的selector状态文件,应将对应文件放在color下,除了button外,控件需声明可点击与获取焦点。其他的根据情况设置background。
3、 简单图形
4、代码中使用渐变:Android开发 GradientDrawable详解
- 堆叠圆环:stroke是做不了渐变边框的,两层图像形成内外边框
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
//外边框整个
<item>
<shape android:shape="rectangle">
<corners android:radius="100dp" />
<gradient
android:angle="0"
android:endColor="#000000"
android:startColor="#ffffff"
android:type="linear" />
</shape>
</item>
//内图形叠在上面
<item
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp">
<shape android:shape="rectangle">
<corners android:radius="100dp" />
<gradient
android:angle="0"
android:endColor="#000000"
android:startColor="#ffffff"
android:type="linear" />
</shape>
</item>
</layer-list>
二、规约
- 配合着色器也很好用 android:backgroundTint="#FF6C7AF6"
1、背景+弧度:bg_ffffff_r10
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="10dp" />
<solid android:color="@color/white" />
</shape>
2、背景+部分弧度:bg_white_top_r37
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners
android:topLeftRadius="27dp"
android:topRightRadius="27dp"
android:bottomRightRadius="0dp"
android:bottomLeftRadius="0dp"
/>
<solid android:color="@color/white" />
</shape>
3、描边+背景+弧度:bg_white_ff2f3d_r33_line_1
- 单描边:line_1_7a5ee9_r14
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:width="1dp"
android:color="#FF2F3D" />
<corners android:radius="33dp"/>
<solid android:color="@color/white"/>
</shape>
- 描边+渐变背景+弧度
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:width="1dp"
android:color="@color/color_white" />
<corners android:radius="33dp"/>
<gradient
android:startColor="#febe5a"
android:angle="315"
android:endColor="#fe630c"/>
</shape>
4、虚线:
1、横向:line_dash_horizontal_adadad
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line">
<stroke
android:width="1dp"
android:color="#ADADAD"
android:dashWidth="5dp"
android:dashGap="5dp" />
<size android:height="2dp"/>
</shape>
2、竖向:line_dash_vertical_adadad
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:left="-300dp"
android:right="-300dp">
<rotate
android:fromDegrees="90"
android:toDegrees="90">
<shape android:shape="line">
<stroke
android:width="1dp"
android:color="#ADADAD"
android:dashWidth="2dp"
android:dashGap="2dp" />
</shape>
</rotate>
</item>
</layer-list>
3、上下两条线
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:gravity="top" android:height="1dp">
<shape>
<solid android:color="#E5E6EB"/>
</shape>
</item>
<item android:gravity="bottom" android:height="1dp">
<shape>
<solid android:color="#E5E6EB"/>
</shape>
</item>
</layer-list>
4、渐变规约
1、可以看到普通渐变是start在左,end在右【如果格式化的话,gradient end会在上面】
2、如果angle是90的话,会逆时针转90度,所以-90等于270度,startcolor为上方

- 进行规约【遵循识图原则,别管提供的xml顺序】
- 从左到右渐变:gradual_start_827cf7_to_b032f1_r9
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="9dp"/>
<gradient
android:angle="0"
android:startColor="#827CF7"
android:endColor="#B032F1"
/>
</shape>
- 从上到下规约:gradual_top_827cf7_to_b032f1_r9
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="9dp"/>
<gradient
android:angle="-90"
android:startColor="#827CF7"
android:endColor="#B032F1"
/>
</shape>
- 补充三层渐变代码
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="90"
android:endColor="#C3B4FF"
android:centerColor="#F3F0FF"
android:startColor="#F3F0FF" />
<corners android:radius="0dp" />
</shape>
- 圆角和局部圆角
- View/Imageview可通过以下添加阴影:android:elevation="8dp"
3、selector样式,颜色与drawable可混用,格式写对即可
- selector双色
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#000000" android:state_selected="true" /> <!-- 黑色 -->
<item android:color="#FFFFFF" android:state_selected="false" /> <!-- 白色 -->
</selector>
- select放drawable
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_vip_product_select" android:state_selected="true" /> <!-- 选中状态 -->
<item android:drawable="@drawable/ic_vip_product_unselect" /> <!-- 未选中状态 -->
</selector>
- 一半矢量图一半资源
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="false">
<shape>
<gradient android:angle="180" android:endColor="#FFFF0105" android:startColor="#FFFF0105" />
<corners android:radius="100dp" />
</shape>
</item>
<item android:drawable="@drawable/ic_bg" android:state_selected="true" />
</selector>
- 全矢量
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape >
<stroke
android:width="1dp"
android:color="#FF2F3D" />
<corners android:radius="33dp"/>
<solid android:color="@color/white"/>
</shape>
</item>
<item android:state_selected="false">
<shape >
<stroke
android:width="1dp"
android:color="#FF2F3D" />
<corners android:radius="33dp"/>
<solid android:color="@color/white"/>
</shape>
</item>
</selector>
5、Selected带Checked
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_login_checked" android:state_selected="true" />
<item android:drawable="@drawable/ic_login_checked" android:state_checked="true" />
<item android:drawable="@drawable/ic_login_uncheck" />
</selector>
5、圆
1、普通圆:oval_008aff_8
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="7dp"
android:height="7dp" />
<solid android:color="#008AFF" />
</shape>
6、组合图形
1、圆环组合
//圆环图形叠加
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="100dp"
android:height="100dp" />
<solid
android:color="#66FFFFFF" />
</shape>
</item>
<item android:top="10dp" android:start="10dp" android:end="10dp" android:bottom="10dp"> <!-- 控制第二个圆的位置 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="40dp"
android:height="40dp" />
<solid
android:color="#22D2C7" /> <!-- 红色 -->
</shape>
</item>
</layer-list>
2、 扫描线蒙层,还是蛮有意思的
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:width="3dp">
<shape>
<solid android:color="@color/white" />
<corners android:radius="5dp" />
</shape>
</item>
<item
android:width="40dp"
android:left="3dp"
android:right="3dp"
android:top="3dp">
<shape>
<gradient
android:angle="-90"
android:endColor="@color/color_clear"
android:startColor="#B3FFFFFF" />
</shape>
</item>
</layer-list>
3、内边线
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="@color/white" />
<corners android:radius="16dp" />
</shape>
</item>
<item
android:bottom="9dp"
android:end="9dp"
android:start="9dp"
android:top="9dp">
<shape>
<stroke
android:width="1dp"
android:color="#FFFFB18B" />
<corners android:radius="16dp" />
</shape>
</item>
</layer-list>
//设定尺寸
<size
android:width="65dp"
android:height="21dp" />
4、分割线:divider_e7e7e7_0_6
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="0.6dp" /> <!-- 控制线的厚度 -->
<solid android:color="#E7E7E7" />
</shape>
补充知识
一、关于修改依赖包中的icon:
1、遍历子view,获取,修改。