看完这篇还不会自定义 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 !
写在最后
本文是我对个人学习过程的总结,如果大家有发现错误,希望能在评论区指出,谢谢
看完这篇还不会自定义 View ,我跪搓衣板的更多相关文章
- 看完这篇还不会 GestureDetector 手势检测,我跪搓衣板!
引言 在 android 开发过程中,我们经常需要对一些手势,如:单击.双击.长按.滑动.缩放等,进行监测.这时也就引出了手势监测的概念,所谓的手势监测,说白了就是对于 GestureDetector ...
- 看完这篇还不懂Redis的RDB持久化,你们来打我!
一.为什么需要持久化 redis里有10gb数据,突然停电或者意外宕机了,再启动的时候10gb都没了?!所以需要持久化,宕机后再通过持久化文件将数据恢复. 二.优缺点 1.rdb文件 rdb文件都是二 ...
- 看完这篇还不清楚Netty的内存管理,那我就哭了!
说明 在学习Netty的时候,ByteBuf随处可见,但是如何高效分配ByteBuf还是很复杂的,Netty的池化内存分配这块还是比较难的,很多人学习过,看过但是还是云里雾里的,本篇文章就是主要来讲解 ...
- 看完这篇还不会 Elasticsearch 搜索,那我就哭了!
本文主要介绍 ElasticSearch 搜索相关的知识,首先会介绍下 URI Search 和 Request Body Search,同时也会学习什么是搜索的相关性,如何衡量相关性. Search ...
- 看完这篇还不会用Git,那我就哭了!
你使用过 Git 吗?也许你已经使用了一段时间,但它的许多奥秘仍然令人困惑. Git 是一个版本控制系统,是任何软件开发项目中的主要内容.通常有两个主要用途:代码备份和代码版本控制.你可以逐步处理代码 ...
- 看完这篇还不懂 MySQL 主从复制,可以回家躺平了~
大家好,我是小羽. 我们在平时工作中,使用最多的数据库就是 MySQL 了,随着业务的增加,如果单单靠一台服务器的话,负载过重,就容易造成宕机. 这样我们保存在 MySQL 数据库的数据就会丢失,那么 ...
- 看完这篇再不会 View 的动画框架,我跪搓衣板
引言 众所周知,一款没有动画的 app,就像没有灵魂的肉体,给用户的体验性很差.现在的 android 在动画效果方面早已空前的发展,1.View 动画框架 2.属性动画框架 3.Drawable 动 ...
- 【最短路径Floyd算法详解推导过程】看完这篇,你还能不懂Floyd算法?还不会?
简介 Floyd-Warshall算法(Floyd-Warshall algorithm),是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似.该算法名称以 ...
- [转帖]看完这篇文章你还敢说你懂JVM吗?
看完这篇文章你还敢说你懂JVM吗? 在一些物理内存为8g的服务器上,主要运行一个Java服务,系统内存分配如下:Java服务的JVM堆大小设置为6g,一个监控进程占用大约 600m,Linux自身使用 ...
随机推荐
- ubuntu 忘记密码,忘记root密码的解决办法
ubuntu的root默认是禁止使用的,在安装的时候也没有要求设置root的密码.要使用,给root设置密码就行了,sudo passwd root .如果只是普通用户密码忘了,用root就可以修改. ...
- food(洛谷P4040 [AHOI2014/JSOI2014]宅男计划)
题目在这里 题目描述 外卖店一共有N种食物,分别有1到N编号.第i种食物有固定的价钱Pi和保质期Si.第i种食物会在Si天后过期.JYY是不会吃过期食物的. 比如JYY如果今天点了一份保质期为1天的食 ...
- centOS 7 安装java sdk 并测试是否安装成功
CentOS 6.X 和 7.X 自带有OpenJDK runtime environment (openjdk).它是一个在linux上实现开源的Java 平台. 安装方式: 1.输入以下命令,以 ...
- 354. Russian Doll Envelopes
You have a number of envelopes with widths and heights given as a pair of integers (w, h). One envel ...
- JWT在PHP使用及问题处理
官网 https://jwt.io/ 3.0版本 https://github.com/lcobucci/jwt 安装 composer require lcobucci/jwt 依赖 PHP 5.5 ...
- vue element-ui Tabs 标签页实现【更多】功能
element-ui Tabs本身是没有更多功能的,如果在外边添加一个更多按钮,又非常不好看, 而利用API中Tabs Attributes的before-leave勾子方法可以实现这个功能, 简单P ...
- 2018年东北农业大学春季校赛 E 阶乘后的0【数论】
链接:https://www.nowcoder.com/acm/contest/93/E来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言52428 ...
- JLOI2018 日志
JLOI2018 今年有幸参加吉林省的省选,考过之后在这里写一下总结和感受. DAY1: t1(chess):首先看到题目,第一想法是暴力,上来直接写了暴力,枚举所有的情况,再在这些情况里找到差值最大 ...
- luogu P1579 哥德巴赫猜想(升级版)
题目描述 一个等差数列是一个能表示成a, a+b, a+2b,..., a+nb (n=0,1,2,3,...)的数列. 在这个问题中a是一个非负的整数,b是正整数.写一个程序来找出在双平方数集合(双 ...
- Java获取路径的方法分析详解(Application/Web)
1.利用System.getProperty()函数获取当前路径: System.getProperty("user.dir");//user.dir用户当前的工作目录,输出:D: ...