笔记(三)——自定义View

——》个人平时笔记,看到的同学欢迎指正错误,文中多处摘录于各大博主精华、书籍

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测量值的几个Modle.png

当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式并且其大小遵循Layoutparams中的大小。当View的宽/高是match_parent时,如果父容器的模式是精准模式,那么View也是精准模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。当View的宽/高是wrap_content时,不管父容器的模式是精准还是最大化,View的模式总是最大化并且大小不能超过父容器的剩余空间。UNSPECIFIED这个模式主要用于系统内部多次Measure的情形,一般来说,我们不需要关注此模式。

MeausreSpec1.png


MeausreSpec2.png

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需要重写。

MeausreSpec3源码.png


结合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); }

}


MeasureSpec.png

上图红框实际不可能出现原因:

(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/

宽高区别.png

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为正值是反常的不是我们料想的那样,以左上角为圆点“右正左负,上负下正”,这是因为源码中如下

scrollerby

使用scrooler弹性滑动来控制view的移动,实则是view自己让自己移动的。

调用invalidate()刷新界面,从而再次回到computeScroll(),回到在computeScroll()继续处理滑动事件。假如View的滑动已经停止了那就没有必要再次执行invalidate()了。说到底,不是Scroller让View发生了滚动而是View自己在滚动。只不过在这个过程中Scroller在不停地追踪View的滚动,而且提供了许多的辅助而已,比如:可以提供偏移量,耗时,当前位置等等信息。

站在源码的肩膀上全解Scroller工作机制

代码如下

scroll.png
scroller.png

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过程,即两者的赋值时机不同,测量宽/高的赋值时机稍微早一些。


绘制过程.png

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便是且与的关系,需要同时满足

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容