看完这篇还不会自定义 View ,我跪搓衣板

自定义 View

在实际使用的过程中,我们经常会接到这样一些需求,比如环形计步器,柱状图表,圆形头像等等,这时我们通常的思路是去Google 一下,看看 github 上是否有我们需要的这些控件,但是如果网上收不到这样的控件呢?这时我们经常需要自定义 View 来满足需求。


接下来让我们开启自定义控件之路

关于自定义控件,一般辉遵循一下几个套路

  • 首先重写 onMeasure() 方法
  • 其次重写 onDraw() 方法
  • 总所周知 onMeasure()

方法是用来重新测量,并设定控件的大小,我们知道控件的大小是用 width 和 height 两个标签来设定的。通常有三种赋值情况 :

  • 首先直接赋值,比如直接给定 15dp 这样确切的大小
  • 其次 match_parent
  • 当然还有 wrap_parent

这时也许你就会有疑问,既然都已经有了这些属性,那还重写 onMeasure 干嘛,直接调用 View 的方法不就行了吗?但是你想想,比如你设计了一个圆形控件,用户在 width 和 height 都设置了 wrap_parent 属性,同时又给你传了一张长方形的图片,那结果会怎么样?必然得让你“方”啊。。所以这时就需要重写 onMeasure 方法,设定其宽高相等。


那么该如何重写 onMeasure() 方法呢?

首先把 onMeasure() 打出来

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

这时大家不眠会好奇,明明是重绘大小,那么给我提供宽高就行了呀?这个 int widthMeasureSpec, int heightMeasureSpec ,是个什么鬼?其实很好理解,大家都知道计算机中数据是已二进制存储的。同时,就像我之前讲的 View 的大小赋值形式有三种,那么在计算机中,要存储二进制数,需要几位二进制呢,答案很明了 -> 两位。同时大家也发现,这两个参数都是 int 型的。int 型数据在计算机中用 30为存储。所以聪明的 Google 就把这 30 位划分为两部分。第一部分两位拿来存类型,后面 28 位拿来存数据大小。


开始重写 onMeasure() 方法

首先,无论是 width 还是 height ,我们都得先判断类型,再去计算大小,so~ 咱先写个方法专门用于计算并返回大小。

测量模式 表示意思
UNSPECIFIED 父容器没有对当前View有任何限制,当前View可以任意取尺寸
EXACTLY 当前的尺寸就是当前View应该取的尺寸
AT_MOST 当前尺寸是当前View能取的最大尺寸
   private int getMySize(int defaultSize, int measureSpec) {
        // 设定一个默认大小 defaultSize
        int mySize = defaultSize;
        // 获得类型
        int mode = MeasureSpec.getMode(measureSpec);
        // 获得大小
        int size = MeasureSpec.getSize(measureSpec);

        switch (mode) {
            case MeasureSpec.UNSPECIFIED: {//如果没有指定大小,就设置为默认大小
                mySize = defaultSize;
                break;
            }
            case MeasureSpec.AT_MOST: {//如果测量模式是最大取值为size
                //我们将大小取最大值,你也可以取其他值
                mySize = size;
                break;
            }
            case MeasureSpec.EXACTLY: {//如果是固定的大小,那就不要去改变它
                mySize = size;
                break;
            }
        }
        return mySize;
    }

然后,我们再从 onMeasure() 中调用它

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 分别获得长宽大小
        int width = getMySize(100, widthMeasureSpec);
        int height = getMySize(100, heightMeasureSpec);

        // 这里我已圆形控件举例
        // 所以设定长宽相等
        if (width < height) {
            height = width;
        } else {
            width = height;
        }
        // 设置大小
        setMeasuredDimension(width, height);
    }

在 xml 中应用试试效果

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    tools:context=".activities.MainActivity">

    <com.entry.android_view_user_defined_first.views.MyView
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:default_size="@drawable/ic_launcher_background"/>

</LinearLayout>


到这里图就已经重绘出来了,让我们运行一下下

我们惊呆了,说好的控件呢??! 别急,咱还没给他上色呢,所以它自然是透明的。所以现在重写 onDraw() 方法,在 onDraw() 方法中

我们通过 canvas (安卓的一个绘图类对象进行图形的绘制)

    @Override
    protected void onDraw(Canvas canvas) {
        // 调用父View的onDraw函数,因为View这个类帮我们实现了一些
        // 基本的而绘制功能,比如绘制背景颜色、背景图片等
        super.onDraw(canvas);
        int r = getMeasuredWidth() / 2;//也可以是getMeasuredHeight()/2,本例中我们已经将宽高设置相等了
        Log.d(TAG, r + "");
        // 圆心的横坐标为当前的View的左边起始位置+半径
        int centerX = r;
        // 圆心的纵坐标为当前的View的顶部起始位置+半径
        int centerY = r;
        // 定义灰色画笔,绘制圆形
        Paint bacPaint = new Paint();
        bacPaint.setColor(Color.GRAY);
        canvas.drawCircle(centerX, centerY, r, bacPaint);
        // 定义蓝色画笔,绘制文字
        Paint paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setTextSize(60);
        canvas.drawText("大傻瓜", 0, r+paint.getTextSize()/2, paint);
    }

运行一下

大功告成!但是善于思考的可能会发现:使用这种方式,我们只能使用父类控件的属性,但是我们有时需要更多的功能,比如:图片控件需要改变透明度,卡片控件需要设定阴影值等等,那么父类控件的属性显然不够用了,这时我们就要开始实现自定义布局。

自定义布局属性 xml 属性


开始

由于自定义布局属性一般只需要对 onDraw() 进行操作。所以 onMeasure() 等方法的重写我就不再啰嗦了,这里我打算继承字 view 实现一个类似 TextView 的控件。

首先,让我们现在 res/values/styles 文件中增加一个自定义布局属性。

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <!--定义属性集合名-->
    <declare-styleable name="MyView">
        <!--我们定义为 default_size 属性为 屈指类型 像素 dp 等-->
        <attr name="text_size" format="dimension"/>
        <attr name="text_color" format="color"/>
        <attr name="text_text" format="string"/>
    </declare-styleable>

</resources>

这些标签都是什么意思呢?

首先:

MyView 是自定义布局属性的名字,也就是标签也就是入口,在 onDraw 中,用?context.obtainStyledAttributes(attrs, R.styleable.MyView); 获得自定义布局属性的全部子项。

其次:

?attr 中的 name 便是你属性的名字,比如说这个?text_size 、text_color 、text_text? 这三个属性,在 布局文件中就是:

    <com.entry.android_view_user_defined_first.views.MyView
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:text_text="hello world"
        app:text_size="20sp"
        app:text_color="@color/colorAccent"/>

最后:

format 标签,format 标签指定的是数据类型,具体可以看这篇,我在这里就不重复了 -> https://blog.csdn.net/pgalxx/article/details/6766677


解析和引用

上面我们先定义了属性,又在布局中对其赋值,那么实际中,我们如何在自定义控件里,获得它的实际值呢?让我们先写下构造方法,在构造方法中获得这些值的大小:

    private int textSize;
    private String textText;
    private int textColor;

    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyView);

        textSize = array.getDimensionPixelSize(R.styleable.MyView_text_size, 15);
        textText = array.getString(R.styleable.MyView_text_text);
        textColor = array.getColor(R.styleable.MyView_text_color,Color.BLACK);

        array.recycle();
    }
  • 建立一个 TypeArray 对象,用于存储自定义属性所传入的的值。obtainStyledAttributes 方法又两个参数,第二个参数就是我们在styles.xml文件中的 标签,即属性集合的标签,在R文件中名称为R.styleable+name
  • 然后根据 array 对象,获取传入的值。一般来说,它的方法有两个属性,第一个参数为属性集合里面的属性,R文件名称:R.styleable+属性集合名称+下划线+属性名称,第二个参数为,如果没有设置这个属性,则设置的默认的值
  • 最后记得将TypedArray对象回收

来重写下 onDraw() 方法。

由于在构造方法中,我们已经获得基本的值,所以在 onDraw() 中,将这些东西绘制出来就行了,这里直接上代码:

    @Override
    protected void onDraw(Canvas canvas) {
        // 调用父View的onDraw函数,因为View这个类帮我们实现了一些
        // 基本的而绘制功能,比如绘制背景颜色、背景图片等
        super.onDraw(canvas);
        int r = getMeasuredWidth() / 2;//也可以是getMeasuredHeight()/2,本例中我们已经将宽高设置相等了
        // 圆心的横坐标为当前的View的左边起始位置+半径
        int centerX = r;
        // 圆心的纵坐标为当前的View的顶部起始位置+半径
        int centerY = r;
        // 定义灰色画笔,绘制圆形
        Paint bacPaint = new Paint();
        bacPaint.setColor(Color.GRAY);
        canvas.drawCircle(centerX, centerY, r, bacPaint);
        // 定义蓝色画笔,绘制文字
        Paint paint = new Paint();
        paint.setColor(textColor);
        paint.setTextSize(textSize);
        canvas.drawText(textText, 0, r+paint.getTextSize()/2, paint);
    }

运行一下下:perfect !

写在最后

本文是我对个人学习过程的总结,如果大家有发现错误,希望能在评论区指出,谢谢 ??

原文地址:https://www.cnblogs.com/yuanhao-1999/p/11073398.html

时间: 2024-10-17 06:47:11

看完这篇还不会自定义 View ,我跪搓衣板的相关文章

看完这篇再不会Android权限组件设计,我跪搓衣板!

先看下 Demo 的 代码 不上 gif 了,录这个时间太长,gif 太大网页很卡.Demo 的思路如下,正常的判断权限,有3个回调,用户确认给予权限,用户不给,和用户点选不在显示系统权限弹窗.这里我们在用户不显示弹窗后的回调里启动系统权限设置页,在用户关闭权限设置页面过后,我们再检测下=刚刚用户给没给权限,没给权限的话就自己显示个弹窗,提示用户不给权限就关闭页面 Demo 代码如下: class?PermissionActivity : AppCompatActivity() { ????ov

看完这篇你还敢说,不懂Spring中的IoC容器?

一. 什么是IoC 什么是耦合和内聚 耦合指的就是模块之间的依赖关系.模块间的依赖越多,则表示耦合度越高,相应的维护成本就越高.内聚指的是模块内功能之间的联系.模块内功能的联系越紧密,则表示内聚度越高,模块的职责也就越单一.所以在程序开发中应该尽量的降低耦合,提高内聚.也就是设计原则中的开闭原则和单一职责原则. 工厂模式 工厂模式就是用来解决程序间耦合的一种设计模式.可以把所有要创建的对象放在工厂的一个集合里,当需要使用这个对象的时候,直接从工厂里面取出来用就行. 工厂模式的优点: 一个调用者想

看完这个你还不理解右值引用和移动构造 你就可以来咬我(上)

共分三篇,这是第一篇.另外两篇,看完这个你还不理解右值引用和移动构造 你就可以来咬我(中),看完这个你还不理解右值引用和移动构造 你就可以来咬我(下). C++ 右值引用 & 新特性 C++ 11中引入的一个非常重要的概念就是右值引用.理解右值引用是学习"移动语义"(move semantics)的基础.而要理解右值引用,就必须先区分左值与右值. 对左值和右值的一个最常见的误解是:等号左边的就是左值,等号右边的就是右值.左值和右值都是针对表达式而言的,左值是指表达式结束后依然存

wwwlyjustcom看完这篇,你就会Linux基本操作了,请耐看完199O8836661

只有光头才能变强这个学期开了Linux的课程了,授课的老师也是比较负责任的一位.总的来说也算是比较系统地学习了一下Linux了~本文章主要是总结Linux的基础操作以及一些简单的概念,对于开发者来说,能使用Linux做一些基本的操作是必要的!那么接下来就开始吧,当然了我的Linux仅仅是入门水平,如果有错的地方还需请大家多多包涵,并不吝在评论区指出错误~一.为什么我们要学习Linux相信大部分人的PC端都是用Windows系统的,那我们为什么要学习Linux这个操作系统呢???Windows图形

PDF怎么拆分成多个PDF,看完这篇文章你就明白了

PDF文件对于每一个经常在职场上工作的人来说,是特别常见的一个文档格式,PDF格式深受人们的喜爱,因为是特别好用的,但同时也是比较难进行编辑和修改的,特别是遇到PDF文档过长,为了方便浏览和及时查找对我们有用的内容,这就需要将PDF文档拆分成多个PDF,那么PDF怎么拆分成多个PDF?通过今天的文章就来告诉大家PDF文档拆分的方法,看完这篇文章你就明白了,那么我们就一起来看看吧.?方法一:软件拆分法借助软件:如果想要将PDF文档拆分成多个PDF,那就需要借助迅捷PDF转换器来实现,这个软件有着丰

零基础如何开始学习 Python?看完这篇从小白变大牛!

1.选择Python版本 对于Python工程师来说,Python的版本则是你们的工作环境.所以在学习之前一定要考虑选择一个合适自己的版本,Python3对零基础的小白很友好,易上手.选好版本后就可以开始学习了.创一个小群,供大家学习交流聊天如果有对学python方面有什么疑惑问题的,或者有什么想说的想聊的大家可以一起交流学习一起进步呀.也希望大家对学python能够持之以恒python爱好群,如果你想要学好python最好加入一个组织,这样大家学习的话就比较方便,还能够共同交流和分享资料,给你

关于 Docker 镜像的操作,看完这篇就够啦 !(下)

原文:关于 Docker 镜像的操作,看完这篇就够啦 !(下) 紧接着上篇<关于 Docker 镜像的操作,看完这篇就够啦 !(上)>,奉上下篇 !!! 镜像作为 Docker 三大核心概念中最重要的一个关键词,它有很多操作,是您想学习容器技术不得不掌握的.本文将带您一步一步,图文并重,上手操作来学习它. 目录: 一.Docker 删除镜像 1.1 通过标签删除镜像 1.2 通过 ID 删除镜像 1.3 删除镜像的限制 1.4 清理镜像 二.Docker 创建镜像 2.1 基于已有的镜像创建

如何更准确过滤信息?看完本篇你就知道

无论是使用Excel办公,还是浏览器搜寻关键字,都是需要通过条件过滤来实现.今天,手把手教你实现Java web项目--实现多条件过滤功能. 分页查询需求分析:在列表页面中,显示指定条数的数据,通过翻页按钮完成首页/上一页/下一页/尾页的查询数据分析:通过观察,页面上需要显示下面的几个数据:当前页:currentPage页面大小:pageSize总页数:totalPage首页:1上一页:prevPage下一页:nextPage尾页:endPage总条数:totalCount结果集:result

ae模板怎么套用?看完这篇ae模板套用教程你就懂了

首先我们要知道,模板,是什么?模板就是别人做好了的工程文件,所以东西都已经完成,不需要任何效果方面的修改了.那么ae模板怎么套用?就是你用的时候,替换.修改或者删去某些东西,变成自己的视频.比如片头LOGO模板,你肯定得要有自己的LOGO,去替换模板里的LOGO,然后渲染成片,才会成你的片头:看完这篇ae模板套用教程你就懂了. https://www.macdown.com 本站提供了海量AE模板,我们选择片头LOGO模板,做你自己的片头. 首先下载完后,打开这个模板. 然后看一眼项目栏 我用的