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

简介: View树和UI界面架构图   UI界面架构图: android视图最外层是一个window对象。 phoneWindow来实现。 phoneWindow将一个decorView作为整个布局的根view. 屏幕分为TitleView和ContentView. ContentView的根布局为framelayout.   view的测量: view的测量通过onMesure()来进行的: onMesure用来确定视图大小和位置。

View树和UI界面架构图

 

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的形式添加到组件上那种。很少这种去实现。
 
对于组件的点击等事件处理, 一般都是些接口,接口里面方法指向对应组件的系统点击等事件来处理。
相关文章
|
1月前
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
27 1
|
2月前
|
缓存 搜索推荐 Android开发
安卓开发中的自定义控件实践
【10月更文挑战第4天】在安卓开发的海洋中,自定义控件是那片璀璨的星辰。它不仅让应用界面设计变得丰富多彩,还提升了用户体验。本文将带你探索自定义控件的核心概念、实现过程以及优化技巧,让你的应用在众多竞争者中脱颖而出。
|
2月前
|
Android开发 开发者
安卓应用开发中的自定义视图
【9月更文挑战第37天】在安卓开发的海洋中,自定义视图犹如一座座小岛,等待着勇敢的探索者去发现其独特之处。本文将带领你踏上这段旅程,从浅滩走向深海,逐步揭开自定义视图的神秘面纱。
44 3
|
2月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
101 0
|
22天前
|
搜索推荐 Android开发 开发者
安卓应用开发中的自定义控件实践
在安卓应用开发的广阔天地中,自定义控件如同璀璨的星辰,点亮了用户界面设计的夜空。它们不仅丰富了交互体验,更赋予了应用独特的个性。本文将带你领略自定义控件的魅力,从基础概念到实际应用,一步步揭示其背后的原理与技术细节。我们将通过一个简单的例子——打造一个具有独特动画效果的按钮,来展现自定义控件的强大功能和灵活性。无论你是初学者还是资深开发者,这篇文章都将为你打开一扇通往更高阶UI设计的大门。
|
1月前
|
搜索推荐 前端开发 Android开发
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。
|
1月前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
41 5
|
2月前
|
缓存 搜索推荐 Android开发
安卓开发中的自定义控件基础与进阶
【10月更文挑战第5天】在Android应用开发中,自定义控件是提升用户体验和界面个性化的重要手段。本文将通过浅显易懂的语言和实例,引导你了解自定义控件的基本概念、创建流程以及高级应用技巧,帮助你在开发过程中更好地掌握自定义控件的使用和优化。
56 10
|
1月前
|
前端开发 Android开发 UED
安卓应用开发中的自定义控件实践
【10月更文挑战第35天】在移动应用开发中,自定义控件是提升用户体验、增强界面表现力的重要手段。本文将通过一个安卓自定义控件的创建过程,展示如何从零开始构建一个具有交互功能的自定义视图。我们将探索关键概念和步骤,包括继承View类、处理测量与布局、绘制以及事件处理。最终,我们将实现一个简单的圆形进度条,并分析其性能优化。
|
2月前
|
XML 前端开发 Java
安卓应用开发中的自定义View组件
【10月更文挑战第5天】自定义View是安卓应用开发的一块基石,它为开发者提供了无限的可能。通过掌握其原理和实现方法,可以创造出既美观又实用的用户界面。本文将引导你了解自定义View的创建过程,包括绘制技巧、事件处理以及性能优化等关键步骤。