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()进行绘制。代码如下:

Paint paint1=new Paint(); //定义画笔
paint1.setColor(Color.YELLOW);
paint1.setStyle(Paint.Style.FILL);

然后,通过以下的代码,就可以绘制出一个带矩形框的Textview,但是需要在绘制完成后在调用父类的onDraw(),因为是在系统控件上拓展,所以,还要有其原来的功能。

@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,例子如下:

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

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

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

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

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

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

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);
  }

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

<com.example.myapplication.View.Buttonbtn
    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位为测量的大小。

@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()方法可以获取触摸点的坐标,来完成一些交互操作。

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类的时候,就需要重写里面的这三个方法:

  1. dispatchTouchEvent()
  2. onInterceptTouchEvent()
  3. onTouchEvent()

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

  1. dispatchTouchEvent()
  2. 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,事件处理后就不会再上传。事件的分发和拦截的流程就大致讲解完成了。

(0)

相关推荐

  • Android自定义view仿IOS开关效果

    本文主要讲解如何在 Android 下实现高仿 iOS 的开关按钮,并非是在 Android 自带的 ToggleButton 上修改,而是使用 API 提供的 onDraw.onMeasure.Canvas 方法,纯手工绘制.基本原理就是在 Canvas 上叠着放两张图片,上面的图片根据手指触摸情况,不断移动,实现开关效果. 废话不说,上效果图,看看怎么样 样式如下: 网上也有实现这种效果的,但是大都滑动没中间消失的动画,或者是很复杂,今天用简单的绘图方式实现,重点就在onDraw里绘图. 功

  • Android自定义view渐变圆形动画

    本文实例为大家分享了Android自定义view渐变圆形动画的具体代码,供大家参考,具体内容如下 直接上效果图 自定义属性 attrs.xml文件 <resources> <declare-styleable name="ProgressRing"> <!--进度起始色--> <attr name="pr_progress_start_color" format="color" /> <!--

  • Android自定义View实现公交成轨迹图

    本文实例为大家分享了Android自定义View实现公交成轨迹图的具体代码,供大家参考,具体内容如下 总体分析下:水平方向recyclewview,item包含定位点,站台位置和站台名称. 下面看实现: 1.继承framelayout,实现构造方法: public class BusStopPlateView extends FrameLayout { ... public BusStopPlateView(@NonNull Context context) { super(context);

  • Android自定义View实现微信支付密码输入框

    本文实例为大家分享了Android实现微信支付密码输入框的具体代码,供大家参考,具体内容如下 效果图 项目中使用到了支付密码功能,其实这类界面是比较常用的,涉及支付密码的输入的一般都会用到对的,所以单独地把这部分抽取出来,有需要的朋友可以拿去用哈! 效果就是支付,弹出密码框,输入密码,这个过程密码不可见,并且提供一个输入完毕的监听! 这个弹出层呢,其实就是一个DialogFragment,逻辑封装在其内部 一.弹出层进出动画 (anim文件) push_bottom_in.xml <?xml v

  • Android自定义View实现课程表表格

    自己闲下来时间写的一个课表控件,使用的自定义LinearLayout,里面View都是用代码实现的,最终效果如下图,写的可能有问题希望多多指点 创建一个自定义LinearLayout 控件用来装载课程的信息和课程的周数,和节数大概的布局三这样的 根据上面的看来觉得总体布局我分了两个 上面的星期是一个 下面的节数和格子是一个  总体使用Vertical  而单独内部者使用了Horizontal布局  中间使用了两种布局线条 是这样的 /** * 横的分界线 * * @return */ priva

  • Android自定义view仿iOS弹出框效果

    本文实例为大家分享了Android自定义view仿iOS弹出框的具体代码,供大家参考,具体内容如下 运行效果图 自定义对话框的使用,仿照ios.从底部弹出,类似pop窗口.包括消息.图片.列表及对话框. 好了,用法都会,直接贴上代码 1.layout布局文件 view_actionsheet.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="ht

  • Android自定义view实现车载可调整轨迹线

    本文实例为大家分享了Android自定义view完成车载可调整轨迹线的具体代码,供大家参考,具体内容如下 同事做的view,拿过来做个记录. /** * */ package com.text.myviewdemo.view; import org.apache.http.conn.routing.RouteInfo.LayerType; import android.content.Context; import android.graphics.Canvas; import android.

  • Android自定义View的使用及其原理知识点总结

    在Android开发中,系统提供给我们的UI控件是有限的,当我们需要使用一些特殊的控件的时候,只靠系统提供的控件,可能无法达到我们想要的效果,这时,就需要我们自定义一些控件,来完成我们想要的效果了.下面,我就来讲讲自定义控件的那些事. 首先,我来讲讲Android的控件架构.Android的控件可以被分为两类,分别是ViewGroup和View.在ViewGroup中可以包含多个View,并且管理他们.控件树就是有这两个部分组成的,控件树的上层负责的是下层控件的绘制和测量以及交互.我们在Acti

  • Android自定义View原理(实战)

    目录 1.为什么需要自定义View 2.自定义View的基本方法 3.自定义View的属性如何操作 4.View的视图结构 5.View的坐标系 6.View树的绘制流程 6.1 measure过程 6.2 分析自定义ViewGroup的onMeasure过程 6.3 分析自定义ViewGroup的onLayout过程 6.4 自定义Layout实战 6.5 细节 1.为什么需要自定义View Android系统内置的View不满足我们的业务需求 2.自定义View的基本方法 onMeasure

  • Android自定义View实现仿GitHub的提交活跃表格

    说明 本文可能需要一些基础知识点,如Canvas,Paint,Path,Rect等类的基本使用,建议不熟悉的同学可以学习GcsSloop安卓自定义View教程目录,会帮助很大. 上图就是github的提交表格,直观来看可以分为几个部分进行绘制: (1)各个月份的小方格子,并且色彩根据提交次数变化,由浅到深 (2)右下边的颜色标志,我们右对齐就可以了 (3)左边的星期,原图是从周日画到周六,我们从周一画到周日 (4)上面的月份,我们只画出1-12月 (5)点击时候弹出当天的提交情况,由一个小三角和

  • Android 自定义view和属性动画实现充电进度条效果

    近期项目中需要使用到一种类似手机电池充电进度的动画效果,以前没学属性动画的时候,是用图片+定时器的方式来完成的,最近一直在学习动画这一块,再加上复习一下自定义view的相关知识点,所以打算用属性动画和自定义view的方式来完成这个功能,将它开源出来,供有需要的人了解一下相关的内容. 本次实现的功能类似下面的效果: 接下来便详细解析一下如何完成这个功能,了解其中的原理,这样就能举一反三,实现其他类似的动画效果了. 详细代码请看大屏幕 https://github.com/crazyandcoder

  • Android自定义View 仿QQ侧滑菜单的实现代码

    先看看QQ的侧滑效果 分析一下 先上原理图(不知道能否表达的清楚 ==) -首先这里使用了 Android 的HorizontalScrollView 水平滑动布局作为容器,当然我们需要继承它自定义一个侧滑视图 - 这个容器里面有一个父布局(一般用LinerLayout,本demo用的是),这个父布局里面有且只有两个子控件(布局),初始状态菜单页的位置在Y轴上存在偏移这样可以就可以形成主页叠在菜单页的上方的视觉效果:然后在滑动的过程程中 逐渐修正偏移,最后菜单页和主页并排排列.原理搞清了实现起来

  • Android自定义View实现仿1号店垂直滚动广告条代码

    效果图展示,图片有点卡,耐心看会,原程序是很流畅的 实现步骤: 声明变量 初始化画笔.文本大小和坐标 onMeasure()适配wrap_content的宽高 onDraw()画出根据坐标画出两段Text 监听点击事件 在Activity中实现点击事件 实现原理(坐标变换原理):整个过程都是基于坐标Y的增加和交换进行处理的,Y值都会一直增加到endY,然后进行交换逻辑 步骤一:声明变量 由于1号店是两句话的滚动,所以我们也是使用两句话来实现的 private Paint mPaint; priv

  • Android自定义View实现打字机效果

    一.先来看看效果演示 二.实现原理: 这个其实不难实现,通过一个定时器不断调用TextView的setText就行了,在setText的时候播放打字的音效. 具体代码如下: import java.util.Timer; import java.util.TimerTask; import android.content.Context; import android.media.MediaPlayer; import android.text.TextUtils; import android

  • Android 自定义View实现单击和双击事件的方法

    自定义View, 1. 自定义一个Runnable线程TouchEventCountThread ,  用来统计500ms内的点击次数 2. 在MyView中的 onTouchEvent 中调用 上面的线程 3. 自定义一个Handler, 在TouchEventHandler 中 处理 统计到的点击事件, 单击, 双击, 三击, 都可以处理 核心代码如下: public class MyView extends View { ...... // 统计500ms内的点击次数 TouchEvent

  • Android自定义View实现多边形统计图示例代码

    前言   最近利用空闲时间学习了自定义View的一些知识,为了巩固,写了一个小东西,顺便分享出来,下面话不多说了,来一起看看详细的介绍吧. 简介   一个多边形统计图.边数,每个方向的值,每个点的文字等等都是可以设置的. 下面就来分析一下这个自定义View 这个view由以下几个部分组成 M层N边形 中心到各顶点的连线 填充区域 文字 @Override protected void onDraw(Canvas canvas) { if (!canDraw()) { return; } canv

  • Android自定义View仿探探卡片滑动效果

    Android自定义View仿探探卡片滑动这种效果网上有很多人已经讲解了实现思路,大多都用的是RecyclerView来实现的,但是我们今天来换一种实现思路,只用一个自定义的ViewGroup来搞定这个实现. 下面我们先看一下实现的效果: 这个自定义View用法也很简单,首先从github上下载或者fork这个项目,在布局中添加: <com.liyafeng.view.swipecard.SwipeCardLayout android:id="@+id/scl_layout" a

随机推荐