【Android 進(jìn)階之路】自定義View控件

  • 時(shí)間:2016年12月25日
  • 設(shè)備:Android 7.0 小米4
  • 需求:自定義一個(gè)齒輪
  • 地點(diǎn):安師大秋實(shí)園

本博客內(nèi)容一致同步到本人的博客站點(diǎn):http://www.zhoutaotao.xyz 歡迎訪問(wèn)留言交流

前言

+盡管安卓SDK提供了非常豐富的控件供大家使用,但是在一些特殊的場(chǎng)合,仍然不能夠滿足需求,所以我們要學(xué)會(huì)自定義符合自己的需求,在此學(xué)習(xí)了一些簡(jiǎn)單的心得,代碼比較簡(jiǎn)單,分享一下自己的心得,并記錄學(xué)習(xí)過(guò)程。由于是新手,如果錯(cuò)誤的理解,多謝指正,我會(huì)立即改正,以免誤導(dǎo)他人。謝謝!

預(yù)期效果

簡(jiǎn)單的才靜態(tài)齒輪效果,可調(diào)節(jié)顏色,齒輪半徑。先做個(gè)靜態(tài)的,后面學(xué)習(xí)扎實(shí)了,再做個(gè)動(dòng)態(tài)的齒輪,效果圖如下:

QQ拼音截圖未命名.png

由于我是初次接觸自定義View組件,在記錄的過(guò)程中,難免有誤解出現(xiàn),敬請(qǐng)告知和諒解。

步驟

一、 設(shè)置屬性內(nèi)容
二、定義類并繼承View
三、編寫相應(yīng)的方法
四、在布局中使用

正文

  • 廢話不多說(shuō),開始主題

設(shè)置屬性內(nèi)容

1、在資源文件目錄value中新建資源文件attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--齒輪顏色顏色-->
    <attr name="grarColor" format="color"></attr>
    <!--齒輪半徑 單位:dp-->
    <attr name="gearRadius" format="dimension"></attr>
    <!--這是聲明屬性 name的值要和自定義View的java類名一致,后面就會(huì)看到-->
    <!--把上面的屬性給復(fù)制下來(lái),移除format屬性-->
    <declare-styleable name="Gear">
        <!--齒輪顏色顏色-->
        <attr name="grarColor"></attr>
        <!--齒輪半徑 單位:dp-->
        <attr name="gearRadius"></attr>
    </declare-styleable>
    </resources>

這里的name比較重要,并非隨意的命名,需要和其java文件相匹配,后面會(huì)用到,里面有三個(gè)屬性,很簡(jiǎn)單的聲明。

定義類并繼承View

  • 新建Java文件Gear.java,文件名要和第一步的name一致,否則不能使用的
  • 其構(gòu)造函數(shù)有四個(gè),由于剛接觸,沒有深入研究,這里就不說(shuō)區(qū)別了,一般我們使用兩個(gè)參數(shù)的那一個(gè)
public class Gear extends View {
    public Gear(Context context) {
        super(context);
    }
    public Gear(Context context, AttributeSet attrs) {
        super(context, attrs);
      //我們一般使用這一個(gè),當(dāng)然也可以使用第三個(gè)構(gòu)造函數(shù)
    }
    public Gear(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    public Gear(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
}
  • 構(gòu)造完成之后,需要獲取在xml布局文件傳入的值

//聲明全局變量,保存屬性參數(shù)
private int gearColor;
private float gearRadius;
private float gearLength;
//畫筆
private Paint paint;

public Gear(Context context, AttributeSet attrs) {
    super(context, attrs);
    //獲取屬性集合
    TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.Gear);
    //為全局變量賦值
    //獲取顏色,第一位參數(shù)屬性,第二個(gè)是默認(rèn)值,注意是8位的16進(jìn)制數(shù),前兩位為不透明度,要寫上,不可省略
    gearColor=typedArray.getColor(R.styleable.Gear_grarColor,0xFF000000);
    //獲取半徑,后面的為默認(rèn)值,由于傳入的數(shù)據(jù)單位是dp,需要轉(zhuǎn)化為px
    gearRadius=typedArray.getDimension(R.styleable.Gear_gearRadius,dp2px(0));
    //從屬性集中獲取屬性完成之后一定要回收
    typedArray.recycle();
    //初始化畫筆
    paint=new Paint();
    //設(shè)置畫筆的寬度
    paint.setStrokeWidth(dp2px(4));
}

public float dp2px(int dpValue){
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpValue,getResources().getDisplayMetrics());
}

編寫相應(yīng)的方法

  • 測(cè)量的方法onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width=getRealValue(widthMeasureSpec);
//由于布局中的寬度存在三種類型,精確的數(shù)據(jù),包含,和占用父布局,所以我們需要處理數(shù)據(jù)
    int height=getRealValue(heightMeasureSpec);
//計(jì)算完成后,調(diào)用此方法,傳入實(shí)際的需要寬度和高度
    setMeasuredDimension(width,height); 
}
public int getRealValue(int value){
    int mode=MeasureSpec.getMode(value);//得到數(shù)據(jù)的模式,模式占用32位數(shù)據(jù)的前兩位
    int size=MeasureSpec.getSize(value);//得到系統(tǒng)給的數(shù)值
    int result=0;
    if (mode==MeasureSpec.EXACTLY)    {//如果是精準(zhǔn)的數(shù)值的話,就使用系統(tǒng)的值
        result=size;
    }else    { 
       result= (int) paint.descent();
//如果比包含的話,使用畫筆的數(shù)據(jù),也就是包含了,這之前一定要調(diào)用paint.setStrokeWidth(float width)
        if (mode==MeasureSpec.AT_MOST)//占用父布局的話
        { 
           result=size; 
       }
    }
    return result;
}
  • 測(cè)量的方法onMeasure()

當(dāng)然onDraw()之前還有一個(gè)方法

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
}
//onLayout方法是ViewGroup中子View的布局方法,用于放置子View的位置。
//放置子View很簡(jiǎn)單,只需在重寫onLayout方法,然后獲取子View的實(shí)例
//調(diào)用子View的layout方法實(shí)現(xiàn)布局。
//在實(shí)際開發(fā)中,一般要配合onMeasure測(cè)量方法一起使用。
//由于這個(gè)Demo很簡(jiǎn)單,Layout就不做處理了,有興趣的自行研究學(xué)習(xí)。

+下面看重點(diǎn)onDraw()方法,我們進(jìn)行繪制,首先使用數(shù)學(xué)工具分析我們的需求:

QQ拼音截圖未命名.png
  • 先畫大圓,半徑為R,圓心位置為中心
  • 在畫小圓,半徑為R/3,圓心位置為中心
  • 在畫圓心,并且連接齒輪上的某一點(diǎn)
  • 其次畫齒輪,圓周分60份,要認(rèn)識(shí)到在特殊的點(diǎn),比如0,5,10,15,20,25,30的長(zhǎng)度略高于其他長(zhǎng)度
  • 齒輪本質(zhì)為線,只要確定起點(diǎn)和終點(diǎn)即可,我們嘗試著來(lái)計(jì)算。
  • 起點(diǎn):(x+RSin a,y+Rcos a)
  • 終點(diǎn) : (x+ (R+l)sin a,y+(R+l)cos a) 其中l(wèi)為齒輪的長(zhǎng)度,在特殊的點(diǎn)略大點(diǎn)

下面看代碼

@Overrideprotected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //圓心位置
    int circle_x = getWidth() / 2;
    int circle_y = getHeight() / 2;
    //設(shè)置畫筆顏色
    paint.setColor(gearColor);
    //設(shè)置畫筆為繪制邊框
    paint.setStyle(Paint.Style.STROKE);
    //設(shè)置線寬
    paint.setStrokeWidth(dp2px(4));
    //繪制大圓形
    canvas.drawCircle(circle_x, circle_y, dp2px((int) gearRadius), paint);
    //繪制小圓,半徑為大圓形的1/3
    canvas.drawCircle(circle_x, circle_y, dp2px((int) gearRadius) / 3, paint);
    //繪制圓心點(diǎn)
    canvas.drawPoint(circle_x, circle_y, paint);
    //繪制齒輪
    //1/60刻度的弧度值
    float kedu = (float) (2 * Math.PI / 60);
    paint.setStrokeWidth(dp2px(3));
    for (int i = 0; i < 60; i++) {
        int len = 18;
        if (i % 5 == 0)
            len = 30;
        float start_X = (float) (circle_x + dp2px((int) gearRadius) * Math.sin(kedu * i));
        float start_Y = (float) (circle_y + dp2px((int) gearRadius) * Math.cos(kedu * i));
        float end_X = (float) (circle_x + (dp2px((int) gearRadius) + len) * Math.sin(kedu * i));
        float end_Y = (float) (circle_y + (dp2px((int) gearRadius) + len) * Math.cos(kedu * i));
        canvas.drawLine(start_X, start_Y, end_X, end_Y, paint);
        if (i==6)
            canvas.drawLine(circle_x,circle_y,start_X,start_Y,paint);
    }
}

在布局中使用

  • 很簡(jiǎn)單的使用方法,需要在根布局中設(shè)置一個(gè)屬性,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<!--在布局中加入    xmlns:tools="http://schemas.android.com/apk/res-auto"-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <cn.app.hybord.tao.myapplication.Gear
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:gearRadius="50dp"
        tools:grarColor="#FFFF00FF"
        />
</RelativeLayout>
  • 效果圖
QQ拼音截圖未命名.png

附錄:動(dòng)態(tài)設(shè)置顏色和半徑

  • 思路:在自定義View中寫入公有的方法,在代碼中綁定后調(diào)用,然后調(diào)用相應(yīng)的設(shè)置方法進(jìn)行重繪
  • 布局文件:
<?xml version="1.0" encoding="utf-8"?>
<!--在布局中加入    xmlns:tools="http://schemas.android.com/apk/res-auto"-->
<LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android" 
   xmlns:tools="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:padding="15dp"
    android:layout_height="match_parent">
    <EditText
        android:id="@+id/edit_raduis"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:hint="請(qǐng)輸入半徑長(zhǎng)度"
        />
    <Button
        android:id="@+id/btn_draw"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:hint="重新繪制"
                />
    <cn.app.hybord.tao.myapplication.Gear
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:gearRadius="50dp"
        tools:grarColor="#FFFF00FF"
        />
</LinearLayout>
  • Gear.java中的方法
public void setGearColor(int gearColor) {
    //重新賦值
    this.gearColor = gearColor;
    //重繪
    invalidate();
    //備注:子線程重繪
   // postInvalidate();
}
public void setGearRadius(float gearRadius) {
    //如上注釋
    this.gearRadius = gearRadius;
    invalidate();
}
  • Activity中的代碼文件
public class MainActivity extends AppCompatActivity {
    private EditText radius;
    private Button reDraw;
    private Gear gear;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        radius= (EditText) findViewById(R.id.edit_raduis);
        reDraw= (Button) findViewById(R.id.btn_draw);
        gear= (Gear) findViewById(R.id.gear);
        reDraw.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
            //僅僅寫了修改半徑,如果修改其他的類似的方法
                gear.setGearRadius(Float.parseFloat(radius.getText().toString().trim()));
            }
        });
    }
}

效果

默認(rèn)半徑【也就是xml布局中的文件設(shè)置的半徑】

morenbanjiang.png

Radius=20

20.png

Radius=150

150.png

本博客內(nèi)容一致同步到本人的博客站點(diǎn):http://www.zhoutaotao.xyz 歡迎訪問(wèn)留言交流

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容