• Android开发艺术探索学习笔记(四)


      第四章 View的工作原理

        4.1初识ViewRoot和DecorView

        ViewRoot是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。

        View的三大流程指的是测量(measure),布局(layout),绘制(draw)。

        measure用来测量View的宽和高,layout用来确定View在父容器中的放置位置,draw负责将View绘制在屏幕上。

         measure过程决定了View的宽和高,Measure完成以后,可以通过getMeasuredWidth和 getMeasuredHeight方法来获取View测量后的宽和高;Layout过程决定了View的四个顶点的坐标和实际的View宽高,可通过 getTop、getBottom、getLeft、getRight来获取View的四个顶点的位置,并可通过getWidth和getHeight方 法来获取View的最终宽高。Draw过程决定了View的显示,只有draw方法完成以后View的内容才能呈现在屏幕上。

        DecorView是顶级的View;获取通过setContentView方法所设置的View的方法如下:

        View contentView=findViewById(ViewGroup)findViewById(android.R.id.content).getChildAt(0);

        DecorView其实是一个FrameLayout,View层的事件都先经过DecorView,然后才传递给我们的View。

        4.2理解MeasureSpec

        MeasureSpec(测量规格)在很大程度上决定了一个View的尺寸规格,之所以说是很大程度上是因为这个过程还受父容器的影响,因为父容器影响View的MeasureSpec的创建过程。

        MeasureSpec代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,SpecSize是指在某种测量模式下的规格大小。

        SpecMode有三种分别是:

        (1)UNSPECIFIED

          父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态;

        (2)EXACTLY

          父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式。

        (3)AT_MOST

          父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体体现。它对应于LayoutParams中的wrap_content。

        DecorView的MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定;普通View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定。

        总之,只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以快速的确定出子元素的MeasureSpec了,有了MeasureSpec就可以进一步确定出子元素测量后的大小了。

        4.3View的工作流程

          View的工作流程主要是指measure、layout、draw这三大过程,即测量、布局和绘制,其中measure确定View的测量宽高,layout确定View的最终宽高和四个顶点的位置,而draw则将View绘制到屏幕上。

          4.3.1 measure过程

            measure过程分为两种情况,一种是view的measure过程,一种是ViewGroup的measure过程。

          (1)view的measure过程

          view的measure过程直接由measure方法来完成。

          注意:直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent

          (2)ViewGroup的measure过程

          对于ViewGroup来说,除了完成自己的measure过程以外,还会遍历去调用所有子元素的measure方法,各个子元素在递归去执行这个过程。

          注 意:在某些极端情况下,系统可能需要多次measure才能确定最终的测量宽高,在这种情况下,在onMeasure方法中拿到的测量宽高很可能是不准确 的。一个比较好的习惯是在onLayout方法中去获取View的测量宽高或者最终宽高(原因:在View的默认实现中,View的测量宽高和最终宽高是 相等的,只不过测量宽高形成于View的measure过程,而最终宽高形成于View的layout过程,即两者的赋值时机不同,测量宽高的赋值时机稍 微早一些)

          举例测量宽高不等于最终宽高的情况:

    public void layout(int l,int t,int r, int b){
        surper.layout(l,t,r+100,b+100);
    }

           上述代码会导致在任何情况下View的最终宽高总是比测量宽高大100px!

          4.3.2 layout过程

          Layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有的子元素并调用其layout方法,在layout方法中onLayout方法又会被调用。layout方法确定View本身的位置,而onLayout方法则会确定所有子元素的位置。

          4.3.2 draw过程

           draw过程的作用是将View绘制到屏幕上面。draw过程的步骤:

          (1)绘制背景background.draw(canvas);

          (2)绘制自己(onDraw);

          (3)绘制children(dispathDraw);

          (4)绘制装饰(onDrawScrollBars)。

          View绘制过程的传递是通过dispatchDraw来实现的。

        4.4自定义View

          4.4.1自定义View的分类

          1.继承View重写onDraw方法(需要自己支持wrap_content,并且padding也需要自己处理);

          2.继承ViewGroup派生特殊的Layout(需要合适的处理ViewGroup的测量、布局这两个过程,并同时处理子元素的测量和布局过程);

          3.继承特定的View(比如TextView,不需要自己支持wrap_content和padding等);

          4.继承特定的ViewGroup(比如LinearLayout,不需要自己处理ViewGroup的测量和布局这两个过程)。

          2和4的主要差别在于2更接近系统底层。

          4.4.2自定义View须知

          自定义View时主要有如下几个注意事项:

          (1)让View支持wrap_content;

          (2)如果有必要,让View支持padding;

          (3)尽量不要在View中使用handler,没必要(因为View本身就提供了post系列的方法);

           (4)View中如果有线程或者动画,需要及时停止,参考View#onDetachedFromWindow(当包含此View的 Activity退出或者当前View被remove时,View的onDetachedFromWindow方法会被调用;当包含此View的 Activity启动时,View的onAttachedToWindow方法会被调用);

          (5)View带有滑动嵌套情形时,需要处理好滑动冲突。

          4.4.3自定义View示例

          1.继承View重写onDraw方法

          其中主要是对wrap_content和padding的处理。

    public class CircleView extends View {
        private int mColor = Color.RED;
        private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
        public CircleView(Context context) {
            super(context);
            init();
        }
    
        public CircleView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
        
        public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            TypedArray a=context.obtainStyledAttributes(attrs,R.styleable.CircleView);
            mColor=a.getColor(R.styleable.CircleView_circle_color,Color.RED);
            a.recycle();
            init();
        }
    
        private void init() {
            mPaint.setColor(mColor);
        }
    
        /**
         * 重写onMeasure方法主要是为了解决wrap_content的问题,否则设置wrap_content会无效。
         * @param widthMeasureSpec
         * @param heightMeasureSpec
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
            if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(200, 200);
            } else if (widthSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(200, heightSpecSize);
            } else if (heightSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(widthSpecSize, 200);
            }
        }
    
        /**
         * 重写onDraw方法注意其中对padding的处理
         * @param canvas
         */
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            final int paddingLeft=getPaddingLeft();
            final int paddingTop=getPaddingTop();
            final int paddingRight=getPaddingRight();
            final int paddingBottom=getPaddingBottom();
            int width=getWidth()-paddingLeft-paddingRight;
            int height=getHeight()-paddingTop-paddingBottom;
            int radius=Math.min(width,height)/2;
            canvas.drawCircle(paddingLeft+width/2,paddingTop+height/2,radius,mPaint);
        }
    }

          顺便总结一下Android自定义属性的步骤

          (1)在values目录下创建自定义属性的XML文件,例如attrs.xml,文件名没有限制,文件中的内容如下(这里自定义一个格式为“color”的属性“circle_color”):

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="CircleView">
            <attr name="circle_color" format="color"/>
        </declare-styleable>
    </resources>

          (2)在View的构造方法中解析自定义属性的值并作相应处理。

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            TypedArray a=context.obtainStyledAttributes(attrs,R.styleable.CircleView);
            mColor=a.getColor(R.styleable.CircleView_circle_color,Color.RED);
            a.recycle();
            init();
        }

          注意最后要调用recycle方法来释放资源;

          (3)在布局文件中使用自定义属性。

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity"
        tools:showIn="@layout/activity_main">
    
        <tadakastu.myapplication.CircleView
            android:layout_margin="20dip"
            android:layout_width="wrap_content"
            android:layout_height="100dp"
            android:padding="20dip"
            app:circle_color="#00ff00"
            android:background="#000000" />
    </RelativeLayout>

          不要忘记添加

     xmlns:app="http://schemas.android.com/apk/res-auto"

          以上就是自定义属性的步骤。

          2.继承ViewGroup派生特殊的Layout

          这种方法主要用于实现自定义的布局(P 209页例子)。    

  • 相关阅读:
    Http中的patch
    如何实现腾讯地图的路径规划功能?
    各类数据库分页SQL语法
    ABC222F
    ABC222 G
    LG5308 [COCI2019] Quiz(wqs二分+斜率优化DP)
    [USACO21OPEN] Portals G(Kruskal)
    【做题笔记】SP27379 BLUNIQ
    【做题笔记】CF938C Constructing Tests
    CSP-J/S2021 自闭记
  • 原文地址:https://www.cnblogs.com/shiwei-bai/p/5082325.html
Copyright © 2020-2023  润新知