本篇主要記錄我學習自定義view時的一個demo。初步完成效果如下:
音量控制效果圖
-
首先,在attr中定義好需要在xml中定義的屬性:
<attr name="firstColor" format="color"/>
<attr name="secondColor" format="color"/>
<attr name="background" format="reference"/>
<attr name="circleWidth" format="dimension"/>
<attr name="dotCount" format="integer"/>
<attr name="splitSize" format="integer"/>
<declare-styleable name="MyVoiceControlView">
<attr name="firstColor"/>
<attr name="secondColor"/>
<attr name="background"/>
<attr name="circleWidth"/>
<attr name="dotCount"/>
<attr name="splitSize"/>
</declare-styleable>
######為什么要把屬性抽出來在外面寫? 我也是和大神學的,這樣的寫法可以類比成員變量的用法。
******
* ####然后,創建一個View的子類,繼承它的構造方法,并在構造方法中獲取xml中定義的屬性值:
一般使用三參構造器,如果需要使用單參的,可以通過this調三參的方法來實現。
<pre>
public MyVoiceControlView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyVoiceControlView, 0, defStyleAttr);
for (int i = 0; i < typedArray.getIndexCount(); i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.MyVoiceControlView_firstColor:
mFirstColor = typedArray.getColor(attr, Color.BLACK);//默認顏色、底色
break;
case R.styleable.MyVoiceControlView_secondColor:
mSecondColor = typedArray.getColor(attr, Color.WHITE);//選中后的顏色
break;
case R.styleable.MyVoiceControlView_background://中心的喇叭圖片,等下會繪制出來
mImage = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(attr, 0));
break;
case R.styleable.MyVoiceControlView_dotCount:
mDotCount = typedArray.getInteger(attr, 10);//點的數量
break;
case R.styleable.MyVoiceControlView_circleWidth:
mCircleWidth = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_PX, 16, getResources().getDisplayMetrics()
));//這里是將xml中定義的dimension類型轉化為可以繪制的px屬性,默認設置16
break;
case R.styleable.MyVoiceControlView_splitSize:
mSplitSize = typedArray.getInteger(attr, 20);//這個屬性是 點之間間隙的大小,默認20
break;
}
}
typedArray.recycle();
mPaint = new Paint();
mPaint.setAntiAlias(true);//抗鋸齒
mRect = new Rect();
}
</pre>
* ####為簡單起見,就直接onDraw()了,這里略掉onMeasure()步驟,代碼中View的尺寸寫死就行
- onDraw很簡單,將中心圖片畫出來后,再用for循環畫出外圈的點(其實是strokeCap為Round的弧線實現的)
<pre>
mPaint.setStrokeWidth(mCircleWidth);
mPaint.setStrokeCap(Paint.Cap.ROUND);//線段的斷點形狀為round
mPaint.setStyle(Paint.Style.STROKE);
int center = getWidth() / 2;
//根據需要畫的個數以及間隙計算每個塊塊所占的比例*240,不畫整圓了這里
float itemSize = (240 * 1.0f - (mDotCount - 1) * mSplitSize) / mDotCount;
// 用于定義的圓弧的形狀和大小的界限的rectf
RectF oval = new RectF(mCircleWidth / 2, mCircleWidth / 2,
getWidth() - mCircleWidth / 2, getWidth() - mCircleWidth / 2);
mPaint.setColor(mFirstColor); // 設置圓環的顏色
for (int i = 0; i < mDotCount; i++) {
canvas.drawArc(oval, i * (itemSize + mSplitSize) + 150, itemSize, false, mPaint); // 根據進度畫圓弧
}
mPaint.setColor(mSecondColor); // 設置圓環的顏色
// 根據進度繪制圓弧
for (int i = 0; i < mCurrentCount; i++) {
canvas.drawArc(oval, i * (itemSize + mSplitSize) + 150, itemSize, false, mPaint);
}
int r = center - mCircleWidth;
mRect.left = (int) ((1 - Math.sqrt(2) / 2) * r + mCircleWidth);
mRect.top = (int) ((1 - Math.sqrt(2) / 2) * r + mCircleWidth);
mRect.right = (int) ((1 + Math.sqrt(2) / 2) * r + mCircleWidth);
mRect.bottom = (int) ((1 + Math.sqrt(2) / 2) * r + mCircleWidth);
//如果圖片的尺寸過小,無法與外圓相交,就使用它本來的尺寸
if (mImage.getWidth() < Math.sqrt(2) * r) {
mRect.left = r + mCircleWidth - mImage.getWidth() / 2;
mRect.right = mRect.left + mImage.getWidth();
mRect.top = r + mCircleWidth - mImage.getWidth() / 2;
mRect.bottom = mRect.top + mImage.getWidth();
}
canvas.drawBitmap(mImage, null, mRect, mPaint);
</pre>
這時,繪制出來的效果基本可以展示了
UI界面的xml中調用這個view:(注意先聲明命名空間)
<pre>
<com.ec.vone.view.MyVoiceControlView
android:layout_width="100dp"
android:layout_height="100dp"
android:visibility="visible"
custom:background="@drawable/dududu"
custom:circleWidth="10dp"
custom:dotCount="10"
custom:firstColor="@color/color_bg_1"
custom:secondColor="@color/current"
custom:splitSize="20"
android:id="@+id/myVoiceControlView" />
</pre>
中心的圖片是as中生成的,不用去煩你的美工MM或者摳半天圖了。

* 到這里,可以直接在這個類中添加set和get方法,動態設置就可以收工了,如果想實現一點交互,就可以重寫onTouchEvent():
<code>
@Override
public boolean onTouchEvent(MotionEvent event) {
//用來判斷滑動距離是這個距離的多少倍。 乘3只是為了達到更好的控制效果,這個自定義即可
float moveLength = getHeight() / (mDotCount *3 );
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
xLast = (int) event.getX();
yLast = (int) event.getY();
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_MOVE:
//x軸方向move事件
int xCurrent = (int) event.getX() - xLast;
int moveCountX = (int) (xCurrent / moveLength);
if (xCurrent > 0) {//——→滑
if (moveCountX > 1) {
up(); //自定義的方法
xLast = (int) event.getX();
}
} else if (xCurrent < 0) {//下滑
if (moveCountX < -1) {
down();//自定義的方法
xLast = (int) event.getX();
}
}
//y軸方向move事件
int yCurrent = (int) (event.getY() - yLast);
int moveCountY = (int) (yCurrent / moveLength);
if (yCurrent > 0) {//↓滑
if (moveCountY > 1) {
down();
yLast = (int) event.getY();
}
} else if (yCurrent < -1) {
if (yCurrent < -1) {
up();
yLast = (int) event.getY();
}
}
break;
}
return true;
}
</code>
up() 和 down()方法
<pre>
public void up() {
if (mCurrentCount < mDotCount)
mCurrentCount++;
postInvalidate();
}
public void down() {
if (mCurrentCount >= 1)
mCurrentCount--;
postInvalidate();
}
</pre>
這里我設置的是向→ 或者 ↑ 都是增加音量,反之減小。
這些也是自己照著大神的demo練習的,有很多地方不一定適用,大伙看看就行,歡迎指正。
[具體代碼點擊這里查看](https://github.com/TrueHub/SomeView)