Android 自定义控件
2017-02-15 15:27
381 查看
前言:
在日常的Android开发中会经常和控件打交道,有时Android提供的控件未必能满足业务的需求,这个时候就需要我们实现自定义一些控件,这里将介绍自定义控件的原理和实现方法。参考文章:
Android自定义控件
Android自定义控件之基本原理
Android自定义控件之自定义属性
自定义控件要求:
应当遵守Android标准的规范(命名,可配置,事件处理等)。在XML布局中可配置控件的属性。
对交互应当有合适的反馈,比如按下,点击等。
具有兼容性, Android版本很多,应该具有广泛的适用性。
创建自定义控件步骤:
下面以创建一个圆形百分比控件为例,讲解自定义控件的实现过程。第一步:声明属性
在res/values文件下添加一个attrs.xml文件,如果项目比较大的话,会导致attrs.xml代码相当庞大,这时可以根据相应的功能模块起名字,方便查找,例如:登录模块相关attrs_login.xml使用
<declare-styleable name="PercentView"> </declare-styleable>来定义一个属性集合,name就是属性集合的名字,这个名字一定要起的见名知意。
然后就是定义属性值了,通过
<attr name="textColor" format="color" />方式定义属性值,属性名字同样也要起的见名知意,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种主要实现方式:继承已有的控件来实现自定义控件: 主要是当要实现的控件和已有的控件在很多方面比较类似, 通过对已有控件的扩展来满足要求。
通过继承一个布局文件实现自定义控件,一般来说做组合控件时可以通过这个方式来实现。注意此时不用onDraw方法,在构造函数中通过Inflater加载自定义控件的布局文件,再addView(view),自定义控件的图形界面就加载进来了。
通过继承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"); } }
运行结果
根据不同的配置显示的两种效果源码下载
地址:http://download.csdn.net/detail/youmingyu/9755075相关文章推荐
- android自定义控件的创建和使用
- Android 实现自定义控件效果1
- Android中自定义控件
- android中使用自定义控件是报android.view.InflateException: Binary XML 异常
- Android应用之个人应用软件开发(4)【深度UI设计自定义控件】
- Android 自定义控件 eBook 翻书效果
- Android 自定义控件-SnakeLayout (仿gallery)
- android EditText里面嵌入两个按钮,通过按钮可以加减EditText里的数字,组合自定义控件。
- Android declare-styleable:自定义控件的属性(attr.xml,TypedArray)的使用
- Android 实现自定义控件效果2
- Android 自定义控件 eBook 翻书效果
- Android 自定义控件 eBook 翻书效果
- Android 自定义控件的 拖拽、移动 实现 方法
- Android 自定义控件 仿乐Phone UI
- 【转】Android自定义控件中自定义属性的处理方式
- android 自定义控件属性
- Android 自定义控件-SnakeLayout (仿gallery)
- android自定义控件的创建和使用
- [转]Android.自定义控件的实现
- Android自定义控件(2)