Android自定义View 学习整理

Paint类:画笔

Canvas类:画布,画出成品;与屏幕显示不是一个概念,相当于一个透明图层

ARGB

不在onDraw()中创建变量:因为当需要重绘时就会调用onDraw(),创建的变量会一直被重复创建,引起频繁GC,引起卡顿。

Path类

Region类:区域相交

 

视图动画:补间动画(scale、alpha、translate、rotate)、AnimationSet

                  帧动画(set和get取资源的方法要对应)AnimationDrawable;

                  加载xml:AnimationUtils.loadAnimation()

属性动画:ValueAnimator、ObjectAnimator(继承自ValueAnimator);AnimatorSet

                  加载xml:AnimatorInflater.loadAnimator()

重复次数为INFINITE(无限循环)的动画,当activity结束的时候,必须调用cancel()函数取消动画,否则动画将无限循环,从而导致View无法释放,进一步导致整个Activity无法释放,最终引起内存泄漏。

TimeInterpolator、TypeEvaluator

Interpolator返回的小数值表示的是当前动画的数值进度,Evaluator把插值器返回的小数进度转换成当前数值进度所对应的的值。既可以通过重写插值器改变数值进度来改变数值位置,也可以通过改变估值器中的数值进度所对应的具体数值来改变数值位置。

ValueAnimator流程:

ObjectAnimator流程:ObjectAnimator指定操作的控件对象,在动画开始后,先到控件类中去寻找设置属性所对应的的set函数,然后把动画中间值作为参数传给这个set函数并执行它。

当且仅当我们只给动画设置一个值时,程序才会调用属性对应的get函数来得到动画的初始值。不存在get函数时,则会取参数类型的默认值作为初始值,无法取得默认值时,程序崩溃。

AnimatorSet.Builder类

AnimatorSet的监听函数只是用来监听AnimatorSet的状态的,与其中的动画无关;AnimatorSet中并没有设置循环的函数(AnimationSet中有),所以动画执行一次就结束了,永远无法执行到onAnimationRepeat()函数中。

animatorSet.setStartDelay()的延时=本身延时+第一个动画的延时。

Math类

不使用AnimatorSet,利用关键帧Keyframe,也可以实现多动画同时播放。

PropertyValuesHolder

ViewPropertyAnimator:直接给View设置动画属性---view.animate().XXXX

性能考量:ViewPropertyAnimator并没有像ObjectAnimator一样使用反射或者JNI技术,而ViewPropertyAnimator会根据预设的每一个动画帧计算出对应的所有属性值,并设置给控件,然后调用一次invalidate()函数进行重绘,从而解决了在使用ObjectAnimator时每个属性单独计算、单独重绘的问题。所以ViewPropertyAnimator相对于ObjectAnimator和组合动画,性能有所提升。

android:animateLayoutChanges="true/false"   ViewGroup动态添加/删除item动画

LayoutTransition  ViewGroup内item自定义动画

PathMeasure类

getSegment(float startD,float stopD,Path dst,boolean startWithMoveTo)函数:startWithMoveTo的意思就是在添加新的路径前,是否调用Path.MoveTo()函数将路径的起始点改为新添加路径(dst)的起始点。如果设置为true,就会将路径起始点移动到新添加路径(dst)的起始点,就可以保持当前新添加路径(dst)的形状;而如果设置为false,则不会调用Path.MoveTo()函数,会将新添加路径(dst)的起始点改为原来路径的终点,从而保持连续性。新添加路径(dst)出起始点位置被更改以外,其它路径是不会被更改的。

getPosTan(float distance,float[] pos,float[] tan)函数:distance距离Path起始点的长度,pos切点坐标值,tan切点正切值。该函数将把路径上对应的值赋给pos、tan。

getMatrix()函数:同getPosTan(float distance,Matrix matrix,int flag),只不过把数据按flag封装成matrix。

moveTo()可理解成笔尖。

SVG动画:SVG图像+动画,通过标签将二者联系起来,然后使用AnimatedVectorDrawableCompat.create()加载,最后控件通过setDrawable的方式使用动画。

GPU:图形处理器,绘制步骤:1让View层次结构失效,2记录、更新显示列表,3绘制显示列表

            较CPU绘制,多了第二个步骤,在第一步View层次结构失效后,并不是直接开始逐层绘制的,而是首先把这些View的绘制函数作为绘制指令记录在一个显示列表中,然后再读取显示列表中的绘制指令,调用OpenGL的相关函数完成实际绘制。

            优点:硬件加速提高了android系统显示和刷新的速度

            缺点:OpenGL兼容性问题、内存消耗问题、电量消耗问题

贝塞尔曲线

标签设置多个图层叠加

setShadowLayer()添加阴影效果:使用高斯模糊算法------对于正在处理的每一个像素,取周围若干个像素的RGB值并且平均,这个平均值就是模糊处理过的像素。如果对图片中的所有像素都这么处理,那么处理完成的图片就会变得模糊。其中,所取周围像素的半径就是模糊的半径。所以,模糊半径越大,所得平均像素与原始像素相差就越大,也就越模糊。其中,文字和绘制的图形的阴影是使用自定义的阴影画笔颜色来绘制的,而图片的阴影则是直接产生一张相同的图片,仅对阴影图片的边缘进行模糊。

setMaskFilter()添加发光效果

public Bitmap extractAlpha()方法:根据原图像新建一幅仅具有Alpha值的空白图像,而且这幅图像的颜色是在使用canvas.drawBitmap()函数绘制时由传入的画笔颜色指定的。以此用来给图片添加纯色阴影。

paint.setShader():“印章工具”-----------BitmapShader(Bitmap bitmap,TileMode tileX,TileMode tileY),后面两个参数是指图片小于指定区域时在X、Y轴上的填充模式。总是先填充Y轴,再填充X轴。Shader总是从控件的左上角开始的,无论利用绘图函数绘制多大的图像,在哪里绘制,都与Shader无关。

LinearGradient:线性渐变 

RadialGradient:放射渐变 

混合模式:将两张图片无缝结合

             PorterDuffXfermode:18种模式

onDraw():用于绘制自身

dispatchDraw():用于绘制子视图

无论是View还是ViewGroup,对这两个函数的调用顺序都是onDraw()-->dispatchDraw();但在ViewGroup中,当它有背景的时候就会调用onDraw(),否则就会跳过onDraw(),直接调用dispatchDraw()。所以,如果要在ViewGroup中绘图,往往会重写dispatchDraw()。

如果用Bitmap构造了一个Canvas,那么在这个Canvas上绘制的图像也都会保存在这个Bitmap上,而不会画在View上,如果想画在View上,就必须使用onDraw中传入的Canvas画一遍Bitmap。

图层与画布:Canvas的save()和restore()函数,除这两个函数之外,还有其它一些函数用来保存和恢复画布的状态。

          saveLayer()  [saveLayerAlpha(),多了一个透明度]  函数会创建一块全新的透明画布,大小与指定保存的区域大小一致,其后的绘图操作都放在这块画布上进行。在绘制结束后,会直接覆盖在原始画布上显示。

图层:每次调用canvas.drawXXX系列函数,都会生成一个透明图层专门来绘制这个图形。绘制完成后,就覆盖在画布上。

画布:每块画布都是一个Bitmap,所有的图像都是画在这个Bitmap上的。画布有两种:一种是View的原始画布,是通过onDraw函数传入的,参数中的canvas对应的是View的原始画布,控件的背景就是画在这块画布上的;另一种是新建画布,通过saveLayer(),new Canvas(Bitmap bitmap)等函数人为的新建一块画布,所有的draw函数都是在新建画布上进行的,只有调用restore(),restoreToCount()函数之后,才会返回到原始画布上进行绘制。

Canvas:画布的表现形式。

调用restore之后,restore方法前调用的rotate/translate/scale方法全部就还原了,画布的坐标系统恢复到save方法之前,但是这里要注意的是,restore方法的调用只影响restore之后绘制的内容,对restore之前已经绘制到屏幕上的图形不会产生任何影响。save()保存的就是Canvas中坐标轴的状态。

restore与restoreToCount的关系的关系:两个函数针对的是同一个栈,所以完全可以通用。不同的是,restore函数默认将栈顶内容退出还原画布;而restoreToCount则一直退栈,直到把指定索引的画布信息退出来,之后的栈最上层的画布信息将作为最新的画布。

shap标签对应的Java类是GradientDrawable,而不是ShapeDrawable。

当使用setImageDrawable(drawable)函数来设置ImageView数据源时,自定义Drawable的位置和大小与ImageView的scaleType有关。当使用setBackgroundDrawable(drawable)函数来设置View背景时,自定义Drawable的宽、高与控件大小一致,控件的宽、高则选取本身宽、高与自定义Drawable宽、高中的最大值。setBackgroundDrawable(drawable)所设置的背景会忽略源图像中的padding属性。

Canvas中保存着一个Bitmap对象,调用Canvas的各种绘制函数,最终是绘制到其中的Bitmap上的。

内存中存储的Bitmap对象与文件中存储的Bitmap图片不是一个概念:文件中存储的Bitmap图片是经过压缩算法得到的,而内存中存储的Bitmap对象是通过BitmapFactory或者Bitmap的create方法创建的,它保存在内存中,而且具有明确的宽高。所以,内存中存储的一个Bitmap对象,它所占的内存大小=Bitmap的宽*Bitmap的高*每像素所占内存大小(字节数)。

/data/data目录下存放的是系统中所有app的数据文件,以apk包名划分,其中会有提交的数据库及xml数据文件。BitmapFactory.decodeFileDescriptor()比BitmapFactory.decodeFile()更节省内存:前者调用Native层的函数,直接由Native层解析出Bitmap返回。

inJustDecodeBound获取图片信息:只解析图片信息,不加载图片不分配内存,解析到图片宽高、类型;

inSampleSize采样率:压缩图片:指每隔多少个样本采样一次作为结果,如将这个字段设置为4,意思是从原本图片的4个像素中取一个像素作为结果返回,其余的都被丢弃。采样率的值,具体计算需要拿原始宽高与目标宽高想比,然后取宽高比中的最小值,这个最小值就是采样率。

加载分辨率文件夹里面的图片时,如果所有文件夹都不符合屏幕的分辨率,android系统会在加载时动态缩放这张图片所占的像素数,缩放的比例=屏幕分辨率/文件所在文件夹的分辨率。这时候Bitmap所占的内存空间就不在是宽*高*字节数了。特别的,如果加载的图片是本地SD卡中的,系统将不进行缩放。inScaled属性:设置在这种情况下是否需要动态缩放。

图片填充算法:

inPreferredConfig设置像素的存储格式:比如从ARGB_8888(默认)设置成RGB_565

通过BitmapFactory加载的Bitmap都是像素不可更改的,只有通过Bitmap中的几个函数创建的Bitmap才是像素可更改的:

copy(BitmapFactory.Config config,boolean isMutable)

createBitmap(int width,int height,Bitmap.Config config)

createBitmap(DisplayMetrics display,int width,int height,Bitmap.Config config)

createScaledBitmap(Bitmap src,int dstWidth,int dstHeight,boolean filter)

对于像素不可更改的图像,是不能做画布的,会报错。

自定义View,onFinishInflate()函数的调用时机是在系统将xml解析出对应的控件实例的时候,这时候,控件已经生成,但还没有被使用。

ImageView默认是不会响应单击事件的,需要显示添加android:clickable="true"属性。

public boolean compress(CompressFormat format,int quality,OutputStream stream):将压缩过的Bitmap写入指定的输出流中。

SurfaceView:使用双缓冲技术(多加一块缓冲画布,当需要执行绘图操作时,先在缓冲画布上绘制,绘制好后直接将缓冲画布上的内容更新到主画布上)、自带画布,支持在子线程中更新画布内容(处理同步问题)

lockCanvas(rect)支持局部更新,在使用前需要用while循环清屏。

SurfaceView中的缓冲画布的数量是动态分布的。如果用户获取画布的频率较慢,那么将会分配两块缓冲画布;否则将会分配3的倍数块画布,具体分配多少块,视实际情况而定。

View和SurfaceView各自的应用场景:

当界面需要被动更新时,用View较好。比如,与手势交互的场景,因为画面的更新是依赖onTouch来完成的,所以可以直接使用invalidate()函数。在这种情况下,这一次Touch和下一次Touch间隔的时间比较长,不会产生影响。

当界面需要主动更新时,用SurfaceView较好。比如一个人在一直跑动,这就需要一个单独的线程不停地重绘人的状态,避免阻塞主线程。

当界面绘制需要频繁刷新,或者刷新时数据处理量比较大时,应该用SurfaceView来实现,比如视频播放及Camera。

当SurfaceView需要使用View的onDraw()函数来重绘控件时,需要在初始化的时候调用setWillNotDraw(false),否则onDraw()函数不会被调用。setWillNotDraw是一种优化策略,他让控件显式地告诉系统,在重绘屏幕时,哪个控件需要重绘,哪个控件不需要重绘,这样就可以大大提高重绘效率。

自定义属性attrs.xml      标签

使用代码获取用户所定义的某个属性的值,主要使用TypedArray类,这个类提供了获取某个属性值的所有方法。在使用完以后,必须调用TypedArray类的recycle()函数来释放资源。

绘制流程

onMeasure(int widthMeasureSpec,int heightMeasureSpec)两个参数是指父类传递过来给当前View的一个建议值,即想把当前View的尺寸设置为宽widthMeasureSpec,高heightMeasureSpec。

测量完成以后,通过setMeasuredDimension();将值设置给系统。

MeasureSpec转换成32位二进制时,前两位代表mode模式,后30位代表size数值。

UNSPECIFIED(未指定):父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小。

EXACTLY(完全):父元素决定子元素的确切大小,子元素将被限定在给定的边界里而忽略它本身的大小。match_parent/具体值

AT_MOST(至多):子元素至多达到指定大小的值。wrap_content

getMeasuredWidth(); getWidth();

getMeasuredWidth();函数在onMeasure()过程结束后就可以获取到宽度值,而getWidth();函数要在layout()过程结束后才能获取到宽度值。getMeasuredWidth();函数中的值是通过setMeasuredDimension();函数来进行设置的,而getWidth();函数中的值是通过layout(left,top,right,bottom)函数来进行设置的。setMeasuredDimension();函数提供的测量结果只是为布局提供建议的,最终的取用与否要看layout()函数。

在所有控件的顶部有一个ViewRoot,它才是所有控件的祖先节点。

ViewGroup中,generateLayoutParams()函数默认只会提取layout_width、layout_height的值,如果自定义ViewGroup有margin值,要重写该函数,并且返回new MarginLayoutParams(getContext(),attrs);才会提取margin值。

GestureDetector手势检测:SimpleOnGestureListener实现OnGestureListener和OnDoubleTapListener的全部函数,按需重写。

Andorid中所有的视图都是通过Window来呈现的,不管是Activity、Dialog还是Toast,他们的视图都是附加在Window上的,而WindowManager则提供了对这些Window的统一管理功能(addView、updateViewLayout、removeView)。Window有三种类型,应用Window、子Window和系统Window。应用Window对应着一个Activity,子Window不能独立存在,他需要附属在特定的父Window中,比如Dialog就是一个子Window。系统Window是需要声明权限才能创建的,比如Toast和系统状态栏都是系统Window。

 

 

 

 

你可能感兴趣的:(Android)