尚硅谷自定义View学习笔记-小白到实战

该笔记适合准备入手Android自定义View学习的小伙伴,对于大神的话,建议去其他地方逛逛哈,在此博主声明一下:我不是尚硅谷的什么托,只是一个菜鸟,现在在补着Java基础,搞了一套尚硅谷的Java视频以及Android视频,所以笔记可能会常出现这些字眼,请言语讽刺我是托的麻烦你闭嘴哈

视频

  • 尚硅谷事件机制以及自定义控件的绘制基础 (最需要下载的:15天安卓视频->视频->06_事件机制.zip以及07_四大应用组件之Service)
  • 尚硅谷Android自定义控件视频(实战),记得下载上面的链接视频,因为其涉及了基础(如果是小白)

  • 视频格式转gif-Video to animated GIF converter

备用下载链接: https://pan.baidu.com/s/1jIytuCY 密码: ggty

博客

  1. youlookwhat/CustomViewStudy: 自定义View从入门到进阶(集合Hongyang大神)).

自定义控件的准备

  1. Android中的dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent() - 张兴业的博客 - 博客频道 - CSDN.NET
  2. Android_CustomSwitch at master · IamXiaRui/Android_Demo_View
  3. View 的事件分发机制 | 一个写代码的地方

触屏操作的理解

  • 最基本的操作类型:
    down : 手指按下
    move : 手指在屏幕上移动
    up : 手指从屏幕上离开
  • 触屏操作的顺序: down->move->move->…->up
  • 对屏幕的任何一个操作, 系统都会创建一个MotionEvent对象来对应这个操作
MotionEvent 相关API
  1. MotionEvent : 触屏事件
    int ACTION_DOWN=0 : 代表down
    Int ACTION_MOVE=2 ; 代表move
    Int ACTION_UP=1 : 代表up
    getAction() : 得到事件类型值
    getX() : 得到事件发生的x轴坐标(相对于当前视图)
    getRawX() :得到事件发生的x轴坐标(相对于屏幕左顶点)
    getY() : 得到事件发生的y轴坐标(相对于当前视图)
    getRawY() :得到事件发生的y轴坐标(相对于屏幕左顶点)
  2. Activity
    boolean dispatchTouchEvent(MotionEvent event) : 分发事件
    boolean onTouchEvent(MotionEvent event) : 处理事件的回调
  3. View
    boolean dispatchTouchEvent(MotionEvent event) : 分发事件
    boolean onTouchEvent(MotionEvent event) : 处理事件的回调方法
    void setOnTouchListener(OnTouchListener l) : 设置事件监听器
    void setOnClickListener(OnClickListener l) : 设置点击监听
    void setOnLongClickListener(OnLongClickListener l) : 设置长按监听
    void setOnCreateContextMenuListener(OnCreateContextMenuListener l) : 用于创建菜单
  4. ViewGroup
    boolean dispatchTouchEvent(MotionEvent ev) : 分发事件
    boolean onInterceptTouchEvent(MotionEvent ev) : 拦截事件的回调方法

注意:其中View跟ViewGroup中都有dispatchTouchEvent方法,但View这个方法更多倾向于分发给自身的onTouchEvent跟setOnTouchListener方法,而ViewGroup中的这个方法是将事件分发给子View

触摸事件的分发与处理
  • 事件产生的顺序为: down–>move–>move…—>up
  • 事件对象被系统创建后, 首先会调用对应Activity对象的dispatchTouchEvent()进行分发
  • down在分发给视图对象的过程中要确定消费者(onTouchEvent()返回true),如果都返回false, 那事件的消费者只能是Activity了
  • 后面的move和up事件, 将事件分发给消费者(可能是视图对象,也可能是Activity)处理, 如果视图不消费, 直接交给Activity处理消费
  • 当前事件的消费者只是决定了下一个事件优先交给它处理
  • 每个事件都需要有一个消费者

优秀博客:Android事件分发机制完全解析,带你从源码的角度彻底理解(上) - 郭霖的专栏 - 博客频道 - CSDN.NET

按键操作的理解

操作的基本类型

down : 手指按下
up : 手指从按键上离开
* 按键操作的顺序: down->down->down->…->up
* 对按键的任何一个操作, 系统都会创建一个KeyEvent对象来对应这个操作
* 按键的长按监听: down之后一定时间还没有up时会触发长按监听回调

按键操作的相关API

KeyEvent
int ACTION_DOWN = 0 : 标识down的常量
int ACTION_UP = 1 : 标识up的常量
int getAction() : 得到事件类型
int getKeyCode() : 得到按键的keycode(唯一标识)
startTracking() : 追踪事件, 用于长按监听 (在keyDown那里设置,event.startTracking()方法,这样onKeyLongPress方法才会有效,但特殊的是back键已经那只了这个方法)
Activity
boolean dispatchKeyEvent(KeyEvent event) : 分发事件
boolean onKeyDown(int keyCode, KeyEvent event) : 按下按键的回调
boolean onKeyUp(int keyCode, KeyEvent event) : 松开按键的回调
boolean onKeyLongPress(int keyCode, KeyEvent event) : 长按按键的回调

自定义控件的基础

先看以前的笔记:尚硅谷15天Android基础(复习笔记) - it菜鸟的飞行梦 - 博客频道 - CSDN.NET

  • 面试题:如何区别View与Activity ?
    1.View是能显示到手机屏幕中UI组件
    2.Activity是四大组件中唯一能与用户进行直接交互的应用组件
    3.Activity只是控制和管理View, 真正显示和处理事件的是View本身来完成

View(及其子类)的生命周期

生命周期:创建对象(Activity的onResume()执行之后才会进入后面的流程)、显示(测量onMeasure、布局onLayout、绘制onDraw)、事件处理、死亡

  • 创建对象

    • 创建方式(2种)
      • new MyView(context)
      • 加载布局文件(必须有自定义View的全类名标签)
    • 流程方法
      • 构造方法
        • Xxx(Context context)
        • Xxx(Context context, AttributeSet set)
    • onFinishInflate()-作用是得到子View对象,只有布局的方式才会调用即在xml创建布局这种
    • onAttachedToWindow()-作用也是得到子View对象,哪种创建对象的方式都会执行这个方法
    • Activity的onResume()执行之后才会进入后面的流程(onFinishInflate()之后执行, onAttachedToWindow()之前执行)
      这里写图片描述
      面试题:为什么在Activity的onCreate()方法里拿不到布局控件的宽高
      因为执行完onResume()这个方法之后Activity界面才会执行后面如测量,布局等的方法
  • 测量

    • 作用:计算并确定视图的大小(width/height)
    • 流程方法
      • measure()
        • 系统在此方法中测量计算出当前视图的宽高
        • 此方法为final类型,不能被重写
      • onMeasure()
        • 当mearure()中计算出的视图的宽高就会调用此方法, 在此方法默认保存的视图宽高(里面它调用的setMeasuredDimension方法就是保存MeaSure测量出的宽高)
        • 重写它, 做我们自己的工作, 比如得到当前视图测量的宽高, 保存我们指定的宽度
  • 布局

    • 作用:确定视图显示的坐标(left, top, right, bottom)
    • 流程方法
      • layout(l, t, r, b)
        • 不会重写此方法, 只会调用视图对象的此方法, 指定其新的显示位置(作用在自己的身上)
      • onLayout()
        • 重写它, 在layout()的过程中, 如果视图的位置/强制重新布局就会调用此方法(作用在儿子身上)
    • 强制重新布局
      • view.requestLayout()
  • 绘制

    • 作用:画出视图的样子
    • 流程方法
      • draw()
        • 绘制视图通用的部分
        • 确定绘制的流程
        • 一般不会重写此方法
      • onDraw()
        • 重写此方法,绘制自己需要的样子
        • 一些具体的View类(TextView/ImageView)都重写了此方法,而布局不会重写这个方法的
    • 强制重绘
      • invalidate():只能在主线程执行
      • postInvalidate():可以在主线程或分线程执行
  • 事件处理

    • 流程方法
      • dispatchTouchEvent()
        • 分发事件
        • 从外向里一层一层分发, 分发到事件发生的最里面的视图对象
      • boolean onInterceptTouchEvent()
        • 拦截请求, 只有return true才拦截成功
        • 如果事件被拦截,事件不会再向内层分发, 交给当前的视图处理(但不一定是它消费)
      • boolean onTouchEvent()
        • 处理事件
        • 消费事件, 条件: return true
      • requestDisallowInterceptTouchEvent(true)
        • 反拦截
        • view.getParent().requestDisallowInterceptTouchEvent(true)-View没有拦截事件的能力,ViewGruop才有拦截事件的能力
    • 事件机制
      • 分发
        • 将TouchEvent对象从Activity对象开始,
          由外向内分发给对应的布局和子View对象
      • 处理
        • 回调OnTouchListener的boolean onTouch()
        • 回调boolean onTouchEvent()
      • 消费
        • 回调方法返回true
      • 拦截
        • onInterceptTouchEvent()执行返回true
        • 如果返回true, TouchEvent就不会再传入子View对象
      • 反拦截
        • view.getParent().requestDisallowInterceptTouchEvent(true)
        • 使父View不能再拦截, 事件就会分发到当前View对象
  • 死亡
    • 什么时候会死亡?
      • 视图对象被移除
      • Activity死亡之前
    • 流程方法
      • onDetachedFromWindow()

animation动画知识

View和ViewGroup的初步认识

  • Android的UI界面都是由View和ViewGroup及其派生类组合而成的。
    • 其中,View是所有UI组件的基类,而 ViewGroup是容纳这些组件的容器,其本身也是从View派生出来的.
    • View对象是Android平台中用户界面体现的基础单位。
    • View类是它称为“widgets(工具)”的子类的基础,它们提供了诸如文本输入框和按钮之类的UI对象的完整实现。
    • ViewGroup类同样为其被称为“Layouts(布局)”的子类奠定了基础,它们提供了象流式布局、表格布局以及相对布局之类的布局架构。
    • 一般来说,开发Android应用程序的UI界面都不会直接使用View和ViewGroup,而是使用这两大基类的派生类。
      • View派生出的直接子类有:AnalogClock,ImageView,KeyboardView, ProgressBar,SurfaceView, TextView,ViewGroup,ViewStub
      • View派生出的间接子类有:AbsListView,AbsSeekBar, AbsSpinner, AbsoluteLayout, AdapterView,AdapterViewAnimator, AdapterViewFlipper, AppWidgetHostView, AutoCompleteTextView,Button,CalendarView, CheckBox, CheckedTextView, Chronometer, CompoundButton
      • ViewGroup派生出的直接子类有AbsoluteLayout,AdapterView,FragmentBreadCrumbs,FrameLayout, LinearLayout,RelativeLayout,SlidingDrawer
      • ViewGroup派生出的间接子类有:AbsListView,AbsSpinner, AdapterViewAnimator, AdapterViewFlipper, AppWidgetHostView, CalendarView, DatePicker, DialerFilter, ExpandableListView, Gallery, GestureOverlayView,GridView,HorizontalScrollView, ImageSwitcher,ListView

这里特别指出,ImageView是布局具有图片效果的UI常用的类,SurfaceView是用来进行游戏开发的与一般View相比较为特殊的非常重要的类,而AbsoluteLayout、 FrameLayout,LinearLayout, RelativeLayout这几个ViewGroup的直接子类是Android UI布局中最基本的布局元素。

动画的分类

  • View Animation(补间动画):
    基于View的渐变动画,她只改变了View的绘制效果,而实际属性值未变。比如动画移动一个按钮位置,但按钮点击的实际位置仍未改变。在代码中定义动画,可以参考AnimationSet类和Animation的子类;而如果使用XML,可以在res/anim/文件夹中定义XML文件。

  • Drawable Animation(帧动画):
    加载一系列Drawable资源来创建动画,这种传统动画某种程度上就是创建不同图片序列,顺序播放,就像电影胶片。在代码中定义动画帧,使用AnimationDrawable类;XML文件能更简单的组成动画帧,在res/drawable文件夹,使用采用来定义不同的帧。感觉只能设置的属性是动画间隔时间。

  • Property Animation(属性动画):
    动画的对象除了传统的View对象,还可以是Object对象,动画之后,Object对象的属性值被实实在在的改变了。Property animation能够通过改变View对象的实际属性来实现View动画。任何时候View属性的改变,View能自动调用invalidate()来试试刷新。

参考博客:
1. Android应用开发之所有动画使用详解 - 工匠若水 - 博客频道 - CSDN.NET
2. Android属性动画完全解析(上),初识属性动画的基本用法 - 郭霖的专栏 - 博客频道 - CSDN.NET
3. Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法 - 郭霖的专栏 - 博客频道 - CSDN.NET
4. Android属性动画完全解析(下),Interpolator和ViewPropertyAnimator的用法 - 郭霖的专栏 - 博客频道 - CSDN.NET
5.

自定义控件实战

源码下载:simplebam/custom_view

用系统控件重新组合

  • 优酷自定义菜单

尚硅谷自定义View学习笔记-小白到实战_第1张图片



* 广告条效果

  • 用到的控件:ViewPager
    1. ViewPager详解(一)简单介绍和使用 - 简书
    2. ViewPager详解(二)广告轮播图 - 简书
    3. Android之——史上最简单图片轮播广告效果实现 - 博客频道 - CSDN.NET
    4. 自己造轮子–一款实用的Android广告栏实现过程(一) - 简书
  • 最终效果
    1. Android自定义带动画无限自动轮播的Banner控件 - 简书
  • 可以考虑的第三方开源库:
    1. Android-ConvenientBanner: 通用的广告栏控件,让你轻松实现广告头效果。
    2. 首页垂直滚动TextView广告效果,使用TextSwicher+animation实现 - 门徒07的博客 - 博客频道 - CSDN.NET

尚硅谷自定义View学习笔记-小白到实战_第2张图片

  • 下拉框
    • 更好的实现:
      1. Android之——自定义下拉菜单的实现 - 刘亚壮的专栏 - 博客频道 - CSDN.NET
      2. Android 自定义View修炼-如何打造Android自定义的下拉列表框控件 - Jamy Cai - 博客园

    尚硅谷自定义View学习笔记-小白到实战_第3张图片

自定义类继承View

  • 一个视图从创建到显示过程中的主要方法
    • 1.构造方法实例化类
    • 2.测量-measure(int,int)–>onMeasure();
      如果当前View是一个ViewGroup,还有义务测量孩子
      孩子有建议权
    • 3.指定位置-layout()–>onLayout();
      指定控件的位置,一般View不用写这个方法,ViewGroup的时候才需要,一般View不需要重写该方法
    • 4.绘制视图–draw()–>onDraw(canvas)
      根据上面两个方法参数,进入绘制
  • 自定义开关

    参考博客:

    1. Android自定义控件系列二:自定义开关按钮(一) - 苦咖啡的自留地 - 博客频道 - CSDN.NET
    2. Android自定义控件系列三:自定义开关按钮(二) - 苦咖啡的自留地 - 博客频道 - CSDN.NET


尚硅谷自定义View学习笔记-小白到实战_第4张图片
  • 水波纹

    参考博客:
    1. Android5.0水波纹效果ripple实现 - wansho - 博客园
    2. Android 自定义view实现水波纹效果 - 享受技术带来的快乐 - 博客频道 - CSDN.NET
    3. Android 水波纹点击效果(Ripple Effect) - wingyip - 博客园
    4. Android L中水波纹点击效果的实现 - 任玉刚 - 博客频道 - CSDN.NET


尚硅谷自定义View学习笔记-小白到实战_第5张图片
  • 自定义属性

    引用博客:

    1. Android自定义View(二、深入解析自定义属性) - openXu的专栏 - 博客频道 - CSDN.NET
    2. Android 自定义VIEW属性用法详解(attrs、TypedArray)_Windows Phone_IThao123
    3. Android:自定义控件 — 自定义属性 枚举值(固定属性值) - 博客频道 - CSDN.NET
    4. Android 中 Bitmap 和 Drawable 相互转换的方法 - 哦? - 博客频道 - CSDN.NET
  • 面试题:Android Bitmap 和 BitmapDrawable的区别

    1. Bitmap继承Parcelable,可见是一个可以跨进程传输的对象
      BitmapDrawable继承Drawable,可Drawable只是一个抽象类,可见此类是一个存放数据流的载体
    2. 使用情况:如果想绑定imageView之类的控件,两者都可以用,而想要将图片数据转换成其它对象,Bitmap功能更强大,而BitmapDrawable只是一个流的载体,所以一般获取src资源文件的时候用得多,而想要把资源图片截入到Bitmap需要转换后才可得到Bitmap对象。两者之间有微妙的联系,又有微妙的区别,请看情况而定

尚硅谷自定义View学习笔记-小白到实战_第6张图片


自定义类继承ViewGroup

  • 防ViewPager

    参考博客:

    1. android 布局之滑动探究 scrollTo 和 scrollBy 方法使用说明 - 未来之路 的专栏 - 博客频道 - CSDN.NET
    2. 实现3D翻转效果的仿ViewPager - KevinsCSDN的博客 - 博客频道 - CSDN.NET
    3. 让你的动画不再生硬 Android插值器Interpolator使用秘籍 - 旋转 跳跃 然后 团灭 - 博客频道 - CSDN.NET
    4. android动画插值器Interpolator使用demo - 下载频道 - CSDN.NET
    5. 插补器Interpolator配图详解 - pengkv的专栏 - 博客频道 - CSDN.NET
    6. Android-通过自定义ViewPager来高仿土巴兔选择装修风格效果(中间放大效果) - 泡在网上的日子
    7. Android 自定义View修炼-自定义HorizontalScrollView视图实现仿ViewPager效果 - Jamy Cai - 博客园
    8. Android开发-自定义View-AndroidStudio(十三)仿ViewPager(3) - iwanghang(一个播音与主持艺术专业、干过网游打金工作室,做过海鲜小吃排挡的新手程序员) - 博客频道 - CSDN.NET


关于自定义ViewGroup的问题
1. Android自定义View(三、深入解析控件测量onMeasure) - openXu的专栏 - 博客频道 - CSDN.NET
2.自定义View 1:关于View,ViewGroup的测量和绘制流程 - Android开发社区 | CTOLib码库
3.Activtiy完全解析(三、View的显示过程measure、layout、draw) - openXu的专栏 - 博客频道 - CSDN.NET

  • 联系人列表

    参考博客:

    1. Android 联系人列表界面(仿iphone、A~Z字母排列、过滤搜索) - 白雨-博客 - 博客频道 - CSDN.NET
    2. Android自定义View——实现联系人列表字母索引 - 阿钟的博客 - 博客频道 - CSDN.NET
    3. android中getWidth()和getMeasuredWidth()之间的区别 - 豌豆豆 - 博客园
    4. 发现一个写法更简洁的实现方式-gjiazhe/WaveSideBar: An Index Side Bar With Wave Effect
    5. 更为绚丽的联系人列表-kongnanlive/bubble-scroll: An animating scroll bar


尚硅谷自定义View学习笔记-小白到实战_第7张图片
  • 侧滑删除菜单

    参考博客:
    1. 【android自定义控件】android ListView添加侧滑删除 - ___leng的专栏 - 博客频道 - CSDN.NET
    更为优秀的做法:
    1. ListView 侧滑菜单的实现 – 大道至简的SwipeMenuLayout - 简书
    2. Android 高仿 QQ5.0 侧滑菜单效果 自定义控件来袭 - Hongyang - 博客频道 - CSDN.NET
    3. RecyclerView侧滑菜单,滑动删除,长按拖拽,下拉刷新上拉加载 - 严振杰 - 博客频道 - CSDN.NET


尚硅谷自定义View学习笔记-小白到实战_第8张图片
联系人+侧滑菜单高度集成
[【Android】史上最简单,一步集成侧滑(删除)菜单,高仿QQ、IOS。 - zxt0601的博客 - 博客频道 - CSDN.NET](http://blog.csdn.net/zxt0601/article/details/53157090) ![联系人+侧滑菜单高度集成](http://ac-mhke0kuv.clouddn.com/104ab70447af9b78832f.gif)

动画的移动

  1. 教你10行代码写侧滑菜单-黑马程序员IT技术论坛 - 黑马程序员快速入学必看论坛
  2. Android 高仿 QQ5.0 侧滑菜单效果 自定义控件来袭 - Hongyang - 博客频道 - CSDN.NET

移动动画的三种方式:

  • 使用scrollTo/scrollBy
    • 用于做View的滑动,它可以比较方便实现滑动的效果,并且不影响内部元素的点击事件;它只能滑动View的内容,并不能滑动View本身。
    • 调用View的scrollTo()和scrollBy()是用于滑动View中的内容,而不是把某个View的位置进行改变。如果想改变莫个View在屏幕中的位置,可以使用如下的方法。
  • 使用动画
    • View动画是对View的影像做操作,它并不能真正改变View的位置参数,包括宽和高。
    • Android3.0以上使用属性动画可以解决,可以改变位置参数。
  • 改变布局参数
    • 适用于对View有交互的View.
    • 使用场景:应用第一次进入时引导动画界面的小红点

坐标问题

  • 以当前控件左上方原点坐标,getX()距离X轴上的距离,getY()距离Y轴上的距离
    MotionEvent.getX();
    MotionEvent.getY();

  • 以屏幕左上方原点坐标,getRawX()距离x轴心上的距离,getRawY()距离Y轴上的距离
    MotionEvent.getRawX();
    MotionEvent.getRawY();

getScrollX(),getScrollY()偏移量的问题

  1. 图解MotionEvent中getRawX、getRawY与getX、getY以及View中的getScrollX、getScrollY - 残阳破晓 - 博客园
  2. 图解Android View的scrollTo(),scrollBy(),getScrollX(), getScrollY() - bigconvience的专栏 - 博客频道 - CSDN.NET
  3. Android中View中的scrollTo(),scrollBy(),getScrollX(), getScrollY()详解 - 博客频道 - CSDN.NET
  4. Android getScrollX()详解 - znouy的博客 - 博客频道 - CSDN.NET

个人小理解:
1.getScrollX() 就是当前view的左上角相对于母视图的左上角的X轴偏移量
如果是从左到右移动得到的值是负数,负数代表内容距离左边的偏移量;从右到左移动是正值
比如button里面的内容对于button而言的偏移量

...>
    ...>

        

尚硅谷自定义View学习笔记-小白到实战_第9张图片
幽默解析版:比如Button里面的内容text移动了,但对于Button的老爸LinearLayout以及爷爷RelativeLayout(一般都是爷爷管爸爸,爸爸管儿子的,所以这里不谈RelativeLayout),老爸LinearLayout从看到儿子Button位置没有改变,就认为他没事(此时Button的getScrollX()的偏移量是0),但是Button看到自己的内容text位置改变了,他认为text有事(此时text的getScrollX()的偏移量不是0了)

整体局部版:把Button当做一个整体,从Button外部看,Button的位置的确没有改变,至于他内部怎么改变(比如他内容text位置改变了),那是他自己的事情,所以Button的getScrollX是0;但仅仅看Button的时候,它内部的text的确位置改变了,那么text的getScrollX不是0(重点理解这句话:getScrollX() 就是当前view的左上角相对于母视图的左上角的X轴偏移量,其实就是一个位移值)
PS:儿子的位置是由父亲作为坐标系确定的

view.scrollTo(x,y)genscrollBy(x,y)区别

  1. 图解Android View的scrollTo(),scrollBy(),getScrollX(), getScrollY() - bigconvience的专栏 - 博客频道 - CSDN.NET
  2. android 布局之滑动探究 scrollTo 和 scrollBy 方法使用说明 - 未来之路 的专栏 - 博客频道 - CSDN.NET
  • view.scrollTo(x,y) 将整个父视图的左上角定为(0,0),再移动这个屏幕的左上角到父视图的点(x,y)处,注意此处的x和y是根据父视图的坐标系来定的。

Scroller

  • scroller.startScroll(int startX, int startY, int dx, int dy)参数说明
    四个参数分别表示起点的坐标和滑动的向量,即从(startX,startY)开始滑 动,横向滑动dx的距离,纵向滑动dy的距离(正值向左滑,负值向右滑),而这里的startX,startY又是参照的父视图左上角为原点坐标的坐标系,滑屏时经常使用getScrollX()和getScrollY()来代表屏幕左边缘和上边缘处于父视图坐标系的具体位置
    公式:
    int dx = 目标- getScrollX()
  • scroller.computeScrollOffset返回的是boolean值,false代表移动完成了
    /**
     * Call this when you want to know the new location.  If it returns true,
     * the animation is not yet finished.
     */ 
    public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }

你可能感兴趣的:(尚硅谷自定义View学习笔记-小白到实战)