【工具类】自定义View(custom-components)

376 阅读10分钟

一、官方文档 custom-components

1、分类

  • 可选:将一个xml转成view

1、继承

  1. 继承View
  2. 扩展 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

  1. 定位:内部类,封装了View的尺寸,确定View的宽高;
  2. 存储形式-int-32位
  • 前两位表示模式mode后30位表示大小siz
  1. 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

基本流程:流程解析

细节分析:Android自定义View全解

2、@JvmOverloads 自动重载

  • 用这种方式实现自动重载可以实现

1、参考使用:有点意思的Kotlin的默认参数与JVMOverloads

image.png

4、 时序:Kotlin中init代码块和构造方法以及伴生对象中代码的调用时机及执行顺序_XeonYu的博客-CSDN博客_kotlin 的init

  1. kotlin中init作用域>constructor次级构造方法>companion object
  • 可以在init里inflate view和执行initview

三、踩坑

1、若自定义View中调用父布局view的ondraw方法。会导致死循环,必要情况下可类似该方法中的处理

四、实践-阶段一(简单使用)

  • 使用继承的方式自定义View

一、继承系统View

1、继承 ViewAnimator

  1. 优势:ViewAnimator是FrameLayout容器的基类,用于在其视图之间切换时执行动画。FrameLayout中添加的View都默认位于左上角,按照添加的顺序,最后添加的View位置最上层。
  2. ViewAnimator的使用就是调整ViewAnimator包裹的子View的显示层次,可以通过setDisplayedChild(int whichChild)方法,设置哪一个子View将被显示。

2、继承ScrollView

1、支持maxHeight

1、背景:

  1. ScrollView不支持设置maxHeight,可通过自定义View实现
  • 其实外界套一个View,设置maxHeight,做起来更快;

2、调用:

  1. 自定义view完成后代码中使用:app:maxHeight="500dp"即可

3、代码说明

  1. 这边思路就很明确了,采用EXACTLY最大模式,把mMaxHeight和AT_MOST设置给他即可;

4、自定义View代码展示

  1. 类代码
  • 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)
    }
}

  1. attrs配置属性
<declare-styleable name="HeightMaxScrollView">
    <attr name="maxHeight" format="dimension" />
</declare-styleable>
  • xml调用自定义属性:app:scrollX="600"

二、完全自定义

1、可打开拖动功能的数字进度条

1、参考文章1:android 自定义view实现数字进度条_android 自定义thumb进度条下方带数字

  1. 基本思路:左边画条线,中间画个Text,右边画条线;

2、参考文章:Android自定义View实现可拖拽的进度条

  1. 基本思路,底下画条线,上头画条线,线右边画个指示器;

3、类名:ProgressDigitalSeekBar

  1. 封进工具类里了
  • 支持点击与拖动
  • 支持按下状态
  • 数字进度条

五、实践-阶段二(使用画笔/画布)

一、简述

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:对画布进行变换

  1. Canvas 来做常见的二维变换
  2. 使用 Matrix 来做常见和不常见的二维变换;
  3. 使用 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博客

  1. setAntiAlias(true) 抗锯齿
  2. measureText() VS .getTextBounds()
  • 参考文章:Android Paint: .measureText() VS .getTextBounds()
  • 两个方法可以用来测量文字宽高信息的,只不过 .getTextBounds() 还可以获得高度信息,因为其使用一个 Rect 对象对宽高信息进行存储;而 .measureText() 则只是返回宽度信息。
  1. 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详解

5、Android Shape 详细使用

  • 堆叠圆环: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为上方 image.png

  1. 进行规约【遵循识图原则,别管提供的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>

3、selector样式,颜色与drawable可混用,格式写对即可

  1. 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>
  1. 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>
  1. 一半矢量图一半资源
<?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>
  1. 全矢量
<?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" />

image.png

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,获取,修改。

二、解决滑动冲突

1、一文解决Android View滑动冲突

2、一文读懂 View 事件分发机制

Sample

1、Android 悬浮窗功能的实现

2、手摸手教你做一个分段式进度条组件

3、android中canvas.drawText参数的介绍以及绘制一个文本居中的案例_moble_xie的博客

4、一个绚丽的loading动效分析与实现

5、可旋转的ImageView