Android—自定義控件

自定義控件的步驟:

1.自定義屬性的聲明和獲取

  • 分析需要的自定義屬性
  • 在res/valus/attrs.xml定義聲明
  • 在layout.xml文件中進行使用
  • 在View的構造方法中進行獲取

2.測量onMeasure

3.繪製onDraw

4.狀態的存儲和恢復(考慮在activity重建之後要存儲和恢復的)

主要是通過onSaveInstanceState()(實現存儲)和onRestoreInstanceState()方法來實現


 在res/values/attrs.xml定義聲明 

 

 在layout.xml文件中進行使用

 

 

在View的構造方法中進行獲取

 自定義一個TestView的類繼承View

public class TestView extends View {
    private static final String TAG = "TestView";
    private Paint mPaint;
    public TestView(Context context) {
        super(context);
    }

    //如果想要view在佈局xml
    // 文件中使用,一定要重寫這個構造方法
    public TestView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
        TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.TestView);

        ta.getBoolean(R.styleable.TestView_test_boolean,false);
        boolean booleanTest=ta.getBoolean(R.styleable.TestView_test_boolean,false);
        int IntegerTest= ta.getInteger(R.styleable.TestView_test_integer,-1);
        float dimensionTest=ta.getDimension(R.styleable.TestView_test_dimension,0);
        String stringTest=ta.getString(R.styleable.TestView_test_string);//getString方法不需要默認值
        int enumTest=ta.getInteger(R.styleable.TestView_test_emun,1);
        ta.recycle();//進行回收

        Log.e(TAG,booleanTest+","+IntegerTest+","+dimensionTest+","+stringTest+","+enumTest);

    }
}

 


測量onMeasure

重寫自定義view的構造方法:

自定義控件模式:

MeasureSpec.EXACTLY 精確模式,尺寸的值是多少,那麼控件的長和寬就是多少
MeasureSpec.ATMOST 最大模,同時父控件給出一個最大空間,不能超過這個值
MeasureSpec.UNSPECIFIED 未指定模式,當前組件,可得到的空間不受限制

用於測量的代碼:

package com.example.animation.frame;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import com.example.animation.R;

public class TestView extends View {
    private static final String TAG = "TestView";
    public TestView(Context context) {
        super(context);
    }

    //如果想要view在佈局xml
    // 文件中使用,一定要重寫這個構造方法
    public TestView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.TestView);

        ta.getBoolean(R.styleable.TestView_test_boolean,false);
        boolean booleanTest=ta.getBoolean(R.styleable.TestView_test_boolean,false);
        int IntegerTest= ta.getInteger(R.styleable.TestView_test_integer,-1);
        float dimensionTest=ta.getDimension(R.styleable.TestView_test_dimension,0);
        String stringTest=ta.getString(R.styleable.TestView_test_string);//getString方法不需要默認值
        int enumTest=ta.getInteger(R.styleable.TestView_test_emun,1);
        ta.recycle();//進行回收

        Log.e(TAG,booleanTest+","+IntegerTest+","+dimensionTest+","+stringTest+","+enumTest);

    }

    public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //獲得父控件傳入的值
        int widthMode=MeasureSpec.getMode(widthMeasureSpec);
        int widthSize=MeasureSpec.getSize(widthMeasureSpec);

        int width=0;
        if(widthMode==MeasureSpec.EXACTLY)
        {
            width=widthSize;
        }else{
            int needWidth=measureWidth()+getPaddingLeft()+getPaddingRight();
            if(widthMode==MeasureSpec.AT_MOST)//needWidth不能超過父控件傳入的值
            {
                width=Math.min(needWidth,widthSize);
            }else{
                width=needWidth;//測量有多大就有多大
            }

        }
        int heightMode=MeasureSpec.getMode(heightMeasureSpec);
        int heightSize=MeasureSpec.getSize(heightMeasureSpec);
        int height=0;

        if(heightMode==MeasureSpec.EXACTLY)
        {
            height=heightSize;

        }else{//自己測量
            int needHeight=measureHeight()+getPaddingTop()+getPaddingBottom();
            if(heightMode==MeasureSpec.AT_MOST)
            {
                height=Math.min(needHeight,heightSize);

            }else{
                height=needHeight;
            }

        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(width,height);
    }
    private int measureHeight()
    {
        return 0;
    }
    private int measureWidth()
    {

        return 0;

    }
}

繪製onDraw:

canvas來繪製形狀,Paint來控制畫筆

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setStrokeWidth(1);
        canvas.drawCircle(getWidth()/2,getHeight()/2,getWidth()/2,mPaint);//⚪

        canvas.drawLine(0,getHeight()/2,getWidth(),getHeight()/2,mPaint);//橫向的線
        canvas.drawLine(getWidth()/2,0,getWidth(),getHeight()/2,mPaint);//
    }

    private void initPaint()//初始化畫筆
    {
        mPaint=new Paint();
        mPaint.setStyle(Paint.Style.STROKE);//畫一個空心⚪、
        mPaint.setStrokeWidth(6);
        mPaint.setColor(0xFFFF0000);
        mPaint.setAntiAlias(true);

    }

狀態的存儲和恢復:

注意一定要給要恢復的控件加上id啊!(不然無法恢復)

保存和恢復的時候,需要注意父控件也要保存和恢復:

private static  final String INSTANCE="instance";
    private static  final String KEY_TEXT="key_text";
    @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle=new Bundle();
        bundle.putString(KEY_TEXT,mText);
        bundle.putParcelable(INSTANCE,super.onSaveInstanceState());
        return super.onSaveInstanceState();


    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if(state instanceof Bundle)
        {
            //先恢復父控件的
            Bundle bundle=(Bundle)state;
            Parcelable parcelable=bundle.getParcelable(INSTANCE);
            super.onRestoreInstanceState(parcelable);
            //再恢復自己的
            mText=bundle.getString(KEY_TEXT);
            return;
        }
        super.onRestoreInstanceState(state);
    }