- 時(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)他人。謝謝!
- 這個(gè)需求是看了 http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0606/3001.html
這篇文章之后覺得不錯(cuò),自己整理思路,不過(guò)好像方法不同,以后再做慢慢研究吧,有興趣的可以看看。
預(yù)期效果
簡(jiǎn)單的才靜態(tài)齒輪效果,可調(diào)節(jié)顏色,齒輪半徑。先做個(gè)靜態(tài)的,后面學(xué)習(xí)扎實(shí)了,再做個(gè)動(dòng)態(tài)的齒輪,效果圖如下:

由于我是初次接觸自定義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é)工具分析我們的需求:

- 先畫大圓,半徑為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>
- 效果圖

附錄:動(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è)置的半徑】

Radius=20

Radius=150

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