前言:

在日常的Android开发中会经常和控件打交道,有时Android提供的控件未必能满足业务的需求,这个时候就需要我们实现自定义一些控件,这里将介绍自定义控件的原理和实现方法。

参考文章:

  • Android自定义控件
  • Android自定义控件之基本原理
  • Android自定义控件之自定义属性

自定义控件要求:

  • 应当遵守Android标准的规范(命名,可配置,事件处理等)。
  • 在XML布局中可配置控件的属性。
  • 对交互应当有合适的反馈,比如按下,点击等。
  • 具有兼容性, Android版本很多,应该具有广泛的适用性。

创建自定义控件步骤:

下面以创建一个圆形百分比控件为例,讲解自定义控件的实现过程。

第一步:声明属性

在res/values文件下添加一个attrs.xml文件,如果项目比较大的话,会导致attrs.xml代码相当庞大,这时可以根据相应的功能模块起名字,方便查找,例如:登录模块相关attrs_login.xml

使用 来定义一个属性集合,name就是属性集合的名字,这个名字一定要起的见名知意。

然后就是定义属性值了,通过方式定义属性值,属性名字同样也要起的见名知意,format表示这个属性的值的类型,类型有以下几种:

  • reference:引用资源
  • string:字符串
  • color:颜色
  • boolean:布尔值
  • dimension:尺寸值
  • float:浮点型
  • integer:整型
  • fraction:百分数
  • enum:枚举类型
  • flag:位或运算

基于上面的要求,我们可以定义一下百分比控件属性:

<declare-styleable name="PercentView">        <attr name="percent_circle_gravity">            <flag name="left" value="0" />            <flag name="top" value="1" />            <flag name="center" value="2" />            <flag name="right" value="3" />            <flag name="bottom" value="4" />        attr>        <attr name="percent_circle_radius" format="dimension" />        <attr name="percent_circle_progress" format="integer" />        <attr name="percent_progress_color" format="color" />        <attr name="percent_background_color" format="color" />    declare-styleable>

第二步:在布局中引用

使用xmlns:fk="http://schemas.android.com/apk/res-auto"为属性集设置一个属性集名称,我这里用的fk,建议在真正的项目中使用项目的缩写,比如微信可能就是使用wx。

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:fk="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <fk.androiddemo_035.PercentView        android:layout_width="200dp"        android:layout_height="200dp"        android:layout_margin="10dp"        android:background="@color/red"        android:padding="10dp"        fk:percent_background_color="@color/gray"        fk:percent_circle_gravity="left"        fk:percent_circle_progress="30"        fk:percent_circle_radius="50dp"        fk:percent_progress_color="@color/blue" />LinearLayout>

第三步:编写继承自View的子类

一、View结构原理

Android系统的视图结构的设计也采用了组合模式,即View作为所有图形的基类,Viewgroup对View继承扩展为视图容器类。

View定义了绘图的基本操作,基本操作由三个函数完成:measure()、layout()、draw(),其内部又分别包含了onMeasure()、onLayout()、onDraw()三个子方法。具体操作如下:

1、measure操作

measure操作主要用于计算视图的大小,即视图的宽度和长度。在view中定义为final类型,要求子类不能修改。measure()函数中又会调用onMeasure()函数,视图大小的将在这里最终确定,也就是说measure只是对onMeasure的一个包装,子类可以覆写onMeasure()方法实现自己的计算视图大小的方式,并通过setMeasuredDimension(width, height)保存计算结果。

2、layout操作

layout操作用于设置视图在屏幕中显示的位置。在view中定义为final类型,要求子类不能修改。layout()函数中有两个基本操作:

(1)setFrame(l,t,r,b),l,t,r,b即子视图在父视图中的具体位置,该函数用于将这些参数保存起来;

(2)onLayout(),在View中这个函数什么都不会做,提供该函数主要是为viewGroup类型布局子视图用的;

3、draw操作

draw操作利用前两部得到的参数,将视图显示在屏幕上,到这里也就完成了整个的视图绘制工作。子类也不应该修改该方法,因为其内部定义了绘图的基本操作:

(1)绘制背景;

(2)如果要视图显示渐变框,这里会做一些准备工作;

(3)绘制视图本身,即调用onDraw()函数。在view中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的显示(比如TextView在这里实现了绘制文字的过程)。而对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的,其包含了多个子view,而子View已经实现了自己的绘制方法,因此只需要告诉子view绘制自己就可以了,也就是下面的dispatchDraw()方法;

(4)绘制子视图,即dispatchDraw()函数。在view中这是个空函数,具体的视图不需要实现该方法,它是专门为容器类准备的,也就是容器类必须实现该方法;

(5)如果需要(应用程序调用了setVerticalFadingEdge或者setHorizontalFadingEdge),开始绘制渐变框;

(6)绘制滚动条;

从上面可以看出自定义View需要最少覆写onMeasure()和onDraw()两个方法。

二、View类的构造方法

创建自定义控件的3种主要实现方式:

  1. 继承已有的控件来实现自定义控件: 主要是当要实现的控件和已有的控件在很多方面比较类似, 通过对已有控件的扩展来满足要求。
  2. 通过继承一个布局文件实现自定义控件,一般来说做组合控件时可以通过这个方式来实现。注意此时不用onDraw方法,在构造函数中通过Inflater加载自定义控件的布局文件,再addView(view),自定义控件的图形界面就加载进来了。
  3. 通过继承view类来实现自定义控件,使用GDI绘制出组件界面,一般无法通过上述两种方式来实现时用该方式。

三、获取自定义属性

每一个属性集合编译之后都会对应一个styleable对象,通过styleable对象获取TypedArray typedArray,然后通过键值对获取属性值,这点有点类似SharedPreference的取法。

TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.PercentView);if (typedArray != null) {backgroundColor = typedArray.getColor(R.styleable.PercentView_percent_background_color, Color.GRAY);    progressColor = typedArray.getColor(R.styleable.PercentView_percent_progress_color, Color.BLUE);    radius = typedArray.getDimension(R.styleable.PercentView_percent_circle_radius, 0);    progress = typedArray.getInt(R.styleable.PercentView_percent_circle_progress, 0);    gravity = typedArray.getInt(R.styleable.PercentView_percent_circle_gravity, CENTER);    typedArray.recycle();}

下面是整个PrecentView类代码:

package fk.androiddemo_035;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.RectF;import android.util.AttributeSet;import android.util.Log;import android.view.View;/** * Created by FK on 2017/2/15. */public class PercentView extends View {    private final static String TAG = PercentView.class.getSimpleName();    private Paint mPaint;    private int backgroundColor = Color.GRAY;    private int progressColor = Color.BLUE;    private float radius;    private int progress;    private float centerX = 0;    private float centerY = 0;    public static final int LEFT = 0;    public static final int TOP = 1;    public static final int CENTER = 2;    public static final int RIGHT = 3;    public static final int BOTTOM = 4;    private int gravity = CENTER;    private RectF rectF;  //用于定义的圆弧的形状和大小的界限    public PercentView(Context context) {        super(context);        init();    }    public PercentView(Context context, AttributeSet attrs) {        super(context, attrs);        initParams(context, attrs);        init();    }    public PercentView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        initParams(context, attrs);        init();    }    private void init() {        mPaint = new Paint();        mPaint.setAntiAlias(true);        rectF = new RectF();    }    private void initParams(Context context, AttributeSet attrs) {        mPaint = new Paint();        mPaint.setAntiAlias(true);        rectF = new RectF();        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PercentView);        if (typedArray != null) {            backgroundColor = typedArray.getColor(R.styleable.PercentView_percent_background_color, Color.GRAY);            progressColor = typedArray.getColor(R.styleable.PercentView_percent_progress_color, Color.BLUE);            radius = typedArray.getDimension(R.styleable.PercentView_percent_circle_radius, 0);            progress = typedArray.getInt(R.styleable.PercentView_percent_circle_progress, 0);            gravity = typedArray.getInt(R.styleable.PercentView_percent_circle_gravity, CENTER);            typedArray.recycle();        }    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPaint.setColor(backgroundColor);        // FILL填充, STROKE描边,FILL_AND_STROKE填充和描边        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);        canvas.drawCircle(centerX, centerY, radius, mPaint);        mPaint.setColor(progressColor);        double percent = progress * 1.0 / 100;        int angle = (int) (percent * 360);        canvas.drawArc(rectF, 270, angle, true, mPaint);  //根据进度画圆弧    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        Log.e(TAG, "onMeasure--widthMode-->" + widthMode);        switch (widthMode) {            case MeasureSpec.EXACTLY://                break;            case MeasureSpec.AT_MOST:                break;            case MeasureSpec.UNSPECIFIED:                break;        }        Log.e(TAG, "onMeasure--widthSize-->" + widthSize);        Log.e(TAG, "onMeasure--heightMode-->" + heightMode);        Log.e(TAG, "onMeasure--heightSize-->" + heightSize);        int with = getWidth();        int height = getHeight();        Log.e(TAG, "onDraw---->" + with + "*" + height);        centerX = with / 2;        centerY = with / 2;        switch (gravity) {            case LEFT:                centerX = radius + getPaddingLeft();                break;            case TOP:                centerY = radius + getPaddingTop();                break;            case CENTER:                break;            case RIGHT:                centerX = with - radius - getPaddingRight();                break;            case BOTTOM:                centerY = height - radius - getPaddingBottom();                break;        }        float left = centerX - radius;        float top = centerY - radius;        float right = centerX + radius;        float bottom = centerY + radius;        rectF.set(left, top, right, bottom);    }    @Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        super.onLayout(changed, left, top, right, bottom);        Log.e(TAG, "onLayout");    }}

运行结果

根据不同的配置显示的两种效果

Android 自定义控件_第1张图片

源码下载

地址:http://download.csdn.net/detail/youmingyu/9755075

更多相关文章

  1. Unity3D游戏开发之在Android视图中嵌入Unity视图
  2. android使用属性动画代替补间动画
  3. Android控件开发——ListView
  4. Android 的系统属性(SystemProperties)分析

随机推荐

  1. Android(安卓)GridView 的使用
  2. android:拷贝sqlite数据库到本地sd卡
  3. AndroidのContentProvider之数据库更新UI
  4. Android客户端异常检测
  5. Android之PullToRefresh(ListView 、Grid
  6. Android(安卓)ContentProvider详解
  7. 记录 Android(安卓)WebView 开发过程的坑
  8. PreferenceActivity 全接触
  9. Android开发环境使用工具Android(安卓)St
  10. Android(安卓)Activity原理以及其子类描