关于 Android 自定义控件,你想谈一些什么?

Android · d_clock · 于 发布 · 最后由 idtkma回复 · 10122 次阅读
30

最近在开始深入的去学习Android自定义控件这块的知识,发现涉及到各方各面的知识点略多,如:

  • View、ViewGroup的绘制
  • 事件分发
  • 各种动画效果
  • 滚动嵌套机制
  • 还有涉及到相关的数学知识等等

作为刚刚想深入学习自定义控件这块知识的孩纸,想知道那些擅长于写各种控件的大牛们,是怎样去一步一步学习到最终可以随心所欲的造控件的!坐等老司机开车!

共收到 8 条回复
96

Android开发自定义控件这个需求其实还是蛮常见的,Android标准控件库根本满足不了日益脑洞的产品和设计师.

自定义控件原则:一个好的自定义控件应当和Android本身提供的控件一样,封装了一系列的功能以供开发者使用,不仅具有完备的功能,也需要高效的使用内存和CPU。

开发自定义控件的步骤:
1、了解View的工作原理 ;
2、 编写继承自View的子类;
3、 为自定义View类增加属性 ;
4、 绘制控件 ;
5、 响应用户消息 ;
6 、自定义回调函数 。

Android本身提供了一些指标:
1. 应当遵守Android标准的规范(命名,可配置,事件处理等);
2. 在XML布局中科配置控件的属性;
3. 对交互应当有合适的反馈,比如按下,点击等;
4. 具有兼容性, Android版本很多,应该具有广泛的适用性。

接下来就看设计师和产品经理的脑洞啦

30
d_clock · #2 ·

#1楼 @xiaochenyi get到不少点子哦,xiaochenyi童鞋在自定义控件这块已经颇具心得。我还是这一块的入门小白,目前进阶学习中,说说自己当前的学习模式吧

1、看书,买了徐宜生的《Android群英传》正在啃;
2、看API文档和一些技术博客;(也即是你说的,了解View的工作原理)
3、挑一个系统控件代码来解读学习,目前的感受是,View和动画的知识这块随着Android版本升级也在不断更新变化;(所以需要考虑你提到的兼容性问题)
4、挑一个有开源代码的控件,来进行高仿实现,一步步深入掌握一些知识点;
5、直接看一些动态效果图,按照自己学习到的原理和思路进行实现;(可惜,目前我还没达到这一步,哭。。。。)

再说说自己学习过程中碰到的一些难点

1、调试效果非常耗时,十天半个月真是见怪不怪;(有没有关于调试自定义控件这块的经验可以分享一下呢?)
2、对数学知识有一点要求,从动画效果这块可以看出,所以也在补习大学的功课中;
3、耐心,调试效果这块真的要沉得住气来慢慢调试;(反省一下自己有点心急,有待提高啊!)

写了这么多,补上感慨最深的一点:学习要持之以恒

哈哈,xiaochenyi童鞋在学习自定义控件这块上,有木有更具体的经历可以再分享一下呢?

491

一、什么是自定义控件

1、概念

简单算来学习Android已经有一年时间了,从最初觉得别人写的软件好厉害到这么厉害的软件我也能写。但是现在还是会被有些软件的UI和动画所惊艳。一开始以为UI上的控件都是画出来的,后来才知道这些控件都有一个共同的名字——自定义控件。

自定义控件

为什么要自定义控件呢?当然不是为了简单的好看。我们知道Android官方自带的控件种类很多,基本能够满足日常的开发需求。但是一件产品的开发不仅仅需要功能上的完善,更要追求用户体验。所以单从用户体验上来说,官方的控件是远远谈不上体验的。所以越来越多的APP使用了自定义控件,一方面美观好看,另一方面极大的提高了用户体验,何乐而不为呢?

而随着Android技术越来越成熟,基本的控件有时已经满足不了简单的开发需求了,这个时候就需要我们自定义出满足功能需求的控件来实现APP的一些需求。

2、实现方式

一般实现自定义控件会有三种方式:

  • 继承已有的控件实现
  • 组合已有的控件实现
  • 完全自定义控件

第一种方式其实也就相当于扩展已有控件的功能,这种实现方式比较简单;第二种组合方式目的是通过多种控件的组合来完成一种控件的需求,也就是通过这种方式自定义出来的控件具有多种基本控件的功能,更加强大,较第一种而言这种实现方式比较复杂;而第三种完全自定义控件这就更加复杂了,这需要我们新建一种控件继承View/ViewGroup,并实现一些其中的属性或方法。

总的来说,按照需求我们采取不同的方式。这里我们先说一下完全自定义控件的方式。

二、完全自定义控件

下面我就分享我最近学习黑马教程中的一个自定义开关的过程。

自定义开关

1、确定需求

从图中我们可以看出,这个开关是由两部分组成,第一部分是背景图也就是显示“开/关”的图片,第二部分是前景图也就是开关小滑块。那么第一步我们肯定是需要将两者组合在一起,成为一个一个全新的控件。所以说这一步中,我们需要绘制出控件的基本形状。

因为这个开关是一个滑动开关,需要用户手动触摸才能改变状态,那么我们肯定需要实现这个控件的触摸事件,通过触摸事件来改变开关的状态。

开关的状态既然需要改变,那么如何知道状态发生改变呢?没错,就是事件监听。我们还需要将这个控件绑定事件监听器,来实时监听开关状态的改变。

一个控件有了形状,有了触摸事件和状态监听器,就已经能实现一些基本的功能需求了。所以总的来说,我们需要做三件事情:

  • 绘制控件
  • 触摸事件监听
  • 状态事件监听

下面我就按照顺序来实现相关的功能。

2、绘制控件

首先我们新建一个 CustomSwitchView 类,类直接继承于View。继承类过后我们需要实现类的几种构造方法。在这里如果用Eclipse新建类的话,我们可以直接勾选 Constructors from superclass 选项。用Android Studio新建类的话,我们可以在类建立过后利用快捷键 Alt + Enter 来实现构造方法。

         /**
     * @ClassName: CustomSwitchView
     * @Description:自定义控件 继承View
     * @author: iamxiarui@foxmail.com
     * @date: 2016年5月5日 下午6:51:49
     */
    public class CustomSwitchView extends View {
         /**
         * @Description:用于代码创建控件
         */
         public CustomSwitchView(Context context) {
             super(context);
         }

         /**
         * @Description:用于在XML中使用,可以指定自定义属性
         */
         public CustomSwitchView(Context context, AttributeSet attrs) {
             super(context, attrs);
         }

         /**
         * @Description:用于在XML中使用,可以指定自定义属性,并指定样式
         */
         public CustomSwitchView(Context context, AttributeSet attrs, int defStyleAttr) {
             super(context, attrs, defStyleAttr);
         }

         /**
         * @Description:用于在XML中使用,可以指定自定义属性,并指定样式及其资源
         */
         public CustomSwitchView(Context context, AttributeSet attrs, 
                                                     int defStyleAttr, int defStyleRes) {
             super(context, attrs, defStyleAttr, defStyleRes);
         }
    }

当构造函数实现之后,我们就需要实现控件的一些属性。这里我们先不用自定义属性,而用自定义的方法来设置相关属性。先定义如下变量,后面我们需要用到:

    //定义背景图

    private Bitmap switchBackgroupBitmap;

    //定义前景图

    private Bitmap switchForegroupBitmap;

变量定义好之后我们需要自定义两个方法,分别设置前景图和背景图,而两个方法的参数都是一个 int 类型的资源ID,然后通过BitmapFactory对象来将资源ID对应的图片资源添加到控件上:

    /**
     * @Title: setBackgroundPic
     * @Description:设置背景图
     * @return: void
     */
     public void setBackgroundPic(int switchBackground) {
        switchBackgroupBitmap = BitmapFactory.decodeResource(getResources(), switchBackground);
     }

     /**
     * @Title: setForegroundPic
     * @Description:设置前景图
     * @return: void
     */
     public void setForegroundPic(int switchForeground) {
         switchForegroupBitmap = BitmapFactory.decodeResource(getResources(), switchForeground);
     }

注意这个时候不是说我们设置上图片就能显示出来,因为我们是自定义控件,所以我们必须将控件绘制在View中,这就涉及到一个非常重要知识——Android界面绘制流程。

界面绘制流程

从图中我们可以看出Android界面绘制流程分为三个部分,第一部分是测量(Measure),在这部分里面View会先做一次测量,计算出自己需要占用多大的面积,我们可以重写 onMeasure() 方法来重新定义View的宽高。第二部分是布局(Layout),这个部分我们需要做的事情就是将整个View中所有的子View大小宽高设置好,可以通过复写 onLayout() 方法来实现,当然如果你的自定义View中没有子View,那就不需要设计这一部分了。第三部分是绘制(Draw),这个很好理解,就是在创建的画布(Canvas)上绘制出我们所需要的View样式,同样可以通过复写 onDraw() 方法来实现。

由于我们现在所要做的就是一个简单开关,只需要直接继承View,并将开关的两张图设置成控件的背景即可,所以我们只要重写 onMeasure() 和 onDraw() 这两个方法。

    /**
     * @Title: onMeasure
     * @Description:测量出自定义控件的长宽
     * @return: void
     */
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         setMeasuredDimension(switchBackgroupBitmap.getWidth(), switchBackgroupBitmap.getHeight());
     }

     /**
     * @Title: onDraw
     * @Description:绘制控件
     * @return: void
     */
     @Override
     protected void onDraw(Canvas canvas) {
         // 先绘制背景
         canvas.drawBitmap(switchBackgroupBitmap, 0, 0, paint);
         //再绘制前景
         canvas.drawBitmap(switchForegroupBitmap, 0, 0, paint);
     }

当上面的方法重写完毕后,我们就可以在Activity中设置图片并显示控件了:

    buttonCSView = (CustomSwitchView) findViewById(R.id.csv_button);
    // 设置背景图 
    buttonCSView.setBackgroundPic(R.drawable.switch_background);
    // 设置前景图 
    buttonCSView.setForegroundPic(R.drawable.switch_foreground);

当然在此之前,我们需要一个画笔工具,因为每一个画笔都需要在创建的时候使用,所以我将画笔工具的创建放在单独的方法中,而且在每一个构造函数中,调用这个方法,也就相当于只要创建了自定义控件,那么就自动创建了一个画笔工具。

    /**
     * @Title: initView
     * @Description:初始化View
     * @return: void
     */
     private void initView() {
         paint = new Paint();
     }

除此之外呢,还需要在布局文件中定义出控件,注意一定要写View所在类的完整包名,在这里我的包名是xr.customswitch.view.CustomSwitchView

    <xr.customswitch.view.CustomSwitchView
         android:id="@+id/csv_button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_centerInParent="true" />;

好了进行到这一步的话,我们的自定义控件就算绘制出来了。但是一个控件绘制出来还不算一个完整的控件,所以我们还需要添加一些事件监听。

3、触摸事件

在写触摸事件之前,我们需要声明一些参数。首先开关在开或者关的时候一定有个状态(isSwitchState),我们必须要根据这个状态来处理一些逻辑问题,所以这个状态我们必须要明确。其次由于是触摸事件,所以我们还需要一个触摸状态(isTouchState),根据触摸状态我们处理触摸事件逻辑。而如何知道开关状态和触摸状态呢,当然是根据前景图中的开关滑块相对于背景图的位置来确定,而这个开关一定是处于背景图中的,不能超过背景图的范围,所以我们必须明确当前开关位置(currentPosition)和这个开关能滑动的最大位置(maxPosition)。

    private boolean isSwitchState = true; //开关状态

    private boolean isTouchState = false; //触摸状态

    private float currentPosition; // 当前开关位置

    private int maxPosition; // 开关滑动最大位置

定义好相关参数及变量后,我们需要知道开关位置的参数规定,直接上图吧。

开关位置参数规定

下面我们就需要重写 onTouchEvent( )与 onDraw() 方法了,由于本文主要讲的是自定义控件的步骤,所以具体逻辑上的处理就看注释吧,写的很详细:

    /**
     * @Title: onTouchEvent
     * @Description:触摸事件
     * @return: void
     */
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
                 // 处于触摸状态
                 isTouchState = true;
                 // 得到位置坐标
                 currentPosition = event.getX();
                 break;

             case MotionEvent.ACTION_MOVE:
                 currentPosition = event.getX();
                 break;

             case MotionEvent.ACTION_UP:
                 // 触摸状态结束
                 isTouchState = false;
                 currentPosition = event.getX();
                 // 中间标志位置
                 float centerPosition = switchBackgroupBitmap.getWidth() / 2.0f;

                 // 如果开关当前位置大于背景位置的一半 显示关 否则显示开
                 boolean currentState = currentPosition > centerPosition;

                 // 当前状态置为开关状态
                 isSwitchState = currentState;
                 break;
          }
         // 重新调用onDraw方法,不断重绘界面
         invalidate();
         return true;
     }

    /**
     * @Title: onDraw
     * @Description:绘制控件
     * @return: void
     */
     @Override
     protected void onDraw(Canvas canvas) {
         // 先绘制背景
         canvas.drawBitmap(switchBackgroupBitmap, 0, 0, paint);

         // 如果处于触摸状态
         if (isTouchState) {
             // 触摸位置在开关的中间位置
             float movePosition = currentPosition - switchForegroupBitmap.getWidth() / 2.0f;
             maxPosition = switchBackgroupBitmap.getWidth() - switchForegroupBitmap.getWidth();
             // 限定开关滑动范围 只能在 0 - maxPosition范围内
             if (movePosition < 0) {
                 movePosition = 0;
             } else if (movePosition > maxPosition) {
                 movePosition = maxPosition;
             }
             // 绘制开关
             canvas.drawBitmap(switchForegroupBitmap, movePosition, 0, paint);
         }
         // 直接绘制开关
         else {
             // 如果是真,直接将开关滑块置为开启状态
             if (isSwitchState) {
                 maxPosition = switchBackgroupBitmap.getWidth() - switchForegroupBitmap.getWidth();
                 canvas.drawBitmap(switchForegroupBitmap, maxPosition, 0, paint);
             } else {
                // 否则将开关置为关闭状态
                canvas.drawBitmap(switchForegroupBitmap, 0, 0, paint);
             }
         }
     }

这里有几个需要注意的问题:

第一触摸事件的返回值一定要返回 true ,目的是让触摸事件一直生效。

第二就是在触摸事件返回之前,我们需要重新绘制控件,这个时候我们没办法直接调用 onDraw() 方法,Android给我们提供了一个方法 invalidate() ,这个方法的目的就是重新调用一次 onDraw() 方法,十分方便。

第三就是开关位置的判定,因为我们只有两个状态,那么如果开关已经划过了背景宽度的一半,那么我们就判定开关位置已经变化。当然也要注意滑块的位置范围在0~maxPosition之间。

到这里我们的触摸事件就算全部搞定了,但是我们知道,开关滑动后需要完成相关逻辑处理。这个时候就需要一个事件监听者,来实时监听开关状态的变化。

4、事件监听者

事件监听者我们不会很陌生,经常使用到的是 onClickListener() ,我们就仿照这个类来实现开关状态的监听。

首先我们需要声明一个状态监听接口对象,并添加监听方法用来Acitivity中的控件来调用。

    /**
     * @ClassName: OnSwitchStateUpdateListener
     * @Description:添加事件状态监听接口对象
     * @author: iamxiarui@foxmail.com
     * @date: 2016年5月5日 下午9:33:35
     */
     public interface OnSwitchStateUpdateListener {
         // 状态回调, 把当前状态传出去
         void onStateUpdate(boolean state);
     }

     /**
     * @Title: setOnSwitchStateUpdateListener
     * @Description:状态监听方法
     * @return: void
     */
     public void setOnSwitchStateUpdateListener(OnSwitchStateUpdateListener 
                                                    onSwitchStateUpdateListener) {
         this.onSwitchStateUpdateListener = onSwitchStateUpdateListener;
     }

然后需要在合适的位置来处理监听的相关逻辑,在这个控件中,我们最好在触摸事件中的 MotionEvent.ACTION_UP 监听,因为开关的变化一定是触摸点抬起后开始变化,所以我们需要在判断开关位置与改变开关状态之间执行监听方法:

    /**
     * @Title: onTouchEvent
     * @Description:触摸事件
     * @return: void
     */
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         switch (event.getAction()) {
             case MotionEvent.ACTION_UP:
                  ...
                  // 如果开关当前位置大于背景位置的一半 显示关 否则显示开
                  boolean currentState = currentPosition > centerPosition;

                  // 如果当然状态不相同且绑定了监听对象 则执行监听方法
                  if (currentState != isSwitchState && onSwitchStateUpdateListener != null) {
                        onSwitchStateUpdateListener.onStateUpdate(currentState);
                  }
                  // 当前状态置为开关状态
                  isSwitchState = currentState;
                  break;
         }

         // 重新调用onDraw方法,不断重绘界面
         invalidate();
         return true;
     }

这个时候我们再在Activity中给控件绑定监听事件,并处理相关逻辑:

    // 绑定监听事件
     buttonCSView.setOnSwitchStateUpdateListener(new OnSwitchStateUpdateListener() {

         @Override
         public void onStateUpdate(boolean state) {
             if (state) {
                 Toast.makeText(MainActivity.this, "打开", Toast.LENGTH_SHORT).show();
             } else {
                 Toast.makeText(MainActivity.this, "关闭", Toast.LENGTH_SHORT).show();
             }
         }
     });

好了,至此可以说一个自定义控件的基本工作已经完成,现在这个控件已经能够正常使用了。简单回顾一下,还是比较复杂的。因为我们添加复写了很多方法,而一些方法的功能其实就是简单的设置一些图片,并没有一些复杂的功能。我们知道Android中控件的一些属性可以直接在XML文件中定义,那么我们是否可以自定义一些属性并直接在XML文件引用呢?答案是肯定的,也就是接下来要说的自定义属性。

三、自定义属性

在说自定义属性之前,我们先明确几个概念。每次我们创建XML布局文件的时候都会有这样一句代码:

~xmlns:android="http://schemas.android.com/apk/res/android"~

由于是自动创建的,所以我们很少注意到这句话。其实这行代码的意思是指定命名空间,用于在一个XML文档中提供名字唯一的元素和属性。也就是指定了一个命名空间叫做 android ,然后后面跟上空间的地址。这样我们才能够使用一些比如 android : id 这样的属性。

其次自定义属性一般是在 values 文件夹下的 attrs.xml 文件中定义好的。格式如下:

    <declare-styleable name = "名称">

    <attr name = "属性名称" format = "属性类型" />

    </declare-styleable>

其中属性的类型一般分为以下几种:

  • reference:某一资源ID。
  • color:颜色值。
  • boolean:布尔值。
  • dimension:尺寸值。注意,这里如果是dp那就会做像素转换。
  • float:浮点值。
  • integer:整型值。
  • string:字符串
  • fraction:百分数。
  • enum:枚举值。
  • flag:自定义,里面对应了自定义的属性值。
  • reference|color:颜色的资源文件。
  • reference|boolean:布尔值的资源文件

而在本例中,我们只需要设置前景图、背景图和初识开关状态即可,所以我们在文件中这样定义:

    <resources>

         <!-- 自定义属性 -->
         <declare-styleable name="CustomSwitchView">
             <attr name="switch_background" format="reference" />
             <attr name="switch_foreground" format="reference" />
             <attr name="switch_state" format="boolean" />
         </declare-styleable>

    </resources> 

定义好之后,我们就可以在XML文件中增加命名空间与这些属性了,注意命名空间一定要是自己的包名,至于空间名称当然是自己随便写。

    xmlns:customswitch="http://schemas.android.com/apk/res/xr.customswitch.view"

    customswitch:switch_background="@drawable/switch_background"

    customswitch:switch_foreground="@drawable/switch_foreground"

    customswitch:switch_state="true"

但是注意,我们虽然可以增加这些属性,但是现在还不能运行。还记得之前四个构造函数么?其中第二个构造函数中有一个参数就是 AttributeSet ,也就是自定义的属性文件。所以我们还需要重写这个构造函数。

    /**
     * @Description:用于在XML中使用,可以指定自定义属性
     */
     public CustomSwitchView(Context context, AttributeSet attrs) {
         super(context, attrs);
         initView();

         // 设置命名空间
         String namespace = "http://schemas.android.com/apk/res/xr.customswitch.view";

         // 通过命名空间 和 属性名称 找到对应的资源对象
         int switchBackgroundResource = 
                           attrs.getAttributeResourceValue(namespace, "switch_background", -1);
         int switchForegroundResource = 
                           attrs.getAttributeResourceValue(namespace, "switch_foreground", -1);
         isSwitchState = attrs.getAttributeBooleanValue(namespace, "switch_state", false);

         // 将资源对象设置到对应位置
         setBackgroundPic(switchBackgroundResource);
         setForegroundPic(switchForegroundResource);
     }

这个时候,当自定义控件被创建的时候会自动调用这个构造函数,而在布局文件中设置的属性就能够正常使用了。

四、总结

好了,通过这么长篇幅的讲解,完全自定义控件应该已经全部说明白了。现在来总结一下细节上的注意事项吧。

  • 新建的自定义控件类一定要继承View。
  • 必须实现新建控件类的几个构造函数。
  • 新建类最好放在指定包下,比如view包。
  • XML文件一定要写出自定义控件的全路径。
  • 添加事件监听器后要在合适的位置执行状态监听。
  • 自定义属性后需要复写构造方法中带有自定义属性参数的方法。
  • 学的时候很多细节不知道归纳,写出来才能让自己印象深刻。

另外作者还是在校学生一枚,水平有限,只是想把自己所学用文字分享出来,如有错误或者不同意见请多加指教,谢谢。

项目源码:IamXiaRui/Android_Demo_View - Github - 博客 - 简书 - 知乎专栏

2
jixiaohua · #4 ·

#3楼 @iamxiarui diycode也是md的噢

491

#4楼 @jixiaohua 恩恩 已经修改了

96
windcake · #6 ·

很详细,大赞。

30
d_clock · #7 ·

@iamxiarui 看完了你文章了喔,挺清晰的,先手动点赞。另外提个小小的建议,自定义控件的话,最好能给个最终的gif动画效果图展示给读者看。期待你下一篇优秀作品!

1651

正好最近在研究自定义View,略有心得,就来分享一下。自定义View的概念很简单,但实际操作中你会发现你需要了解的东西多的超乎你的想象,不是仅仅去看几篇文章就可以学会的,需要你在了解View基本绘制流程的情况下,不断的去进行实战,以学习更多的内容。

View的基本绘制流程

  • Constructors 进行一些参数的初始化,比如自定义属性
  • onMeasure 测量View及其子View的宽高属性,这里是属性,而不仅仅是宽高的值
  • onLayout 确定View及其子View的布局位置,也就是View及其子View在父容器中的坐标位置
  • onSizeChanged View的大小发生改变时,将调用此函数,一个View的大小在绘制过程中可能发生改变,比如父View
  • onDraw View的内容绘制部分,系统会提供给我们一块画布

在了解上述流程之后,可以去学习一些Api,这些Api将在onDraw中对View进行绘制。

Canvas常用方法

  • 绘制图形(点、线、矩形、椭圆、圆等)
  • 绘制文本(文本的居中问题,需要Paint知识)
  • 画布的基本变化(平移、缩放、旋转、倾斜)
  • 画布的裁剪
  • 画布的保存
  • ...

Paint常用方法

  • 颜色
  • 类型(填充、描边)
  • 字体大小
  • 宽度
  • 对齐方式
  • 文字位置属性测量
  • 文字宽度测量
  • 笔锋
  • ...

Path常用方法

  • 添加路径
  • 移动起点
  • 贝塞尔(二阶、三阶)
  • 逻辑运算
  • 重置路径
  • PathEffect
  • Matrix
  • PathMeasure
  • ...

学习到此,你已经可以绘制出,丰富多彩的自定义View了。但是你可能会发现他们都是静止的,并无法进行移动,所以接下来就需要对animator进行学习,主要学习的方面就是属性动画。

动画

  • ObjectAnimator
  • ValueAnimator
  • AnimatorSet
  • 差值器
  • 估值器

学习完动画,你的自定义View已经可以完成各种特效了。但是你现在可能会想,我定义的一直都是View,那么如何自定义ViewGroup呢?那么就需要对View的源码进行学习。

View源码分析

  • View的测量流程
  • View的布局流程
  • View的绘制流程

现在你可能还会想,像ScrollView、Viewpager滑动的View是如何实现的呢?那么你就需要对View的事件分发以及滑动进行学习了。

View事件分发

  • dispatchTouchEvent
  • onInterceptTouchEvent
  • onTouchEvent
  • onTouchListener
  • onClick
  • onLongClick
  • 滑动冲突

View的滑动

  • Scroller
  • invalidate的传递
  • requestLayout的传递

到这里你已经对自定义View进行了深入的学习了,但是现在还有一些东西需要去掌握,那就是如何改造系统现有的View,来更简单的完成自定义View。

经常改造的View有

  • ListView
  • RecyclerView
  • Button
  • ImageView
  • ...

至此,你已经掌握了应用层的自定义View的知识,如果你还有兴趣可以继续深入,比如openGl、WMS。以下是我的自定义View系列文章。


Android自定义View系列


文章目前写了12篇,刚刚写到了我前面说到的事件分发和滑动部分,后面还会继续更新。如果在阅读的过程发现问题,您可以通过issue、评论、邮件等方式联系我。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册