美文网首页
笔记——自定义View(三)

笔记——自定义View(三)

作者: 木溪bo | 来源:发表于2018-12-10 16:57 被阅读3次

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

    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从而影响改变估值器逻辑运算的具体返回值,形成一个动画运动。如:该文插值器与估值器详解 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/cf5092fa269https://blog.csdn.net/lfdfhl/article/details/51347818

    自定义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的大小为wrap_content但它的一个子View大小为match_parent。

    (2) 从根到这个子View的父ViewGroup都是wrap_content,而子View的大小为match_parent。这个极端情况也是不会的,可见情况1的分析.

    (3)从根到这个子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的滚动,而且提供了许多的辅助而已,比如:可以提供偏移量,耗时,当前位置等等信息。

    ---------------------

    原文:https://blog.csdn.net/lfdfhl/article/details/53143114

    代码如下

    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来减少嵌套层析从而优化布局的性能。

    --------------------原文:https://blog.csdn.net/su_1106941640/article/details/53026208

    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()方法

    当一个类没有提供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便是且与的关系,需要同时满足

    相关文章

      网友评论

          本文标题:笔记——自定义View(三)

          本文链接:https://www.haomeiwen.com/subject/shmyfqtx.html