Android开发进阶——自定义View的使用及其原理探索

简介: Android开发进阶——自定义View的使用及其原理探索  在Android开发中,系统提供给我们的UI控件是有限的,当我们需要使用一些特殊的控件的时候,只靠系统提供的控件,可能无法达到我们想要的效果,这时,就需要我们自定义一些控件,来完成我们想要的效果了。

Android开发进阶——自定义View的使用及其原理探索
  在Android开发中,系统提供给我们的UI控件是有限的,当我们需要使用一些特殊的控件的时候,只靠系统提供的控件,可能无法达到我们想要的效果,这时,就需要我们自定义一些控件,来完成我们想要的效果了。下面,我就来讲讲自定义控件的那些事。

  首先,我来讲讲Android的控件架构。Android的控件可以被分为两类,分别是ViewGroup和View。在ViewGroup中可以包含多个View,并且管理他们。控件树就是有这两个部分组成的,控件树的上层负责的是下层控件的绘制和测量以及交互。我们在Activity中使用的findViewById()方法,就是在控件树中用深度遍历的方法搜索到对应的ID的。每一颗控件树的顶部,都有个ViewParent对象,他是整棵树的核心,负责调度所有的交互事件。在Activity中,我们是使用setContentView()来加载布局的。每个Activity都是包含着一个Window对象的,在Android中通常是PhoneWindow,他将一个DecorView作为整个窗口的根View,将要显示的内容呈现在window上。DecorView又分为两个部分,一个是TitleView,一个是ContentView。ContentView是一个ID为content的Framelayout,布局文件就是设置在这里面的。而TitleView就是我们看到topbar标题栏。这就是activity加载布局文件的过程了。

  接下来,我们开始讲自定义控件的使用,下面讲解使用的时候,会夹带着一些原理的分析。自定义控件可以分为三种类型,一种是拓展谷歌提供的系统控件,来达到自己想要的效果。一种是将系统提供的控件组合在一起,作为一个组合控件来使用。还有一种是重新绘制测量一个全新的控件。

一、拓展谷歌提供的系统控件

  假如我们要对Textview控件进行拓展,首先我们要定义一个类继承TextView,选择性的重写它的onDraw()、onMeasure()、onTouchEvent()等方法。其中,onDraw()负责对图像的绘制,onMeasure()负责测量位置,onTouchEvent()负责设置触摸的事件。当我们想直接绘制出有背景颜色的TextView时,可以在类中定义画笔,在onDraw()进行绘制。代码如下:

1
2
3
Paint paint1=new Paint(); //定义画笔
paint1.setColor(Color.YELLOW);
paint1.setStyle(Paint.Style.FILL);
  然后,通过以下的代码,就可以绘制出一个带矩形框的Textview,但是需要在绘制完成后在调用父类的onDraw(),因为是在系统控件上拓展,所以,还要有其原来的功能。

1
2
3
4
5
6
7
@Override

protected void onDraw(Canvas canvas) {
    canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),paint1);//绘制矩形
    canvas.save();
    super.onDraw(canvas);
    canvas.restore();
}

  

  使用canvas对象就可以进行绘图了,对canvas的讲解,我将会在下一篇博客讲解。

  然后,我们只需要在布局文件中加入自定义的控件即可,在布局文件中,自定义view的名字就是自定义控件类的包名加上类名,假设定义CustomTextview类继承TextView,例子如下:

1
2
3

    android:layout_width="wrap_content"
    android:layout_height="match_parent"></com.example.myapplication.View.Buttonbtn>

二、将系统提供的控件组合在一起

  除了拓展原有的控件以外,我们还可以将控件组合成一个新的控件使用。首先,我们先定义一个新的布局文件,并把Imageview和Textview加入,代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13

android:id="@+id/iv"
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@mipmap/ic_launcher" />

android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="消息"
android:textSize="13sp" />

  然后我们定义一个类继承LinearLayout,在类的构造方法中对控件和布局进行初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
public void init(Context context) {

    //指定线性布局的显示方式,垂直
    setOrientation(VERTICAL);
    //设置用户期望的布局方式
    LayoutParams mLayoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    setLayoutParams(mLayoutParams);
    setGravity(Gravity.CENTER);
    setPadding(4, 4, 4, 4);
   //设置其布局文件
    View mButtonbtnView = LayoutInflater.from(context).inflate(layout.botton_btn_view, this, true);
    mImageView = mButtonbtnView.findViewById(id.iv);
    mTextView = mButtonbtnView.findViewById(id.tv);
}

  接下来,它的使用方法就和拓展控件的方法一样了,直接在布局文件中,加入控件即可。

1
2
3

    android:layout_width="wrap_content"
    android:layout_height="match_parent"></com.example.myapplication.View.Buttonbtn>

三、重写View来实现全新的控件

  当系统原生的控件无法满足我们需求时,我们就可以定义一个新的控件来完成需要的功能。创建一个新的控件,需要继承View类,其难点主要在于绘制控件和实现交互。在继承View类时,我们还需要重写它的onDraw(),onMeasure()、onTouchEvent()来实现绘制、测量和触摸事件。

  onDraw()绘制就是在canvas对象上调用其一系列方法进行绘图,绘制控件的形状。

  onMeasure()

  下面,我来讲讲onMeasure()。在绘制View之前,我们需要告诉系统我们需要画一个多大的View以及他的位置,这就是onMeasure()进行的了。首先,我们来了解一下测量的三种模式:

  EXACTLY:精确值模式,在指定view具体数值的时候会用到。

  AT_MOST:最大值模式,将控件设置为"wrap_content"用到,它会根据子控件或者内容变化而变化。

  UNSPECIFIED:绘制控件想要多大就可以多大。

  根据以上三种模式,我们就可以在测量的时候判断和使用了。首先,我们重写一个view的onMeasure()方法。再通过使用MeasureSpec类获得控件的测量模式。MeasureSpec使用的是位运算,其高2位为测量的模式,剩下的30位为测量的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

   int widthMode = MeasureSpec.getMode(widthMeasureSpec);
   int widthSize = MeasureSpec.getSize(widthMeasureSpec);

   if (widthMode == MeasureSpec.EXACTLY) {

   } else if (widthMode == MeasureSpec.AT_MOST) {

   } else if (widthMode == MeasureSpec.UNSPECIFIED) {

   }

}
  以上代码就是通过判断测量模式来给定义控件的大小,这里只是测量了控件的宽度,控件高度的测量也是类似的,就不在做详解。

  前面说过,ViewGroup是用来管理控件的,当ViewGroup的大小为"wrap_content"时,它就会遍历其所有子View,来获得子View的大小,再来设置自身的大小。我们使用过的布局,像RelativeLayout,LinearLayout都是继承ViewGroup的,所以他们也是使用这种方法来获得自己的大小的。

  onTouchEvent()

  onTouchEvent()就是我们所说的触摸事件,由于Android手机是触屏的,所以我们自定义View在触摸屏幕的时候,也需要有一定的处理来完成交互。当重写onTouchEvent方法的时候,我们可以看到,需要传入MotionEvent的对象。我们可以通过这个类来设置触摸的事件,也可以获得触摸点的位置。我们可以通过getAction()来获取触摸事件的行动,来判断是否按下屏幕或者移动。在Android的坐标系中,我们都知道Android的屏幕在竖屏的时候,以左上角的位置为原点,向右为x轴的正方向,向下为y轴的正方向,知道了这个后,我们就可以通过调用getX()和getY()方法可以获取触摸点的坐标,来完成一些交互操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public boolean onTouchEvent(MotionEvent event) {

    float x;
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        {
            x=event.getX();
        }
            break;
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            break;
    }
    return true;
}

  以上就是自定义控件常用重写的方法,通过了重写这几个方法,我们基本就可以实现一个简易的自定义控件了。下面,我们来了解下控件的事件拦截机制的原理。

事件拦截机制分析

  我们前面讲过,控件结构是树形结构,一个ViewGroup中可能有多个ViewGroup或者View,那么,触摸事件是怎么准确的分配给每个View和ViewGroup的呢。我们假设有一个ViewGroupA,在他的里面嵌套着ViewGroupB,而在ViewGroupB的里面,又嵌套着一个View。当我们重写ViewGroupA类的时候,就需要重写里面的这三个方法:

    dispatchTouchEvent()

    onInterceptTouchEvent()

    onTouchEvent()

  而在重写View的时候,需要重写两个方法:

    dispatchTouchEvent()

    onTouchEvent()

  可以根据名字看出,ViewGroup中比View多了onInterceptTouchEvent()方法,这个方法就是事件拦截的核心。在每一个方法中Log一下,再点击View的时候,就会发现方法调用的顺序:

    首先,调用了ViewGroupA类的dispatchTouchEvent()和onInterceptTouchEvent()。

    再调用了ViewGroupB类的dispatchTouchEvent()和onInterceptTouchEvent()。

    再到View的dispatchTouchEvent()方法。

  这个调用的顺序就是事件传递的顺序,而事件处理的顺序则是:

    View的onTouchEvent()。

    ViewGroupB的onTouchEvent()。

    ViewGroupA的onTouchEvent()。

  

  由此,可以看出,事件的分发是由上层的ViewGroup发布的,再逐层下发。而事件的处理,则是由下层的View处理后,再逐层上传。前面也说过,onInterceptTouchEvent()是事件拦截的核心,那么,只要设置它的返回值为true,就可以拦截事件,使其不再下发,而onTouchEvent()返回false,事件处理后就不会再上传。事件的分发和拦截的流程就大致讲解完成了。

  最后,这篇博客是我看了《Android群英传》后总结和归纳出的,希望能帮到正在学习自定义View的朋友,同时,有理解错误的地方,也欢迎大家指出。

原文地址https://www.cnblogs.com/lwkdbk/p/11279008.html

相关文章
|
5天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
7天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
7天前
|
存储 API 开发工具
探索安卓开发:从基础到进阶
【10月更文挑战第37天】在这篇文章中,我们将一起探索安卓开发的奥秘。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和建议。我们将从安卓开发的基础开始,逐步深入到更复杂的主题,如自定义组件、性能优化等。最后,我们将通过一个代码示例来展示如何实现一个简单的安卓应用。让我们一起开始吧!
|
8天前
|
存储 XML JSON
探索安卓开发:从新手到专家的旅程
【10月更文挑战第36天】在这篇文章中,我们将一起踏上一段激动人心的旅程,从零基础开始,逐步深入安卓开发的奥秘。无论你是编程新手,还是希望扩展技能的老手,这里都有适合你的知识宝藏等待发掘。通过实际的代码示例和深入浅出的解释,我们将解锁安卓开发的关键技能,让你能够构建自己的应用程序,甚至贡献于开源社区。准备好了吗?让我们开始吧!
21 2
|
缓存 Java Android开发
|
9天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
【10月更文挑战第35天】在数字化时代,安卓应用的开发成为了一个热门话题。本文旨在通过浅显易懂的语言,带领初学者了解安卓开发的基础知识,同时为有一定经验的开发者提供进阶技巧。我们将一起探讨如何从零开始构建第一个安卓应用,并逐步深入到性能优化和高级功能的实现。无论你是编程新手还是希望提升技能的开发者,这篇文章都将为你提供有价值的指导和灵感。
|
9天前
|
Android开发
布谷语音软件开发:android端语音软件搭建开发教程
语音软件搭建android端语音软件开发教程!
|
17天前
|
编解码 Java Android开发
通义灵码:在安卓开发中提升工作效率的真实应用案例
本文介绍了通义灵码在安卓开发中的应用。作为一名97年的聋人开发者,我在2024年Google Gemma竞赛中获得了冠军,拿下了很多项目竞赛奖励,通义灵码成为我的得力助手。文章详细展示了如何安装通义灵码插件,并通过多个实例说明其在适配国际语言、多种分辨率、业务逻辑开发和编程语言转换等方面的应用,显著提高了开发效率和准确性。
|
16天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
28 5
|
14天前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!