android使用AttributeSet自定义控件
16lz
2021-01-25
当android官方提供的标准控件无法满足我们的需求时,我们可以选择根据自己的需求自定义一个控件,主要要用到AttributeSet和Paint绘图类,具体操作步骤如下
1.我们的自定义控件和其他的控件一样,应该写成一个类,这个类继承View,而这个类的属性是是有自己来决
定的.
2.我们要在res/values目录下建立一个attrs.xml的文件,并在此文件中增加对控件的属性的定义,例如:
<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="CProgressBar_Style" > <attr name="WhorlView_SmallWhorlColor" format="color" /> <attr name="WhorlView_MiddleWhorlColor" format="color" /> <attr name="WhorlView_BigWhorlColor" format="color" /> <attr name="WhorlView_CircleSpeed" format="integer" /> <attr name="WhorlView_Parallax" > <enum name="fast" value="1" /> <enum name="medium" value="0" /> <enum name="slow" value="2" /> </attr> <attr name="WhorlView_SweepAngle" format="float" /> <attr name="WhorlView_StrokeWidth" format="float" /> </declare-styleable></resources>
关于attrs属性的相关知识,即Attr属性是如何在XML中定义的,自定义属性的Value值可以有10种类型以及其类型的组合值,其具体使用方法如下
1. reference:参考某一资源ID。 (1)属性定义: <declare-styleable name = "名称"> <attr name = "background" format = "reference" /> </declare-styleable> (2)属性使用: <ImageView android:layout_width = "42dip" android:layout_height = "42dip" android:background = "@drawable/图片ID" /> 2. color:颜色值。 (1)属性定义: <declare-styleable name = "名称"> <attr name = "textColor" format = "color" /> </declare-styleable> (2)属性使用: <TextView android:layout_width = "42dip" android:layout_height = "42dip" android:textColor = "#00FF00" /> 3. boolean:布尔值。 (1)属性定义: <declare-styleable name = "名称"> <attr name = "focusable" format = "boolean" /> </declare-styleable> (2)属性使用: <Button android:layout_width = "42dip" android:layout_height = "42dip" android:focusable = "true" /> 4. dimension:尺寸值。 (1)属性定义: <declare-styleable name = "名称"> <attr name = "layout_width" format = "dimension" /> </declare-styleable> (2)属性使用: <Button android:layout_width = "42dip" android:layout_height = "42dip" /> 5. float:浮点值。 (1)属性定义: <declare-styleable name = "AlphaAnimation"> <attr name = "fromAlpha" format = "float" /> <attr name = "toAlpha" format = "float" /> </declare-styleable> (2)属性使用: <alpha android:fromAlpha = "1.0" android:toAlpha = "0.7" /> 6. integer:整型值。 (1)属性定义: <declare-styleable name = "AnimatedRotateDrawable"> <attr name = "visible" /> <attr name = "frameDuration" format="integer" /> <attr name = "framesCount" format="integer" /> <attr name = "pivotX" /> <attr name = "pivotY" /> <attr name = "drawable" /> </declare-styleable> (2)属性使用: <animated-rotate xmlns:android = "http://schemas.android.com/apk/res/android" android:drawable = "@drawable/图片ID" android:pivotX = "50%" android:pivotY = "50%" android:framesCount = "12" android:frameDuration = "100" /> 7. string:字符串。 (1)属性定义: <declare-styleable name = "MapView"> <attr name = "apiKey" format = "string" /> </declare-styleable> (2)属性使用: <com.google.android.maps.MapView android:layout_width = "fill_parent" android:layout_height = "fill_parent" android:apiKey = "0jOkQ80oD1JL9C6HAja99uGXCRiS2CGjKO_bc_g" /> 8. fraction:百分数。 (1)属性定义: <declare-styleable name="RotateDrawable"> <attr name = "visible" /> <attr name = "fromDegrees" format = "float" /> <attr name = "toDegrees" format = "float" /> <attr name = "pivotX" format = "fraction" /> <attr name = "pivotY" format = "fraction" /> <attr name = "drawable" /> </declare-styleable> (2)属性使用: <rotate xmlns:android = "http://schemas.android.com/apk/res/android" android:interpolator = "@anim/动画ID" android:fromDegrees = "0" android:toDegrees = "360" android:pivotX = "200%" android:pivotY = "300%" android:duration = "5000" android:repeatMode = "restart" android:repeatCount = "infinite" /> 9. enum:枚举值。 (1)属性定义: <declare-styleable name="名称"> <attr name="orientation"> <enum name="horizontal" value="0" /> <enum name="vertical" value="1" /> </attr> </declare-styleable> (2)属性使用: <LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" android:orientation = "vertical" android:layout_width = "fill_parent" android:layout_height = "fill_parent" > </LinearLayout> 10. flag:位或运算。 (1)属性定义: <declare-styleable name="名称"> <attr name="windowSoftInputMode"> <flag name = "stateUnspecified" value = "0" /> <flag name = "stateUnchanged" value = "1" /> <flag name = "stateHidden" value = "2" /> <flag name = "stateAlwaysHidden" value = "3" /> <flag name = "stateVisible" value = "4" /> <flag name = "stateAlwaysVisible" value = "5" /> <flag name = "adjustUnspecified" value = "0x00" /> <flag name = "adjustResize" value = "0x10" /> <flag name = "adjustPan" value = "0x20" /> <flag name = "adjustNothing" value = "0x30" /> </attr> </declare-styleable> (2)属性使用: <activity android:name = ".StyleAndThemeActivity" android:label = "@string/app_name" android:windowSoftInputMode = "stateUnspecified | stateUnchanged | stateHidden"> <intent-filter> <action android:name = "android.intent.action.MAIN" /> <category android:name = "android.intent.category.LAUNCHER" /> </intent-filter> </activity> 注意: 属性定义时可以指定多种类型值。 (1)属性定义: <declare-styleable name = "名称"> <attr name = "background" format = "reference|color" /> </declare-styleable> (2)属性使用: <ImageView android:layout_width = "42dip" android:layout_height = "42dip" android:background = "@drawable/图片ID|#00FF00" />
3.使用AttributeSet来完成控件类的构造函数,并在构造函数中将自定义控件类中变量与attrs.xml
中的属性连接起来.
这里用一个完整例子来说明,例子原创来自于github,自加的注释
package com.duke.chenyouhui.customprograssbar;import android.annotation.TargetApi;import android.content.Context;import android.content.res.Resources;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.RectF;import android.os.Build;import android.util.AttributeSet;import android.view.View;/** * Created by chenyouhui on 2015/9/7. * 自定义progressbar */public class CProgressBar extends View{ private static final int CIRCLE_NUM = 3;//环的数目 //旋转速度编号 public static final int FAST = 1; public static final int MEDIUM = 0; public static final int SLOW = 2; //旋转速度具体值 private static final int PARALLAX_FAST = 60; private static final int PARALLAX_MEDIUM = 72; private static final int PARALLAX_SLOW = 90; private static final long REFRESH_DURATION = 16L;//时间 //当前动画时间 private long mCircleTime; //每层颜色 private int[] mLayerColors = new int[CIRCLE_NUM]; //旋转速度 private int mCircleSpeed; //视差差速 private int mParallaxSpeed; //弧长 private float mSweepAngle; //弧宽 private float mStrokeWidth; /** * 如果在Code中实例化一个View会调用第一个构造函数,如果在xml中定义会调用第二个构造函数, * 而第三个函数系统是不调用的,要由View(我们自定义的或系统预定义的View,如此处的CustomTextView * 和Button)显式调用,比如在这里我们在第二个构造函数中调用了第三个构造函数, * 并将R.attr.CustomizeStyle传给了第三个参数。第三个参数的意义就如同它的名字所说的, * 是默认的Style,只是这里没有说清楚,这里的默认的Style是指它在当前Application或Activity * 所用的Theme中的默认Style */ public CProgressBar(Context context, AttributeSet attrs) { this(context,attrs,0); } public CProgressBar(Context context) { this(context,null,0); } /** * @param attrs AttributeSet属性 * @param context 上下文 * @param defStyleAttr 风格 */ public CProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //获得默认颜色值 Resources res = getResources(); final int defaultSmallColor = res.getColor(R.color.material_red); final int defaultMiddleColor = res.getColor(R.color.material_green); final int defaultBigColor = res.getColor(R.color.material_blue); //默认速度,最外层为180°/s,默认圆弧长度,默认圆弧宽度 final int defaultCircleSpeed = 270; final float defaultSweepAngle = 90f; final float defaultStrokeWidth = 5f; if (attrs != null){ final TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.CProgressBar_Style); mLayerColors[0] = typedArray.getColor( R.styleable.CProgressBar_Style_WhorlView_SmallWhorlColor,defaultSmallColor); mLayerColors[1] = typedArray.getColor( R.styleable.CProgressBar_Style_WhorlView_MiddleWhorlColor,defaultMiddleColor); mLayerColors[2] = typedArray.getColor( R.styleable.CProgressBar_Style_WhorlView_BigWhorlColor,defaultBigColor); mCircleSpeed = typedArray.getInt( R.styleable.CProgressBar_Style_WhorlView_CircleSpeed,defaultCircleSpeed); int index = typedArray.getInt(R.styleable.CProgressBar_Style_WhorlView_Parallax,0); //设置各层的速度 setParallax(index); mSweepAngle = typedArray.getFloat( R.styleable.CProgressBar_Style_WhorlView_SweepAngle,defaultSweepAngle); //判断圆弧长度是否非法 if(mSweepAngle <= 0 || mSweepAngle >= 360) { throw new IllegalArgumentException("sweep angle out of bound"); } mStrokeWidth = typedArray.getFloat( R.styleable.CProgressBar_Style_WhorlView_StrokeWidth,defaultStrokeWidth); typedArray.recycle();//回收 }else { /**否则都设置成默认值,因为如果在Code中实例化一个View会调用第一个构造函数, * 如果在xml中定义会调用第二个构造函数,因此attrs不为空,则设置为attrs的指定值, * 否则为默认值 */ mLayerColors[0] = defaultSmallColor; mLayerColors[1] = defaultMiddleColor; mLayerColors[2] = defaultBigColor; mCircleSpeed = defaultCircleSpeed; mParallaxSpeed = PARALLAX_MEDIUM; mSweepAngle = defaultSweepAngle; mStrokeWidth = defaultStrokeWidth; } } private void setParallax(int index) { switch (index) { case FAST: mParallaxSpeed = PARALLAX_FAST; break; case MEDIUM: mParallaxSpeed = PARALLAX_MEDIUM; break; case SLOW: mParallaxSpeed = PARALLAX_SLOW; break; default: throw new IllegalStateException("no such parallax type"); } } /** * 重写onDraw方法 */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //需要绘制三个圆弧 for (int i = 0; i < CIRCLE_NUM; i++){ //绘制圆弧的起始角度,因为圆弧是动态的,因此起始角度需要动态随时间变化 float angle = (mCircleSpeed + mParallaxSpeed * (CIRCLE_NUM - i - 1)) * mCircleTime * 0.001f; drawArc(canvas, i, angle); } } private boolean mIsCircling = false; /** * 不断的进行绘制圆弧 * */ public void start(){ mIsCircling = true; new Thread(new Runnable() { @Override public void run() { mCircleTime = 0L; while (mIsCircling){ invalidateWrap(); mCircleTime = mCircleTime + REFRESH_DURATION; try { Thread.sleep(REFRESH_DURATION); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } public void stop(){ mIsCircling = false; mCircleTime = 0L; invalidateWrap(); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void invalidateWrap() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { postInvalidateOnAnimation(); } else { postInvalidate(); } } /** * canvas.drawArc * oval:圆弧所在的椭圆对象。 * startAngle:圆弧的起始角度。 * sweepAngle:圆弧的角度。 * useCenter:是否显示半径连线,true表示显示圆弧与圆心的半径连线,false表示不显示。 * paint:绘制时所使用的画笔。 */ private void drawArc(Canvas canvas, int index, float startAngle){ Paint paint = checkArcPaint(index); //最大圆是view的边界 RectF oval = checkRectF(calcuRadiusRatio(index));//一个矩形对象 canvas.drawArc(oval,startAngle,mSweepAngle,false,paint); } private Paint mArcPaint; private Paint checkArcPaint(int index){ if (mArcPaint == null){ mArcPaint = new Paint(); }else { mArcPaint.reset(); } mArcPaint.setColor(mLayerColors[index]); mArcPaint.setStyle(Paint.Style.STROKE); mArcPaint.setStrokeWidth(mStrokeWidth); mArcPaint.setAntiAlias(true);//设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。 return mArcPaint; } private RectF mOval;//圆弧所在的椭圆对象 private RectF checkRectF(float radiusRatio){ if (mOval == null){ mOval = new RectF(); } float start = getMinLength() * 0.5f * (1 - radiusRatio) + mStrokeWidth; float end = getMinLength() - start; mOval.set(start,start,end,end);//Set the rectangle's coordinates to the specified values. return mOval; } private int getMinLength(){ return Math.min(getWidth(),getHeight()); } private static final float RADIUS_RATIO_P = 0.2f; /** * 计算每一圈的半径比例 * * @param index * @return */ private float calcuRadiusRatio(int index){ return 1f - (CIRCLE_NUM - index - 1) * RADIUS_RATIO_P; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int minSize = (int) (mStrokeWidth * 4 * CIRCLE_NUM); int wantSize = (int) (mStrokeWidth * 8 * CIRCLE_NUM); int size = measureSize(widthMeasureSpec,wantSize,minSize); //调用 setMeasuredDimension(int,int)来存储这个View经过测量得到的measured width and height。 setMeasuredDimension(size,size); } /** * 测量view的宽高 * * @param measureSpec 每一个MeasureSpec代表了对宽度或者高度的一个要求。每一个MeasureSpec * 有一个尺寸(size)和一个模式(mode)构成。MeasureSpecs这个类提供了把一个<size, * mode>的元组包装进一个int型的方法,从而减少对象分配。当然也提供了逆向的解析方法, * 从int值中解出size和mode。 * @param wantSize * @param minSize * @return */ public static int measureSize(int measureSpec, int wantSize, int minSize){ int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY){ result = specSize;//parent指定的边界 }else { result = wantSize;//自己想要的边界 if (specMode == MeasureSpec.AT_MOST){ // wrap_content result = Math.min(result,specSize); } } //测量的尺寸和最小尺寸取大 return Math.max(result, minSize); }}
4.将自定义的控件类定义到布局用的xml文件中去.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <com.duke.chenyouhui.customprograssbar.CProgressBar android:id="@+id/cprogress" android:layout_width="wrap_content" android:layout_height="wrap_content" app:WhorlView_SweepAngle = "10" app:WhorlView_Parallax = "fast"/></RelativeLayout>
注意一点就是xmlns:app="http://schemas.android.com/apk/res-auto"这是自定义控件的命名空间,必须加上
更多相关文章
- 【Android】百度地图自定义弹出窗口
- (Android小应用)电话监听器
- Android(安卓)Fragment生命周期——多屏幕支持
- Android解析自定义标签
- 自定义View之继承View(圆形进度图,播放器条形图)
- android 自定义视图控件开发
- Android(安卓)自定义适配器逐步优化
- Android引入外部自定义特殊字体的方法
- Android解决自定义View获取不到焦点的情况