Android 自定义 View 之入门篇

读前思考

学习一门技术或者看一篇文章最好的方式就是带着问题去学习,这样才能在过程中有茅塞顿开、灯火阑珊的感觉,记忆也会更深刻。

  1. 如何实现自定义 View?
  2. MeasureSpec 是什么?有什么作用?
  3. 如何自定义属性值?
  4. 不同构造方法的作用?

1. 概述

什么时候会用到自定义 View?在我们的日常开发中,可能会遇到一些界面、控件无法用 Android 系统内置的 View 来完成的,这时候就需要我们使用自定义 View 来进行绘制了。

自定义 View 这东西很多人会比较畏惧,如果你认为他比较难,关键还是缺少实践写得少;如果你认为很简单,那可能是你没有遇到过那些奇葩的效果,需要高等数学和各种算法。

现在我们就一起敲开自定义 View 的大门,揭开它的神秘面纱,也许你就会发现,其实它并不可怕。

2. 了解自定义 View 的方法

本文主要做入门级别讲解,固然不会太复杂,自定义 View 的时候,主要重写两个方法

onMeasure():用于测量,你的控件占多大的地方由这个方法指定;

onMeasure( )方法中有两个参数,widthMeasureSpec 和 heightMeasureSpec,可以通过如下代码获取模式和大小

//获取高度模式
int height_mode = MeasureSpec.getMode(heightMeasureSpec);
//获取宽度模式
int with_mode = MeasureSpec.getMode(widthMeasureSpec);
//获取高度尺寸
int height_size = MeasureSpec.getSize(heightMeasureSpec);
//获取宽度尺寸
int width_size = MeasureSpec.getSize(widthMeasureSpec);

测量模式的话,有下面三种

  • UNSPECIFIED:任意大小,想要多大就多大,尽可能大,一般我们不会遇到,如 ListView,RecyclerView,ScrollView 测量子 View 的时候给的就是 UNSPECIFIED ,一般开发中不需要关注它;

  • EXACTLY:一个确定的值,比如在布局中你是这样写的 layout_width="100dp","match_parent","fill_parent";

  • AT_MOST:包裹内容,比如在布局中你是这样写的 layout_width="wrap_content"。

onDarw():用于绘制,你的控件呈现给用户长什么样子由这个方法决定;

onDarw( ) 方法中有个参数 Canvas,Canvas 就是我们要在上面绘制的画布,我们可以使用我们的画笔在上面进行绘制,最后呈现给用户。

3. 实现自定义 View

接下来我们就动手实现一个简单的自定义 View,重在讲解实现自定义 View 的过程。

  1. 新建类继承 View,不需要指定 style 的话,创建两个构造函数即可,重写 onMeasure( ) 和 onDraw( ) 方法。
public class ViewProperty extends View {
    /**
     *在java代码创建视图的时候被调用,
     *如果是从xml填充的视图,就不会调用这个
     */
    public ViewProperty(Context context) {
        super(context);
    }
    /**
     * 这个是在xml创建但是没有指定style的时候被调用
     */
    public ViewProperty(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

}
  1. 添加自定义属性。在 res/values/styles 文件下添加自定义属性,如果没有此文件,则新建即可。
<!-- 这里自定义了两个属性,一个是默认大小,一个是背景颜色-->
<declare-styleable name="ViewProperty">
    <attr name="default_size" format="dimension"/>
    <attr name="backgroundColor" format="color"/>
</declare-styleable>
  1. 在两个参数的构造函数中获取属性,并进行画笔初始化
public ViewProperty(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    
    //通过 TypedArray 获取自定义属性,使用完后记得及时回收
    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ViewProperty);
    mSize = array.getDimensionPixelSize(R.styleable.ViewProperty_default_size, 10);
    mColor = array.getColor(R.styleable.ViewProperty_backgroundColor, Color.RED);
    array.recycle();

    //初始化画笔
    mPaint = new Paint();
    mPaint.setColor(mColor);
    }
  1. onMeasure( )方法中做具体测量操作
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int height = 100;
    int width = 100;
    //获取宽高的测量模式及具体尺寸
    int height_mode = MeasureSpec.getMode(heightMeasureSpec);
    int with_mode = MeasureSpec.getMode(widthMeasureSpec);
    int height_size = MeasureSpec.getSize(heightMeasureSpec);
    int width_size = MeasureSpec.getSize(widthMeasureSpec);
    //根据具体测量模式来给定不同的尺寸
    if (height_mode == MeasureSpec.AT_MOST) {
        height = Dp2Px(getContext(),100);
    } else if (height_mode == MeasureSpec.EXACTLY) {
        height = height_size;
    }
    if (with_mode == MeasureSpec.AT_MOST) {
        width = Dp2Px(getContext(),100);
    } else if (with_mode == MeasureSpec.EXACTLY) {
        width = width_size;
    }
    //调用setMeasuredDimension()方法,传入测量后的尺寸值
    setMeasuredDimension(width, height);

    }
  1. 在 onDraw( ) 方法中做具体绘制工作
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //这里绘制一个矩形
    //传入左、上、右、下坐标及画笔
    canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);
}

/**
*下面是两个工具类,用于 dp 和 px 的转换,
* 由于代码中使用的是 px,布局中尺寸一般使用 dp,
* 所以需要做个转换
*/
public static int Px2Dp(Context context, int px) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, px, context.getResources().getDisplayMetrics());
}
public static int Dp2Px(Context context, int dpi) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpi, context.getResources().getDisplayMetrics());
}

4. 展示自定义 View

先 Rebuild 的一下项目,让编译器识别自定义 View,然后在布局中引用

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    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"
    tools:context="com.keven.jianshu.part3.ShowActivity">
    <com.keven.jianshu.part3.ViewProperty
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:backgroundColor="@color/colorAccent"/>
</android.support.constraint.ConstraintLayout>

显示效果

image

全部代码见 GitHub 地址
https://github.com/keven0632/JianshuNote

文章已经读到末尾了,不知道最初的几个问题你都会了吗?如果不会的话?可以再针对不会的问题进行精读哦!答案都在文中,相信你肯定可以解决的!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269