当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"

这是自定义控件的命名空间,必须加上


更多相关文章

  1. 【Android】百度地图自定义弹出窗口
  2. (Android小应用)电话监听器
  3. Android(安卓)Fragment生命周期——多屏幕支持
  4. Android解析自定义标签
  5. 自定义View之继承View(圆形进度图,播放器条形图)
  6. android 自定义视图控件开发
  7. Android(安卓)自定义适配器逐步优化
  8. Android引入外部自定义特殊字体的方法
  9. Android解决自定义View获取不到焦点的情况

随机推荐

  1. android 程序调试入门
  2. android TabLayout Indicator 圆角
  3. android PopupWindow
  4. UI设计师的 Android 备忘录
  5. (转)Working With Android Contacts
  6. android progressBar 背景改变
  7. Android 蝈蝈
  8. android需要看的书
  9. 编译android 源码
  10. Android 一个简单的登录界面