android自定义控件(理论知识学习 +自定义属性的讲解)

View树和UI界面架构图

android自定义控件(理论知识学习 +自定义属性的讲解)
 

UI界面架构图:

android视图最外层是一个window对象
phoneWindow来实现。
phoneWindow将一个decorView作为整个布局的根view.
屏幕分为TitleView和ContentView.
ContentView的根布局为framelayout.
 

view的测量:

view的测量通过onMesure()来进行的:
onMesure用来确定视图大小和位置。
MesureSpec用来帮助我们测量view.
  1. Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // TODO Auto-generated method stub
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);调用setMeasureredDimension(int width,int height);将测量后的数据设置进去。

 
    测量模式:
    EXACTLY
    当视图控件精确确定大小的时候,系统使用该模式,精确模式。默认支持这种模式。
    
    AT_MOST
    控件的layout_width和layout_height设置为wrap_layout的时候,控件尺寸不超过父控件大小。
    
    UNSPECIFIED
    自定义控件的使用使用,不指定view大小。
 
如何获取测量模式和测量大小:
  1. int specMode =MeasureSpec.getMode(measureSpec);
    int specsize =MeasureSpec.getSize(measureSpec);

     

  2. view的绘制:

View的绘制主要是在onDraw中通过canvas(画布)和paint(画笔)来进行绘制操作。
如下是绘制圆形ImageView里面onDraw方法里面的设置:
  1. @Override
        protected void onDraw(Canvas canvas) {
            if (getDrawable() == null) {
                return;
            }
            canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius,
                    mBitmapPaint);
            if (mBorderWidth != 0) {
                canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius,
                        mBorderPaint);
            }
        }
canvas当你重写onDraw方法的时候由系统提供,通过这个对象来进行绘制操作。
 

ViewGroup的测量:

ViewGroup的宽高设置为:
  1. android:layout_width="wrap_content"
    android:layout_height="wrap_content"
  2. 的时候,ViewGroup通过遍历其下面所有组  件来确定view的大小,子view的大小通过子view的onMesure来确定。

ViewGroup的绘制:

几乎不调用其onDraw方法进行绘制,大部分都是调用dispatchDraw()来绘制子view,遍历所有子view并进行绘制。
 
自定义组件几个比较重要的方法:
onFinishInflate()从xml加载组建后进行回调。
onSizeChanged()组件大小发生变化的时候回调。
onMesure()回调进行组件大小的测量。
onLayout()确定组件的显示位置。
onTouchEvent()确实视图组件事件的分发处理。
 
自定义组件属性以及在代码中如何获取已经给组件设置相关属性内容改变组件显示效果?
例如前面说到的自定义圆形Imageview,我们在attrs.xml文件定义如下:
首先贴出CircleImageView的源码,后面再讲解:
  1. package com.soyoungboy.base.view;
    import com.soyoungboy.base.R;
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Bitmap;
    import android.graphics.BitmapShader;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Matrix;
    import android.graphics.Paint;
    import android.graphics.RectF;
    import android.graphics.Shader;
    import android.graphics.drawable.BitmapDrawable;
    import android.graphics.drawable.ColorDrawable;
    import android.graphics.drawable.Drawable;
    import android.util.AttributeSet;
    import android.widget.ImageView;
    /**
     * 
     * 圆形Imageview
     *
     */
    public class CircleImageView extends ImageView {
        private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
        private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
        private static final int COLORDRAWABLE_DIMENSION = 1;
        private static final int DEFAULT_BORDER_WIDTH = 0;
        private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
        private final RectF mDrawableRect = new RectF();
        private final RectF mBorderRect = new RectF();
        private final Matrix mShaderMatrix = new Matrix();
        private final Paint mBitmapPaint = new Paint();
        private final Paint mBorderPaint = new Paint();
        private int mBorderColor = DEFAULT_BORDER_COLOR;
        private int mBorderWidth = DEFAULT_BORDER_WIDTH;
        private Bitmap mBitmap;
        private BitmapShader mBitmapShader;
        private int mBitmapWidth;
        private int mBitmapHeight;
        private float mDrawableRadius;
        private float mBorderRadius;
        private boolean mReady;
        private boolean mSetupPending;
        public CircleImageView(Context context) {
            super(context);
        }
        public CircleImageView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
        public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            super.setScaleType(SCALE_TYPE);
            TypedArray a = context.obtainStyledAttributes(attrs,
                    R.styleable.CircleImageView, defStyle, 0);
            mBorderWidth = a.getDimensionPixelSize(
                    R.styleable.CircleImageView_CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
            mBorderColor = a.getColor(R.styleable.CircleImageView_CircleImageView_border_color,
                    DEFAULT_BORDER_COLOR);
            a.recycle();
            mReady = true;
            if (mSetupPending) {
                setup();
                mSetupPending = false;
            }
        }
        @Override
        public ScaleType getScaleType() {
            return SCALE_TYPE;
        }
        @Override
        public void setScaleType(ScaleType scaleType) {
            if (scaleType != SCALE_TYPE) {
                throw new IllegalArgumentException(String.format(
                        "ScaleType %s not supported.", scaleType));
            }
        }
        @Override
        protected void onDraw(Canvas canvas) {
            if (getDrawable() == null) {
                return;
            }
            canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius,
                    mBitmapPaint);
            if (mBorderWidth != 0) {
                canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius,
                        mBorderPaint);
            }
        }
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            setup();
        }
        public int getBorderColor() {
            return mBorderColor;
        }
        public void setBorderColor(int borderColor) {
            if (borderColor == mBorderColor) {
                return;
            }
            mBorderColor = borderColor;
            mBorderPaint.setColor(mBorderColor);
            invalidate();
        }
        public int getBorderWidth() {
            return mBorderWidth;
        }
        public void setBorderWidth(int borderWidth) {
            if (borderWidth == mBorderWidth) {
                return;
            }
            mBorderWidth = borderWidth;
            setup();
        }
        @Override
        public void setImageBitmap(Bitmap bm) {
            super.setImageBitmap(bm);
            mBitmap = bm;
            setup();
        }
        @Override
        public void setImageDrawable(Drawable drawable) {
            super.setImageDrawable(drawable);
            mBitmap = getBitmapFromDrawable(drawable);
            setup();
        }
        @Override
        public void setImageResource(int resId) {
            super.setImageResource(resId);
            mBitmap = getBitmapFromDrawable(getDrawable());
            setup();
        }
        private Bitmap getBitmapFromDrawable(Drawable drawable) {
            if (drawable == null) {
                return null;
            }
            if (drawable instanceof BitmapDrawable) {
                return ((BitmapDrawable) drawable).getBitmap();
            }
            try {
                Bitmap bitmap;
                if (drawable instanceof ColorDrawable) {
                    bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION,
                            COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
                } else {
                    bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
                            drawable.getIntrinsicHeight(), BITMAP_CONFIG);
                }
                Canvas canvas = new Canvas(bitmap);
                drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
                drawable.draw(canvas);
                return bitmap;
            } catch (OutOfMemoryError e) {
                return null;
            }
        }
        private void setup() {
            if (!mReady) {
                mSetupPending = true;
                return;
            }
            if (mBitmap == null) {
                return;
            }
            mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP,
                    Shader.TileMode.CLAMP);
            mBitmapPaint.setAntiAlias(true);
            mBitmapPaint.setShader(mBitmapShader);
            mBorderPaint.setStyle(Paint.Style.STROKE);
            mBorderPaint.setAntiAlias(true);
            mBorderPaint.setColor(mBorderColor);
            mBorderPaint.setStrokeWidth(mBorderWidth);
            mBitmapHeight = mBitmap.getHeight();
            mBitmapWidth = mBitmap.getWidth();
            mBorderRect.set(0, 0, getWidth(), getHeight());
            mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2,
                    (mBorderRect.width() - mBorderWidth) / 2);
            mDrawableRect.set(mBorderWidth, mBorderWidth, mBorderRect.width()
                    - mBorderWidth, mBorderRect.height() - mBorderWidth);
            mDrawableRadius = Math.min(mDrawableRect.height() / 2,
                    mDrawableRect.width() / 2);
            updateShaderMatrix();
            invalidate();
        }
        private void updateShaderMatrix() {
            float scale;
            float dx = 0;
            float dy = 0;
            mShaderMatrix.set(null);
            if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width()
                    * mBitmapHeight) {
                scale = mDrawableRect.height() / (float) mBitmapHeight;
                dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
            } else {
                scale = mDrawableRect.width() / (float) mBitmapWidth;
                dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
            }
            mShaderMatrix.setScale(scale, scale);
            mShaderMatrix.postTranslate((int) (dx + 0.5f) + mBorderWidth,
                    (int) (dy + 0.5f) + mBorderWidth);
            mBitmapShader.setLocalMatrix(mShaderMatrix);
        }
    }
    <declare-styleable name="CircleImageView">
            <attr name="border_width" format="dimension" />
            <attr name="border_color" format="color" />
        </declare-styleable>

    CircleImageView为引用名称

format指定属性的类型
 
 1.reference:参考指定Theme中资源ID,这个类型意思就是你传的值可以是引用资源
 2.string:字符串,如果你想别人既能直接写值也可以用类似"@string/test"引用资源的方式,可以写成format="string|reference"
 3.Color:颜色
 4.boolean:布尔值
 5.dimension:尺寸值
 6.float:浮点型
 7.integer:整型
 8.fraction:百分数
 9.enum:枚举 ,如果你提供的属性只能让别人选择,不能随便传入,就可以写成这样
如何在xml文件中设置自定义属性:
  1. <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:orientation="vertical" >
      <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:padding="@dimen/base_padding"
        android:background="@color/light">
        <de.hdodenhof.circleimageview.CircleImageView
          android:layout_width="160dp"
          android:layout_height="160dp"
          android:layout_centerInParent="true"
          android:src="@drawable/demo"
          app:border_width="2dp"
          app:border_color="@color/dark" />
      </RelativeLayout>
      <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:padding="@dimen/base_padding"
        android:background="@color/dark">
        <de.hdodenhof.circleimageview.CircleImageView
          android:layout_width="160dp"
          android:layout_height="160dp"
          android:layout_centerInParent="true"
          android:src="@drawable/lena"
          app:border_width="2dp"
          app:border_color="@color/light" />
      </RelativeLayout>
    </LinearLayout>
  2. xmlns:app="http://schemas.android.com/apk/res-auto"自定义属性的前缀,前缀为app,也就说后面自定义的属性设置必须以app开头,例如后面的:
  3. app:border_width="2dp"
    app:border_color="@color/light"
那么如何在代码中获取属性并设置组件属性呢?我们看下CircleImageView组件的构造方法:
  1. public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            super.setScaleType(SCALE_TYPE);
            TypedArray a = context.obtainStyledAttributes(attrs,
                    R.styleable.CircleImageView, defStyle, 0);
            mBorderWidth = a.getDimensionPixelSize(
                    R.styleable.CircleImageView_CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
            mBorderColor = a.getColor(R.styleable.CircleImageView_CircleImageView_border_color,
                    DEFAULT_BORDER_COLOR);
            a.recycle();
            mReady = true;
            if (mSetupPending) {
                setup();
                mSetupPending = false;
            }
  2. 通过context.obtainStyledAttributes来获取TypedArray对象,通过不同的方法来获取你设置在布局文件里面的属性,后面如果如下内容将获取到的资源设置到组件上面去:
mBorderPaint.setColor(mBorderColor);
mBorderPaint.setStrokeWidth(mBorderWidth);
注意获取完属性数据后,通过recycle来回收资源。其实设置自定义属性主要在设置属性参数类型和在代码中获取属性以及在组件上根据获取的属性内容设置到组件上面,并影响在组件上的显示效果。
 
自定义控件之组合控件:
 
组合控件有两种实现方式:
1,通过在xml文件中定义布局的大概样子,通过继承viewGroup相关控件,然后写上一些回调方法,通过java代码来控制组件内容显示,隐藏,内容变化,颜色,状态的变化等....,这种方式比较简便,容易实现,所以经常使用在普通的组合控件使用上,比如对一些项目中常用布局的组装。前边也有讲到比较简单的实现:http://www.cnblogs.com/androidsuperman/p/4580523.html
 
2,就是在java代码里面实现,其实就是xml里面对组件的形式通过set的形式添加到组件上那种。很少这种去实现。
 
对于组件的点击等事件处理,一般都是些接口,接口里面方法指向对应组件的系统点击等事件来处理。
上一篇:补习系列-springboot-使用assembly进行项目打包


下一篇:【观点】为什么说区块链的未来或许与数字货币无关