您的位置:首页 > 移动开发 > Android开发

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

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

视频

尚硅谷事件机制以及自定义控件的绘制基础 (最需要下载的:15天安卓视频->视频->06_事件机制.zip以及07_四大应用组件之Service)

尚硅谷Android自定义控件视频(实战),记得下载上面的链接视频,因为其涉及了基础(如果是小白)

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

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

博客

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

自定义控件的准备

Android中的dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent() - 张兴业的博客 - 博客频道 - CSDN.NET

Android_CustomSwitch at master · IamXiaRui/Android_Demo_View

View 的事件分发机制 | 一个写代码的地方

触屏操作的理解

最基本的操作类型:

down : 手指按下

move : 手指在屏幕上移动

up : 手指从屏幕上离开

触屏操作的顺序: down->move->move->…->up

对屏幕的任何一个操作, 系统都会创建一个MotionEvent对象来对应这个操作

[b]MotionEvent 相关API[/b]

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轴坐标(相对于屏幕左顶点)

Activity

boolean dispatchTouchEvent(MotionEvent event) : 分发事件

boolean onTouchEvent(MotionEvent event) : 处理事件的回调

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) : 用于创建菜单

ViewGroup

boolean dispatchTouchEvent(MotionEvent ev) : 分发事件

boolean onInterceptTouchEvent(MotionEvent ev) : 拦截事件的回调方法

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

[b]触摸事件的分发与处理[/b]

事件产生的顺序为: down–>move–>move…—>up

事件对象被系统创建后, 首先会调用对应Activity对象的dispatchTouchEvent()进行分发

down在分发给视图对象的过程中要确定消费者(onTouchEvent()返回true),如果都返回false, 那事件的消费者只能是Activity了

后面的move和up事件, 将事件分发给消费者(可能是视图对象,也可能是Activity)处理, 如果视图不消费, 直接交给Activity处理消费

当前事件的消费者只是决定了下一个事件优先交给它处理

每个事件都需要有一个消费者

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

按键操作的理解

[b]操作的基本类型[/b]

down : 手指按下

up : 手指从按键上离开

* 按键操作的顺序: down->down->down->…->up

* 对按键的任何一个操作, 系统都会创建一个KeyEvent对象来对应这个操作

* 按键的长按监听: down之后一定时间还没有up时会触发长按监听回调

[b]按键操作的相关API[/b]

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

用系统控件重新组合

优酷自定义菜单



* 广告条效果

用到的控件:ViewPager

ViewPager详解(一)简单介绍和使用 - 简书

ViewPager详解(二)广告轮播图 - 简书

Android之——史上最简单图片轮播广告效果实现 - 博客频道 - CSDN.NET

自己造轮子–一款实用的Android广告栏实现过程(一) - 简书

最终效果

Android自定义带动画无限自动轮播的Banner控件 - 简书

可以考虑的第三方开源库:

Android-ConvenientBanner: 通用的广告栏控件,让你轻松实现广告头效果。

首页垂直滚动TextView广告效果,使用TextSwicher+animation实现 - 门徒07的博客 - 博客频道 - CSDN.NET



下拉框

更好的实现:

Android之——自定义下拉菜单的实现 - 刘亚壮的专栏 - 博客频道 - CSDN.NET

Android 自定义View修炼-如何打造Android自定义的下拉列表框控件 - Jamy Cai - 博客园



自定义类继承View

一个视图从创建到显示过程中的主要方法

1.构造方法实例化类

2.测量-measure(int,int)–>onMeasure();

如果当前View是一个ViewGroup,还有义务测量孩子

孩子有建议权

3.指定位置-layout()–>onLayout();

指定控件的位置,一般View不用写这个方法,ViewGroup的时候才需要,一般View不需要重写该方法

4.绘制视图–draw()–>onDraw(canvas)

根据上面两个方法参数,进入绘制

自定义开关

参考博客:

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

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



水波纹

参考博客:

1. Android5.0水波纹效果ripple实现 - wansho - 博客园

2. Android 自定义view实现水波纹效果 - 享受技术带来的快乐 - 博客频道 - CSDN.NET

3. Android 水波纹点击效果(Ripple Effect) - wingyip - 博客园

4. Android L中水波纹点击效果的实现 - 任玉刚 - 博客频道 - CSDN.NET



自定义属性

引用博客:

Android自定义View(二、深入解析自定义属性) - openXu的专栏 - 博客频道 - CSDN.NET

Android 自定义VIEW属性用法详解(attrs、TypedArray)_Windows Phone_IThao123

Android:自定义控件 — 自定义属性 枚举值(固定属性值) - 博客频道 - CSDN.NET

Android 中 Bitmap 和 Drawable 相互转换的方法 - 哦? - 博客频道 - CSDN.NET

面试题:Android Bitmap 和 BitmapDrawable的区别

Bitmap继承Parcelable,可见是一个可以跨进程传输的对象

BitmapDrawable继承Drawable,可Drawable只是一个抽象类,可见此类是一个存放数据流的载体

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



自定义类继承ViewGroup

防ViewPager

参考博客:

android 布局之滑动探究 scrollTo 和 scrollBy 方法使用说明 - 未来之路 的专栏 - 博客频道 - CSDN.NET

实现3D翻转效果的仿ViewPager - KevinsCSDN的博客 - 博客频道 - CSDN.NET

让你的动画不再生硬 Android插值器Interpolator使用秘籍 - 旋转 跳跃 然后 团灭 - 博客频道 - CSDN.NET

android动画插值器Interpolator使用demo - 下载频道 - CSDN.NET

插补器Interpolator配图详解 - pengkv的专栏 - 博客频道 - CSDN.NET

Android-通过自定义ViewPager来高仿土巴兔选择装修风格效果(中间放大效果) - 泡在网上的日子

Android 自定义View修炼-自定义HorizontalScrollView视图实现仿ViewPager效果 - Jamy Cai - 博客园

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

联系人列表

参考博客:

Android 联系人列表界面(仿iphone、A~Z字母排列、过滤搜索) - 白雨-博客 - 博客频道 - CSDN.NET

Android自定义View——实现联系人列表字母索引 - 阿钟的博客 - 博客频道 - CSDN.NET

android中getWidth()和getMeasuredWidth()之间的区别 - 豌豆豆 - 博客园

发现一个写法更简洁的实现方式-gjiazhe/WaveSideBar: An Index Side Bar With Wave Effect

更为绚丽的联系人列表-kongnanlive/bubble-scroll: An animating scroll bar



侧滑删除菜单

参考博客:

1. 【android自定义控件】android ListView添加侧滑删除 - ___leng的专栏 - 博客频道 - CSDN.NET

更为优秀的做法:

1. ListView 侧滑菜单的实现 – 大道至简的SwipeMenuLayout - 简书

2. Android 高仿 QQ5.0 侧滑菜单效果 自定义控件来袭 - Hongyang - 博客频道 - CSDN.NET

3. RecyclerView侧滑菜单,滑动删除,长按拖拽,下拉刷新上拉加载 - 严振杰 - 博客频道 - CSDN.NET



[b]联系人+侧滑菜单高度集成[/b]

[【Android】史上最简单,一步集成侧滑(删除)菜单,高仿QQ、IOS。 - zxt0601的博客 - 博客频道 - CSDN.NET](http://blog.csdn.net/zxt0601/article/details/53157090)

![联系人+侧滑菜单高度集成](http://ac-mhke0kuv.clouddn.com/104ab70447af9b78832f.gif)

动画的移动

教你10行代码写侧滑菜单-黑马程序员IT技术论坛 - 黑马程序员快速入学必看论坛

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()偏移量的问题

图解MotionEvent中getRawX、getRawY与getX、getY以及View中的getScrollX、getScrollY - 残阳破晓 - 博客园

图解Android View的scrollTo(),scrollBy(),getScrollX(), getScrollY() - bigconvience的专栏 - 博客频道 - CSDN.NET

Android中View中的scrollTo(),scrollBy(),getScrollX(), getScrollY()详解 - 博客频道 - CSDN.NET

Android getScrollX()详解 - znouy的博客 - 博客频道 - CSDN.NET

个人小理解:

1.getScrollX() 就是当前view的左上角相对于母视图的左上角的X轴偏移量

如果是从左到右移动得到的值是负数,负数代表内容距离左边的偏移量;从右到左移动是正值

比如button里面的内容对于button而言的偏移量

<RelativeLayout
...>
<LinearLayout
...>

<Button
...
android:text="Scroll me"/>

</LinearLayout>
</RelativeLayout>




幽默解析版:比如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)区别

图解Android View的scrollTo(),scrollBy(),getScrollX(), getScrollY() - bigconvience的专栏 - 博客频道 - CSDN.NET

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息