——》个人平时笔记,看到的同学欢迎指正错误,文中多处摘录于各大博主精华、书籍
1、在自定义View中,drawArc()是绘制弧形或者扇形的,drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) ,绘制角度以X轴正方向即正右方为0度位置,顺时针为绘制正角度,逆时针为负;useCenter是否连接圆心,连接为绘画扇形,不连接则绘制弧形。
path.AddCircle(x,y, radius, dir)+canvas.drawPath(path,paint)这种写法,和直接使用canvas.drawCircle(x, y, radius,paint)的效果是一样的,区别只是它的写法更复杂。所以如果只画一个圆,没必要用Path,直接用drawCircle()就行了。drawPath()一般是在绘制组合图形时才会用到的。
2、在自定义view中插值器(Interpolator)和估值器(TypeEvaluator)的关系:
估值器依赖于插值器,一般依赖于系统给的默认插值器,插值器返回的结果值就是回调给估值器中public Object evaluate(float fraction, Object startValue, Object endValue)方法中的fraction系数,插值器动态改变fraction系数从而影响改变估值器运算后的具体结果值,最终通过估值器时时变化的结果值设置属性动画的值,并时时刷新绘制UI控件,形成一个动画效果。如:该文插值器与估值器详解 https://www.jianshu.com/p/2f19fe1e3ca1
插值器影响动画的速度决定值的变化规律(匀速、加速等),即决定的是变化趋势,比如非匀速动画就需要通过插值器来控制动画的播放过程。这个属性可以不指定,默认为@android:anim/accelerate_decelerate_interpolator,即加速减速插值器。
二者关系类似的可以比喻成一个物理位移公式: s=V0t+(at^2)/2,插值器为加速度a,估值器为位移s,插值器只是估值器计算中用到的一个属性值。
3、MeasureSpec封装了父布局ViewGroup传递给子View的布局要求。是要求而并非是强制的,如在子View的onMeasure()中还是可以设置setMeasuredDimension(Width, Height)的;
MeasureSpec通常翻译为”测量规格”,它是一个32位的int数据,其中高2位代表SpecMode即某种测量模式,低30位为SpecSize代表在该模式下的规格大小。
对于顶级View(即DecorView)和普通View来说,MeasureSpec的转换过程略有不同。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定;对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高
链接:https://www.jianshu.com/p/cf5092fa269,自定义View系列教程02--onMeasure源码详尽分析
当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式并且其大小遵循Layoutparams中的大小。当View的宽/高是match_parent时,如果父容器的模式是精准模式,那么View也是精准模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。当View的宽/高是wrap_content时,不管父容器的模式是精准还是最大化,View的模式总是最大化并且大小不能超过父容器的剩余空间。UNSPECIFIED这个模式主要用于系统内部多次Measure的情形,一般来说,我们不需要关注此模式。
ViewGroup的measure()-->onMeasure(),ViewGroup的onMeasure()是抽象方法,但其提供了measureChildren(),这之中会遍历子View然后循环调用measureChild(),这之中会通过getChildMeasureSpec()方法中父ViewGroup的MeasureSpec+子View的LayoutParams一起获取本子View最终生成的MeasureSpec,然后调用子View的child.measure(childWidthMeasureSpec,
childHeightMeasureSpec)到View的onMeasure()-->setMeasureDimension(getDefaultSize(),getDefaultSize())这样一个流程,getDefaultSize()默认返回measureSpec的测量数值,所以继承View进行自定义的wrap_content需要重写。
结合MeausreSpec1.png图发现一个问题:在该图的最后一行,如果子View在XML布局文件中对于大小的设置采用wrap_content,那么不管父ViewGroup的specMode是MeasureSpec.AT_MOST还是MeasureSpec.EXACTLY对于子View而言系统给它设置的specMode都是MeasureSpec.AT_MOST,并且其大小都是parentLeftSize即父ViewGroup目前剩余的可用空间。这时wrap_content就失去了原本的意义,变成了match_parent一样了,所以自定义View在重写onMeasure()的过程中应该手动处理View的宽或高为wrap_content的情况。
这个在《Android开发艺术探索》4.3.1节中完美解释
第一种情况:如果在xml布局中View的宽和高均用wrap_content.那么需要设置View的宽和高为mWidth和mHeight.
第二种情况:如果在xml布局中View的宽或高其中一个为wrap_content,那么就将该值设置为默认的宽或高,另外的一个值采用系统测量的specSize即可,代码中设置如下,其中给mWidth、mHeight在自定义view中设定默认值:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec , heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpceSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int heightSpceSize=MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, mHeight);
}else if(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, heightSpceSize);
}else if(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpceSize, mHeight); }
}
上图红框实际不可能出现原因:
(1) 不可能出现一个这样的View,其根View的大小为wrap_content且它的一个子View大小为match_parent。
(2) 从根View到这个子View的父ViewGroup都是wrap_content,而子View的大小为match_parent。这个极端情况也是不会的,可见情况1的分析.
(3)从根View到这个子View的父ViewGroup都是wrap_content,而子View大小也为wrap_content。这是个正常情况,所以我们用改良后的onMeasure()来专门处理的子View大小为wrap_content的情况。
4、getWidth方法是在layout方法完成后才有的值,所以说在自定义控件的时候在onLayout方法中一般采用getMeasuredWidth来获得控件的宽度,因为getMeasuredWidth在measure后就有了值,而getWidth在layout才有了值。除了onLayout方法中采用getMeasuredWidth方法外,在其他地方一般采用getWidth方法来获取控件的宽度更准确。
Android开发之getMeasuredWidth和getWidth区别从源码分析 https://blog.csdn.net/dmk877/article/details/49734869/
5、在自定义View中加载图片资源Bitmap时:我们可以通过设置绘制区域来控制显示的图片位置以及大小。
如下:由变量w,h来控制绘制结束的矩形dst右下角终点坐标,矩形dst的区域显示图片资源
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mBgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.splash6);
// 指定图片绘制区域,这里设置与图片参数大小一致,绘制完整图片
Rect src =new Rect(0,0,mBgBitmap.getWidth(),mBgBitmap.getHeight());
//绘制区域
Rect dst =new Rect(0,0,w,h);
// 绘制图片
canvas.drawBitmap(mBgBitmap, src, dst,null);
}
6、scrollBy()内部是scrollTo()实现的累加位移,scrollTo()是相对于初始位置做的移动,注意是初始位置。
scrollTo()和scrollBy()时传入的x,y为正值是反常的不是我们料想的那样,以左上角为圆点“右正左负,上负下正”,这是因为源码中如下
使用scrooler弹性滑动来控制view的移动,实则是view自己让自己移动的。
调用invalidate()刷新界面,从而再次回到computeScroll(),回到在computeScroll()继续处理滑动事件。假如View的滑动已经停止了那就没有必要再次执行invalidate()了。说到底,不是Scroller让View发生了滚动而是View自己在滚动。只不过在这个过程中Scroller在不停地追踪View的滚动,而且提供了许多的辅助而已,比如:可以提供偏移量,耗时,当前位置等等信息。
代码如下
7、官方不推荐通过无参的构造方法生成一个canvas。如果要这么做那就需要调用setBitmap()为其设置一个Bitmap。为什么Canvas非要一个Bitmap对象呢?原因很简单:缺少一个载体,Canvas需要一个Bitmap对象来保存像素,如果画的东西没有地方可以保存,又还有什么意义呢?
8、View绘制分三个步骤,顺序是:onMeasure,onLayout,onDraw。经代码亲测,log输出显示:调用invalidate方法只会执行onDraw方法;调用requestLayout方法只会执行onMeasure方法和onLayout方法,并不会执行onDraw方法。所以当我们进行View更新时,若仅View的显示内容发生改变且新显示内容不影响View的大小、位置,则只需调用invalidate方法;若View宽高、位置发生改变且显示内容不变,只需调用requestLayout方法;若两者均发生改变,则需调用两者,按照View的绘制流程,推荐先调用requestLayout方法再调用invalidate方法。
invalidate和postInvalidate:invalidate方法只能用于UI线程中,在非UI线程中,可直接使用postInvalidate方法,这样就省去使用handler的烦恼。
activity和view都有onSaveInstanceState、onRestoreInstanceState,在activity中异常终止情况下这两个方法都会执行onSaveInstanceState->onDestory->onCreate->onRestoreInstanceState,而在按Home键或者启动新Activity仍然会单独触发onSaveInstanceState。
9、如若非使用Relativelayout,一般自定义组件的时候不会去继承RelativeLayout,因为它会进行两次绘制,影响绘制效率;故在能实现相同功能需求时更多的使用LinearLayout和FrameLayout。
总结:LinearLayout和RelativeLayout的性能差别主要体现在onMeasure方法上,RelativeLayout始终要从竖直和水平两个方向对子View进行测量。而Linearlayout,当我们没有在子View中使用layout_weight属性时,LinearLayout只需对子View进行一次测量,否则也需要对子View进行两次测量以确定最终大小。所以如果可以,我们尽量少用layout_weight属性。在使用这两个布局之前,我们可以先进行衡量,如果需要实现的布局嵌套层次不深或者嵌套层次已经固定了,可以考虑用LinearLayout,相对的,如果某个布局嵌套层次很深,此时应该考虑使用RelativeLayout来减少嵌套层析,从而优化布局的性能。
LinearLayout有两个方向走向,一个是竖直方向,一个是水平方向。在onMeasure方法中会根据横向还是纵向来调用measureVertical()或measureHorizontal()方法。我们都知道在LinearLayout中,我们可以使用layout_weight属性,当子View使用了layout_weight属性时就会调用measureChildBeforeLayout()方法,这个方法的大概步骤是先不限制设置了weight属性的子View的大小,只要容器剩余的空间还足够就不对该子View作处理,但凡事都有个度,所以不可能每个设置了weight属性的子View都无限大吧,所以当所有子View测量完毕后,会对设置了weight属性的子View再调用一次measure方法。
LinearLayout和RelativeLayout绘制过程的对比
10、getMeasuredWidth和getWidth:在View的默认实现中,View的测量宽/高和最终宽/高一般情况下是相等的,只不过测量宽/高形成于View的measure过程,而最终宽/高形成于View的layout过程,即两者的赋值时机不同,测量宽/高的赋值时机稍微早一些。
11、安卓动画:
动画分为View动画(视图动画)和属性动画,它们最大的区别是View动画改变的是视图影像效果而不改变属性值,属性动画通过改变属性值来构成动画效果。在实际开发中,建议采用XML来定义View动画,这是因为XML格式的动画可读性更好;建议采用代码来实现属性动画,这是因为通过代码来实现比较简单。
View动画:View动画改变的是视图影像效果而不改变属性值。例如一个平移动画,TextView坐标(0,0)从左往右平移100px,平移后坐标(100,0),而平移后我们看到TextView在(100,0)点上,实际上它还在(0,0)上,通过响应点击事件我们可以知道只有点击原来的位置(0,0)才能响应事件,而当前看到的TextView点击无响应效果。View动画有TranslateAnimation、ScaleAnimation、RotateAnimation和AlphaAnimation等。
帧动画也是属于View动画,帧动画的使用比较简单,但是比较容易引起OOM,所以在使用帧动画时应尽量避免使用过多尺寸较大的图片。
LayoutAnimation也是一个View动画,给ViewGroup的子元素加上出场效果。
使用View动画有时候会出现动画完成后View无法隐藏的现象,即setVisibility(View.GONE)失效了,这个时候只要调用view.clearAnimation()清除View动画即可解决此问题。
属性动画:与View动画不同,它改变的是对象或者说控件的属性值,平移到哪,当前它的位置实际也就是在哪个点。属性动画可以对任意对象的属性进行动画而不仅仅是View,动画默认时间间隔300ms,默认帧率10ms/帧,动画的每一帧都会回调onAnimationUpdate方法。其可以达到的效果是:在一个时间间隔内完成对象从一个属性值到另一个属性值的改变。只要对象有这个属性,它都能实现动画效果。属性动画有ValueAnimator、ObjectAnimator和AnimatorSet。
属性动画可以对任何对象做动画,甚至还可以没有对象,属性动画(ObjectAnimator)改变对象属性,要求对象内要提供该属性的set和get方法(非ObjectAnimator可以没有)。
例如:TextView tvObjectAn; ObjectAnimator animator = ObjectAnimator.ofFloat(tvObjectAn,"rotation",360); 让一个TextView旋转360度,其中属性"rotation"在TextView 父类View中就有提供setRotation() 与getRotation()方法。其中带get***()、set***()的方法实体类在内部估值器中被处理,实现对动画值的改变。
当一个类没有提供set、get方法时,官方文档上告诉我们有3种解决方法:
1.给你的对象加上get和set方法,如果你有权限的话,该解决方法几乎不能实现,因为你一般都是没有权限;
2.用一个类来包装原始对象,间接为其提供get和set方法,在set方法中自定义实现属性的改变;
3.采用ValueAnimator,监听动画过程得到动画过程变动值,通过这个变动值自己实现属性的改变。
12、在Drawable中StateListDrawable的selector标签,表示Drawable集合。selector中每个item对应着一个具体的Drawable,系统按照从上到下的顺序查找,直至查找到第一条匹配的item。一般来说,默认的item都应该放在selector的最后一条并且不附带任何的状态。当上面没有状态匹配时就会匹配到默认的item了。默认的item不附带状态,所以它可以匹配View的任何状态。
<item android="@drawable="@drawable/icon" android:state_checked="false" android:state_pressed="false"/>
写了多个状态如state_checked和state_pressed便是且与的关系,需要同时满足