Android AttributeSetカスタムコントロールの使用

21166 ワード

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"

これはカスタムコントロールのネーミングスペースです.