Android 自定义View学习(一)——准备

作者: 英勇青铜5 | 来源:发表于2016-08-30 20:56 被阅读6540次

    学习资料:

    感谢以上各位大神前辈们 :)

    除了爱哥的自定义系列博客,再推荐一个非常不错的系列,GcsSloop的自定义系列,可以看完我写的再去看,我写的很基础,适合刚开始学习自定义View的同学来了解基础知识,原理及更深入的知识,可看两位大神的系列 : )


    安利,安利,安利

    扔物线大神的 HenCoder 自定义VIew系列


    整个系列学习,记录的大概过程:

    本篇就是个读书笔记而已


    1.Android控件架构

    Android中的每个控件都会在界面中占据一个矩形区域。控件大致分为ViewViewGroupViewGroup控件作为父类控件可以包含多个View控件。

    View树状图

    通过ViewGroup,整个界面控件形成树形结构,也即是控件树。上层控件负责下层控件的测量和绘制,并传递交互事件。在一个Activity中,findViewById()就是在控件树中以树的深度优先遍历来查找对应的元素。

    在每棵树的顶部,都有一个ViewParent对象,所有的交互管理事件都有这个ViewParent对象调度和分配


    通常情况下,在Activity中使用setContent()方法设置一个布局在调用本方法后,布局内容才会真正显示出来。

    UI界面架构图

    每个Activity都包含有一个Window对象,通常是PhoneWindowPhoneWindow将一个DecorView设置为整个应用的窗口的根ViewDecorView作为窗口界面顶层视图,里面封装了一些窗口操作的通用方法。

    DecorView将内容显示在PhoneWindow上,并通过WindowManagerService来进行接收,并通过Activity对象来回调对应的onClickListener。显示时,将屏幕分成两个部分,TitleViewContentViewContent是一个idcontentFrameLayoutactivity_main.xml就在其中。


    通过以上就可以得到下面的标准视图树:

    标准视图树

    视图树的第二层加载一个LinearLayout作为ViewGroup。这一层布局结构会根据对应的参数设置不同的布局。

    最常用的布局,上面显示TitleBar下面是Content。如果用户通过设置requestWindowFeature(Window.FEATURE_NO_TITLE)来设置全屏显示,视图树就只有Content。这也是为什么requestWindowFeature()要在setContent()之前生效的原因。

    当程序在onCreate()方法中调用了setContentView()方法后,ActivityManagerService会回调onResume()方法,系统会把整个DecorView添加进PhoneWindow中,显示出来后,完成界面的绘制。


    2.坐标体系

    View的位置只要由它的四个顶点来决定。分别对应于View的四个属性:

    • topgetTop()左上角的纵坐标
    • leftgetLeft() 左上角的横坐标
    • rightgetRight() 右下角的横坐标
    • bottomgetBottom() 右下角的纵坐标

    View的这些坐标都是相对于View的父容器来说。

    坐标系

    在Android 3.0 后,View增加了:xytranlastionXtranslationYxyView左上角的坐标,tranlastionXtranslationYView左上角相对于父容器的偏移量。
    换算关系:
    x = left + translationX
    y = top + translationY
    需要注意的是,View在平移过程中,top和left表示的原始左上角的位置信息,其值并不会发生改变,此时发生改变的是xytranlastionXtranslationY

    图颜色配的有点多。 :)


    3.View的测量

    一个View显示在屏幕上需要经历三个流程:测量,布局,绘制

    测量的目的在于告诉系统绘制一个多大的,位置在哪里。这个过程在onMeasure()方法中进行。

    测量主要依赖MeasureSppec类。MeasureSpec是一个32位的int值,高2位为测量的模式,低30位为测量的大小。

    测量的模式共有三种:

    • EXACTLY 精确模式,两种情况
      控件的layout_width,layout_height
    1. 指定数值时,例如layout_width="100dp"
    2. 指定为match_parent
    • AT_MOST 最大值模式
      控件的layout_width,layout_height指定为wrap_content

    控件大小一般会随着子空间的或内容的变化而变化,此时要求控件的尺寸只要不超过父类控件允许的最大尺寸即可

    • UNSPECIFIED 未指明模式
      不指定控件大小,View想多大就多大。

    View类默认的onMeasure()方法只支持EXACTLY模式。自定义View时,需要重写onMeasure()方法后,才可以支持其他的模式。假如想要自定义的控件支持wrap_content,就要在onMeasure()中告诉系统自定义控件wrap_content时的大小。


    3.1 最基础的实现

    用来测试onMeasure()方法。

    public class MeasureView extends View {
        public MeasureView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
    

    在布局中使用:

     <com.szlk.customview.custom.MeasureView
         android:id="@+id/mv_custom_activity"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:background="@color/colorAccent" />
    
    wrap_content效果

    运行之后,wrap_content在此时和match_parent效果是一样的。都是沾满全屏。此时在Acitivity中拿到的MeasureView的大小和match_parent是一样的。


    3.2 修改onMeasure()方法

    重写onMeasure()方法后

    重写onMeasure方法
    public class MeasureView extends View {
        public MeasureView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(measureWidth(widthMeasureSpec),measuredHeight(heightMeasureSpec));
        }
    
        /**
         * 测量宽
         * @param widthMeasureSpec
         */
        private int measureWidth(int widthMeasureSpec) {
            int result ;
            int specMode = MeasureSpec.getMode(widthMeasureSpec);
            int specSize = MeasureSpec.getSize(widthMeasureSpec);
            if (specMode == MeasureSpec.EXACTLY){
                result = specSize;
            }else {
                result = 200;
                if (specMode == MeasureSpec.AT_MOST){
                        result = Math.min(result,specSize);
                }
            }
            return result;
        }
    
        /**
         * 测量高
         * @param heightMeasureSpec
         */
        private int measuredHeight(int heightMeasureSpec) {
            int result ;
            int specMode = MeasureSpec.getMode(heightMeasureSpec);
            int specSize = MeasureSpec.getSize(heightMeasureSpec);
            if (specMode == MeasureSpec.EXACTLY){
                result = specSize;
            }else{
                result = 200;
                if(specMode == MeasureSpec.AT_MOST){
                    result = Math.min(result,specSize);
                }
            }
            return  result;
        }
    }
    

    加入了利用MeasureSpec来判断模式。根据不同模式,进行对宽高赋值。在AT_MOST也就是wrap_content时,默认最大的宽高都是200px


    3.3 涉及的部分源码

    • getMode()
     /**
      * Extracts the mode from the supplied measure specification.
      *
      * @param measureSpec the measure specification to extract the mode from
      * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
      *         {@link android.view.View.MeasureSpec#AT_MOST} or
      *         {@link android.view.View.MeasureSpec#EXACTLY}
      */
     @MeasureSpecMode
     public static int getMode(int measureSpec) {
        //noinspection ResourceType
        return (measureSpec & MODE_MASK);
    }
    

    @return可知,返回结果就是MeasureSpec的三种模式


    • getSize()
     /**
      * Extracts the size from the supplied measure specification.
      *
      * @param measureSpec the measure specification to extract the size from
      * @return the size in pixels defined in the supplied measure specification
      */
      public static int getSize(int measureSpec) {
         return (measureSpec & ~MODE_MASK);
      }
    

    返回的是在布局文件中声明的值,结果是px


    • onMeasure()
        /**
         * <p>
         * Measure the view and its content to determine the measured width and the
         * measured height. This method is invoked by {@link #measure(int, int)} and
         * should be overridden by subclasses to provide accurate and efficient
         * measurement of their contents.
         * </p>
         *
         * <p>
         * <strong>CONTRACT:</strong> When overriding this method, you
         * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
         * measured width and height of this view. Failure to do so will trigger an
         * <code>IllegalStateException</code>, thrown by
         * {@link #measure(int, int)}. Calling the superclass'
         * {@link #onMeasure(int, int)} is a valid use.
         * </p>
         *
         * <p>
         * The base class implementation of measure defaults to the background size,
         * unless a larger size is allowed by the MeasureSpec. Subclasses should
         * override {@link #onMeasure(int, int)} to provide better measurements of
         * their content.
         * </p>
         *
         * <p>
         * If this method is overridden, it is the subclass's responsibility to make
         * sure the measured height and width are at least the view's minimum height
         * and width ({@link #getSuggestedMinimumHeight()} and
         * {@link #getSuggestedMinimumWidth()}).
         * </p>
         *
         * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
         *                         The requirements are encoded with
         *                         {@link android.view.View.MeasureSpec}.
         * @param heightMeasureSpec vertical space requirements as imposed by the parent.
         *                         The requirements are encoded with
         *                         {@link android.view.View.MeasureSpec}.
         *
         * @see #getMeasuredWidth()
         * @see #getMeasuredHeight()
         * @see #setMeasuredDimension(int, int)
         * @see #getSuggestedMinimumHeight()
         * @see #getSuggestedMinimumWidth()
         * @see android.view.View.MeasureSpec#getMode(int)
         * @see android.view.View.MeasureSpec#getSize(int)
         */
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }
    

    ViewonMeasure()方法源码中,调用了setMeasuredDimension()方法来确定View的宽和高

    View的源码23000行。 : )


    4.最后

    写的都比较表面,目前深入不了,基本就是看Android 群英传第三章的读写笔记。也不晓得能不能对学习自定义View有些帮助。后面学习,遇到一些知识点也会再补充。

    下一篇学习了解一下CanvasPaint都是干嘛的。

    相关文章

      网友评论

      • System_O:谢谢大佬,写的条条有理,十分清晰,学习了。
      • taey:兄弟,Canvas 方法 以及属性学习 这一章的连接放错了吧,和第四章一样的
        英勇青铜5:多谢指出
      • junl_yaun:`MeasureSpec是一个32位的int值,高2位为测量的模式,低30位为测量的大小。`大佬,这个怎么理解?
        英勇青铜5:个人理解:java int是32位的,前两位的值代表测量模式,剩下的30位则为实际的具体测量值。
        我这里也是人云亦云的结论,我也尝试去看了MeasureSpec的代码,里面的位运算操作看的懵懵懂懂的,你也可以去看看,多少对理解有好处。最近一直不再写新的博客原因也是这样,很多以前写的东西,并没有在实际开发中用过,很多理解都有误,就想着以后再写东西,有一定程度的理解再发。
      • junl_yaun:全屏设置requestWindowFeature(Windows.FEATURE_NO_TITLE) 应该是Window.FEATURE_NO_TITLE
        英勇青铜5:@junl_yaun 多谢指出问题
      • ElyarAnwar:非常不错,青铜6的我也能看懂
        英勇青铜5: @ElyarAnwar 😀😀😀
      • 郑捡书:楼主,MeasureSpec.getSize()获取到是px,但我们在xml中一般都是设置dp,是否有冲突?还是我们需要进行多一步px和dp的转换操作?
        英勇青铜5: @郑捡书 奥奥,你问的这里应该是不需要转换的。但有些地方需要考虑px和dp转换的,不过也就2,3行代码就可以转换了
        郑捡书:@英勇青铜5 知道,是想问这个转化操作是内部帮我们做了吗?
        英勇青铜5: @郑捡书 px和dp可以相互转换的
      • wt龙:我也在看android群英传和艺术开发探索。。作者学习很细心。向你学习。
        英勇青铜5:@Android龙威陶 :smile: :smile: 共勉
      • 囧_囧:脉络清晰 浅显易懂 图文并茂 难得的好文
        英勇青铜5:@英勇青铜5 接到打赏,受宠若惊啊 十分感谢
        英勇青铜5:@囧_囧 :smile: 多谢鼓励。这些都是记录我的学习过程,我就怕自己回头复习知识点,再看时,自己都看不懂了。所以写的过程就理了理思路后写的。以前用笔记录知识点在本子上时,自己把自己坑过
      • dd9672cdcbb0:写的不错,虽然我感觉有点像 《群英传》的软文 :smile: 但恭喜你已经让我对这本书产生兴趣喽
        shanshiping:@英勇青铜5 你写的比群英传好:+1:
        英勇青铜5:@阿斯顿2 我找徐医生要钱去。那再推荐另一本也是非常非常有名的,Android 开发艺术探索。两者真比起来,我更推荐Android开发艺术。
      • dodo_lihao:写得很好
        英勇青铜5:@dodo_lihao 多谢鼓励 共勉
      • 捡淑:马克
      • 3ca6f6426894:Android群英传可以讲的这么细?还是作者加入了自己的理解,我感觉这篇文章脉络非常清晰,感谢分享
        3ca6f6426894: @英勇青铜5 你现在能独立开发项目了吗?求带😂
        3ca6f6426894: @英勇青铜5 你可以独立开发项目了吗 求带😂
        英勇青铜5:@濯伊德 基本都是群英传的知识点,我个人的理解很少。坐标系那里是开发艺术探索书上的。开发艺术探索的知识点特别多,但感觉我目前的水平,先了解view的知识后,再学习开发艺术探索书上的知识点,效果会比较好。

      本文标题:Android 自定义View学习(一)——准备

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