zoukankan      html  css  js  c++  java
  • Android读书笔记3:控件架构以及自定义控件

    安卓平台上这么多多姿多彩的控件是怎么制作出来的?有系统自定义的,也有开发者在系统的基础上进行自定义的。但是他们一定都遵循一定的规则,那就是android对于控件的架构设计。

    言简意赅地说下:
    1、所有的控件都有 共同的父类,要么父类是View,要么父类是ViewGroup,顾名思义,后者意思是View的群组,前者是单个控件。
    有一个概念叫做控件树,即 所有的控件如果画成结构图,一定是一个树状结构图,我们activity里面用的findViewById就是按照树的深度优先遍历来查找对应的view( 从这里看来,如果xml里面写过太过复杂,有可能影响findViewById的效率)。
    View一定是要在ViewGroup内部的,ViewGroup对于其内部的View进行统一调度和分配,控制整个视图效果。

    如图:这是AndroidStudio里面的一个组件树,表示一个layout.xml里面所有组件的结构图。

    2、控件的绘制和显示:activity是人机交互界面,在activity的onCreate方法内部,调用了setContentView之后,activity的内容就会被绘制出来。而绘制的第一步,就是测量Measure。每一个组件在创建之后,都会被分配一个矩形区域,作为它的所属区域用于绘制视图。那这个矩形区域多大,位置在哪?
    矩形的大小,是由View内部的一个方法:onMeasure()

    这是View:onMeasure的一段注释。大概意思是:
    如果这个方法被子类重写了,那么子类就有责任确保测量过的宽高至少是view的最小宽高,具体怎么理解,请看下面的代码。

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            Log.d("MyTag", "onMeasure");
            setMeasuredDimension(myMeasure(widthMeasureSpec, 400), myMeasure(heightMeasureSpec, 100));
        }
    
        /**
         * 如果写自定义组件,那么这段代码基本可以作为 measure 的模板
         *
         * @param measureSpec
         * @param defaultValue
         * @return
         */
        private int myMeasure(int measureSpec, int defaultValue) {
            int result = 0;
            int specMode = MeasureSpec.getMode(measureSpec);
            int specSize = MeasureSpec.getSize(measureSpec);
    
            // 精确模式.
            // 如果在xml内部,定义组件的宽/高的时候,使用的是具体的数字+单位,这种组合。
            // 那么就是精确模式,因为在xml里面明确指定了组件的宽/高。
            if (specMode == MeasureSpec.EXACTLY) {
                result = specSize;
            } else {
                //接着上面的说,如果系统指定宽/高的是wrap_content,
                // 那这个就是AT_MOST模式,在这里就必须给它指定一个默认值defaultValue,这样,就优化了代码,
                // 如果指定wrap_content,系统不知道该分配多大空间给它,于是就默认这个组件的宽/高充满父组件,这个显然不合理,很浪费资源的。
                // 所以这里指定一个默认的最大值,就算组件没有内容,系统也知道最大应该给他多少空间,不会造成浪费
                if (specMode == MeasureSpec.AT_MOST) {
                    result = Math.min(defaultValue, specSize);//这里获得的specSize其实是父组件的size,所以要取二者之小
                }
            }
            return result;
        }

    3、ViewGroup的绘制:其实它本身没有要绘制的东西,除非指定了它的背景颜色。但是ViewGroup会遍历绘制其所有子view,并且调用子view的onDraw方法来绘制。

    4、自定义控件的三种方式:

      1)对现有控件进行扩展:继承系统控件,并且重写onDraw方法。

    package cxy.com.waterviewdemo;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.LinearGradient;
    import android.graphics.Matrix;
    import android.graphics.Paint;
    import android.graphics.Shader;
    import android.os.Build;
    import android.support.annotation.RequiresApi;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.widget.TextView;
    
    /**
     * Created by Hank on 2017/2/22.
     */
    
    public class MyTextView extends TextView {
    
        public MyTextView(Context context) {
            super(context);
        }
    
        public MyTextView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        public MyTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            Log.d("MyTag", "onMeasure");
            setMeasuredDimension(myMeasure(widthMeasureSpec, 400), myMeasure(heightMeasureSpec, 100));
        }
    
        /**
         * 如果写自定义组件,那么这段代码基本可以作为 measure 的模板
         *
         * @param measureSpec
         * @param defaultValue
         * @return
         */
        private int myMeasure(int measureSpec, int defaultValue) {
            int result = 0;
            int specMode = MeasureSpec.getMode(measureSpec);
            int specSize = MeasureSpec.getSize(measureSpec);
    
            // 精确模式.
            // 如果在xml内部,定义组件的宽/高的时候,使用的是具体的数字+单位,这种组合。
            // 那么就是精确模式,因为在xml里面明确指定了组件的宽/高。
            if (specMode == MeasureSpec.EXACTLY) {
                result = specSize;
            } else {
                //接着上面的说,如果系统指定宽/高的是wrap_content,
                // 那这个就是AT_MOST模式,在这里就必须给它指定一个默认值defaultValue,这样,就优化了代码,
                // 如果这个组件目前还没有内容,所以系统就不知道该分配多大空间给它,于是就默认这个组件的宽/高充满父组件,这个显然不合理,很浪费资源的。
                // 所以这里指定一个默认的最大值,就算组件没有内容,系统也知道最大应该给他多少空间,不会造成浪费
                if (specMode == MeasureSpec.AT_MOST) {
                    result = Math.min(defaultValue, specSize);//这里获得的specSize其实是父组件的size,所以要取二者之小
                }
            }
            return result;
        }
    
        Paint paint1, paint2;
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //下面的代码是画边框
            drawRect(canvas);
            super.onDraw(canvas);
            makeWordsBlink();
            canvas.restore();
        }
    
        //上午先解密一下这个渐变器是怎么起作用的
        private int mTranslate;//这里应该是横坐标,默认为0
        private int frequency = 20;//渐变频率:整个组件的长度分成多少等分来进行渐变
    
        private void makeWordsBlink() {
            Log.d("MyTag", "makeWordsBlink:mTranslate=" + mTranslate);
            if (mGradientMatrix != null) {//如果渐变器不为空,就执行渐变过程
                mTranslate += mViewWidth / frequency;//这个变量也有点怪怪的,看不懂,先来分析这里
                //将整个view的长度分成50份,每一次向右移动1/50;
                if (mTranslate > 2 * mViewWidth) {//这个是啥意思,我还得琢磨一下,没试验出什么作用,先保留吧
                    mTranslate = -mViewWidth;
                }
                mGradientMatrix.setTranslate(mTranslate, 0);
                mLinearGradient.setLocalMatrix(mGradientMatrix);
                postInvalidateDelayed(100);
            }
        }
    
        /**
         * 改变textView的边框效果
         *
         * @param canvas
         */
        private void drawRect(Canvas canvas) {
            //初始化画笔
            paint1 = new Paint();
            paint1.setColor(Color.GREEN);
            paint1.setStyle(Paint.Style.FILL);//"充满"模式
    
            paint2 = new Paint();
            paint2.setColor(Color.WHITE);
            paint2.setStyle(Paint.Style.FILL);//"充满"模式
    
            canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint1);//外边框
            canvas.drawRect(10, 10, getMeasuredWidth() - 10, getMeasuredHeight() - 10, paint2);//内边框
            canvas.save();
        }
    
        private int mViewWidth;
        private Paint mPaint;
        private LinearGradient mLinearGradient;
        private Matrix mGradientMatrix;
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            Log.d("MyTag", "onSizeChanged");//这里的onSizeChanged只执行了一次
            createLinearGradientAndMatrix();//原来这里搞一个onSizeChanged只是为了创建相关的类?
        }
    
        private void createLinearGradientAndMatrix() {
            if (mViewWidth == 0) {
                mViewWidth = getMeasuredWidth();//经过测量之后确定下来的组件宽度
                if (mViewWidth > 0) {
                    mPaint = getPaint();//写text的那个画笔
                    Log.d("MyTag", "createLinearGradientAndMatrix:mViewWidth=" + mViewWidth);
                    mLinearGradient = new LinearGradient(0, 0, mViewWidth, 0, new int[]{Color.BLACK, Color.CYAN, Color.BLUE}, null, Shader.TileMode.REPEAT);//这个类有点奇怪
                    mPaint.setShader(mLinearGradient);//给画笔设置渐变器
                    mGradientMatrix = new Matrix();//这里只是为了创建这个“线性渐变器” LinearGradient和“矩阵” Matrix
                }
            }
        }
    }
    

    最终形成了这种,带有边框,并且颜色会渐变的自定义textView。

      

    2) 复合型控件:

    关于自定义控件的部分案例,已上传到guthub:

    https://github.com/18598925736/hankAndroidStudy。

  • 相关阅读:
    OWNER:Java配置文件解决方案 使用简介
    验证数字最简单正则表达式大全
    使用Spring进行统一日志管理 + 统一异常管理
    SpringMVC 拦截器
    Java排序
    tomcat编码配置
    日常任务
    netty入门代码学习
    redis学习
    AutoLayout And Animation
  • 原文地址:https://www.cnblogs.com/hankzhouAndroid/p/6432698.html
Copyright © 2011-2022 走看看