我的第一篇自定義view--Menu選單
一、前言
第一次寫部落格,不知道什麼姿勢才能顯示出一副好像很老練的樣子。老大讓寫一個選單欄控制元件,借鑑了Idtk自定義view,站在巨人的肩上思路是豁然開朗。
github原始碼地址:https://github.com/qdxxxx/MenuChart
歡迎star~
二、效果圖
三、使用方式
MenuChart menuChart = (MenuChart) findViewById(R.id.menuChart);
menuChart.setPieData(mPieDatas); //初始資料
menuChart.setStartAngle(180); //設定起始角度
menuChart.setPieShowAngle (180); //設定總共顯示的角度
menuChart.setCenterBitmap(R.mipmap.menu, PieUtils.dp2px(MainActivity.this, 60), PieUtils.dp2px(MainActivity.this, 60));//設定中心bmp
......
四、開始繪製(分2種情況,觸控|未觸控)
①:未觸控顯示解刨
展示的圖形分為4個模組,標籤層rectFLabl、繪製bmp的rectF、白金層rectFGold(因為不會拼寫銀這個單詞白金高大上啊),對應不同的半徑如上圖所示。程式碼中rectF 表示未觸控層,rectFF表示觸控彈出層,下面會講解。
②:繪製
int layerID=canvas.saveLayer(rectFLabel, mPaint, Canvas.ALL_SAVE_FLAG);//拿出新的畫紙來繪畫
mXPaint.setColor(pie.getLabelColor());
canvas.drawArc(rectFLabel, currentStartAngle, sweepAngle, true, mXPaint); //繪製標籤層
mXPaint.setColor(Color.WHITE);
canvas.drawArc(rectF, currentStartAngle, sweepAngle, true, mXPaint); //繪製bmp層
mXPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawArc(rectFGold, currentStartAngle - PIE_SPACING, sweepAngle + PIE_SPACING * 2, true, mXPaint); //先把白金區域 擦拭乾淨。再繪製
mXPaint.setXfermode(null);
mXPaint.setColor(goldColor);
canvas.drawArc(rectFGold, currentStartAngle, sweepAngle, true, mXPaint); //白金層
mXPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawArc(rectFIn, -1f, PIE_VIEW_ANGLE + 2f, true, mXPaint); //透明層
mXPaint.setXfermode(null);
canvas.restoreToCount(layerID);
③:觸控touch顯示解刨
與觸控層有點類似,標籤層改為陰影部分,原本繪製bmp區域現在繪製文字。不過所有的半徑變大。繪製的方法和上面的神似,這裡就不舉慄說出了。
④:開始繪製文字
繪製文字和bmp首先要先複習一下數學公式再此我希望我的數學老師們都把學費還給我,畢竟我知識也還給你們了啊,
上面公式中,2*PI/360*angle,即求angle的弧度。
Math.sin(x) x 的正玄值。返回值在 -1.0 到 1.0 之間;
Math.cos(x) x 的餘弦值。返回的是 -1.0 到 1.0 之間的數;這兩個函式中的X 都是指的“弧度”而非“角度”,而我們可以用
我們先繪製文字,如上圖的9個文字。“不辣,川菜一點都不辣”
繪製文字和繪製模組RECTF有一點區別,模組是一個角度一個角度繪製,為了使用者體驗。但是繪製文字選擇的是根據animatedValue(從0到最大顯示角度所返回的animation.getAnimatedValue值) 的角度去處理不同區域的文字。
舉個栗子:如果當前有3個模組共180°,那麼animatedValue==60°的時候我們繪製第一個模組的文字,animatedValue<120度的時候我們還是繪製第一個模組的文字。
int pivotX; //中心點X
int pivotY; //中心點Y
//為了驗證文字是否畫在中心點,可以畫兩條基線判斷一下
canvas.drawLine(pivotX - mWidth / 2, pivotY, pivotX + mWidth / 2, pivotY, paint);
canvas.drawLine(pivotX, pivotY - mHeight / 2, pivotX, pivotY + mHeight / 2, paint);
pivotX = (int) (Math.cos(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (rF + rGlodF) / 2); //獲取當前模組中心點x,y軸座標
pivotY = (int) (Math.sin(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (rF + rGlodF) / 2);
paint.setTextAlign(Paint.Align.CENTER);
FontMetrics fontMetrics = paint.getFontMetrics();
float total = -fontMetrics.ascent + fontMetrics.descent;
float yAxis = total / 2 - fontMetrics.descent;
canvas.drawText(string, pivotX, pivotY + yAxis, paint);
⑤:繪製bitmap
繪製之前首先我們要得到bitmap最大width/height,再獲取bitmap中心點。獲取中心點上面的栗子已經有說明了,那麼我們現在著重獲取bitmap的最大寬高。然後得到兩者的較小值即可設定為bitmap區域。
靈魂畫家。。。
double centerR = (r + rGold) / 2; //獲取中心點半徑
double maxH = r - rGold; //最大高
//根據該區域的角度/2 獲取的rY , 再求出內切的正方形邊長,正方形邊長=√2*rY
double maxW = Math.sin(Math.toRadians(angle / 2)) * centerR * Math.sqrt(2);
maxW = maxW < 1 ? maxH : maxW; //防止cos90° 取值極其小
maxH = maxH < 1 ? maxW : maxH;
⑥:繪製原點旋轉的bitmap
我們將bitmap挪動到螢幕中心點,即bitmap中心點==繪製中心點。再根據當前animatedValue的角度進行旋轉。瞭解更多的matrix
bmpMatrix.reset();
bmpMatrix.preTranslate(-bmpCenter.getWidth() / 2, -bmpCenter.getHeight() / 2); //將bitmap移動中心點(原本中心點位於bitmap坐上角(0,0))
bmpMatrix.preRotate(360 / PIE_VIEW_ANGLE * animatedValue * 2, bmpCenter.getWidth() / 2, bmpCenter.getHeight() / 2);//以bitmap中心點為中心 閉著眼 旋轉 跳躍
canvas.drawBitmap(bmpCenter, bmpMatrix, mPaint);
五、OnTouch處理
首先,我們繪圖onDraw()的時候將畫板canvas移動到中心點,所以我們這樣處理座標點
float x = event.getX() - (mWidth / 2);
float y = event.getY()- (mHeight / 2);
第二步我們要求出觸控的角度,因為android裡面座標系與我們數學上的不同,android由第4 - 3 - 2 -1 象限開始繪製角度。
獲取角度的方法:
但是當x<0的時候,,求出的角度實為 [0°,-90°) ,此時我們給它掰正。當x<0,角度就+180°。
求出觸控的角度之後我們再算出觸控的半徑,這樣子就可以判斷當前是否觸控到了我們所繪製的各個模組上。
float x = eventPivotX - (mWidth / 2);
float y = eventPivotY - (mHeight / 2);
float touchAngle = 0; //Android 繪圖座標和我們的數學認知不同,安卓由第4 - 3 - 2 -1 象限繪製角度
if (x < 0) { //x<0 的時候 ,求出的角度實為 [0°,-90°) ,此時我們給它掰正
touchAngle += 180;
}
touchAngle += Math.toDegrees(Math.atan(y / x));
touchAngle = touchAngle - startAngle;
if (touchAngle < 0) {
touchAngle = touchAngle + 360;
}
float touchRadius = (float) Math.sqrt(y * y + x * x);//求出觸控的半徑
//我們用touchSeleteId來表示當前選中的模組
if (rIn < touchRadius && touchRadius < rLabel) {//觸控的範圍在繪製的區域
if (-Arrays.binarySearch(pieAngles, touchAngle) - 1 == touchSeleteId)
{ //如果模組有變動(手指挪到其他模組)再繪製
return true; //如果觸控的地方是已經展開的,那麼就不再重複繪製
}
touchSeleteId = -Arrays.binarySearch(pieAngles, touchAngle) - 1;
invalidate();
} else if (0 < touchRadius && touchRadius < rIn) { //觸控的範圍在原點bmp範圍
if (animatedValue == PIE_VIEW_ANGLE && event.getAction() == MotionEvent.ACTION_DOWN)
{ //如果已經全展開
showEndAnim();
}
}
另外,老大對onTouch觸控的時候有個提議,那就是觸控中心bmp選單之後不鬆手(此時animator顯示),然後滑動螢幕選擇對應的模組後鬆手,一氣呵成。也就是一個ACTION_DOWN和一個ACTION_UP,中間N個ACTION_MOVE,這沒關係,有需求我們就去做是吧。
沒關係,我們在當前Activity做一下處理,在move和up事件中不處理,傳給我們自定義view,傳入的event.getRawX() - pieLocation[0]就可以視為是在我們MenuChart裡面對應的座標點了。[可以試一下點選小二,來份選單btn的時候手指不鬆開移動到展示開的模組。]
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
menuChart.getLocationOnScreen(pieLocation);
menuChart.onPieTouchEvent(event, event.getRawX() - pieLocation[0], event.getRawY() - pieLocation[1]);
return true;
六、程式碼
至此繪製Rect,文字,bitmap,onTouch已經講解了,下面po出程式碼。
public class MenuChart extends View {
public static final String TAG = "QDX";
//預設的寬高都為螢幕寬度/高度
private int DEFAULT_HEIGHT;
private int DEFAULT_WIDTH;
//寬高
private int mWidth;
private int mHeight;
//資料
private ArrayList<PieData> mPieData = new ArrayList<>();
// 每塊模組的 間隔 以下簡稱餅狀影象為模組
private float PIE_SPACING = 2f;
//整個view展示的角度, 360為圓
private int PIE_VIEW_ANGLE = 360;
/**
* animator 動畫時候繪製的角度,最大為 PIE_VIEW_ANGLE
*/
private float animatedValue;
//繪製模組 以及 彈出模組上的中心文字
private Paint mPaint = new Paint();
//畫在邊緣上的 縮寫
private Paint mLabelPaint = new Paint();
//XferMode御用的筆
private Paint mXPaint = new Paint();
//餅狀圖初始繪製角度
private float startAngle = 0;
/**
* rectF 顯示bitMap模組
* rectFGold 白金色圈
* rectFIn 白色內圈
* rectFLabel 標籤外圈
*/
private RectF rectF, rectFGold, rectFIn, rectFLabel;
/**
* rectFF 彈出的最外圈,
* rectFGlodF 彈出的白金層
* reatFInF 彈出的透明層
* rShadowFF 彈出的最外圈的陰影
*/
private RectF rectFF, rectFGlodF, rectFInF, rShadowFF;
/**
* rLabel rectFLabel 未彈出時,最外層標籤
* r rectF 最外圈的半徑
* rGold rectFGold 白金的半徑
* rIn reatFInF 透明內圈
* rF rectF 彈出的最外圈的半徑
* rGlodF rectFGold 彈出白金的半徑
*/
private float rLabel, r, rGold, rIn, rF, rGlodF, rInF;
//展示動畫
private ValueAnimator animatorStart;
//end動畫
private ValueAnimator animatorEnd;
//動畫時間
private long animatorDuration = 500;
private TimeInterpolator timeInterpolator = new AccelerateDecelerateInterpolator();
/**
* 每一個模組佔有的總角度 (前幾模組角度之和)
*/
private float[] pieAngles;
/**
* 選中位置,對應觸控的模組位置
*/
private int touchSeleteId;
//點觸扇形 偏移比例
private double touchRatioRectFF = 1.4;
//最外層標籤
private double labelRadioRectF = 1.2;
//繪製bitmap圓環半徑比例
private double widthBmpRadioRectF = 0.8;
//白金層
private double goldRadioRectF = 0.4;
//最裡面透明層
private double insideRadiusScale = 0.3;
/**
* 中間bitmap收縮倍數
*/
private float bmpScale = 1f;
//文字顏色
private int touchTextColor = Color.BLACK;
//標籤文字顏色
private int labelTextColor = Color.WHITE;
//陰影顏色
private int shadowColor = 0x22000000;
//白金區域顏色
private int goldColor = 0x66b8e0e0;
/**
* 彈出的 模組 中心文字
*/
private int touchCenterTextSize = 50;
/**
* 寫在邊緣的標籤文字大小
*/
private int labelTextSize = 40;
/**
* 繪製每個模組裡的圖片
*/
private List<Bitmap> bmpList = new ArrayList<>();
public MenuChart(Context context) {
this(context, null);
}
public MenuChart(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MenuChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (DEFAULT_WIDTH == 0 || DEFAULT_HEIGHT == 0) {
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
int displayWidth = wm.getDefaultDisplay().getWidth();
int displayHeight = wm.getDefaultDisplay().getHeight();
DEFAULT_WIDTH = displayWidth;
DEFAULT_HEIGHT = displayHeight;
}
int width = measureSize(1, DEFAULT_WIDTH, widthMeasureSpec);
int height = measureSize(1, DEFAULT_HEIGHT, heightMeasureSpec);
int measureSize = Math.min(width, height); //取最小的 寬|高
setMeasuredDimension(measureSize, measureSize);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
rLabel = (float) (Math.max(mWidth, mHeight) / 2 * widthBmpRadioRectF * labelRadioRectF);
rectFLabel = new RectF(-rLabel, -rLabel, rLabel, rLabel);
//標籤層內繪製bmp
r = (float) (Math.max(mWidth, mHeight) / 2 * widthBmpRadioRectF);// 餅狀圖半徑
rectF = new RectF(-r, -r, r, r);
//白金圓弧
rGold = (float) (r * goldRadioRectF);
rectFGold = new RectF(-rGold, -rGold, rGold, rGold);
//透明層圓
rIn = (float) (r * insideRadiusScale);
rectFIn = new RectF(-rIn, -rIn, rIn, rIn);
/************************以下為彈出模組的RECTF************************************/
//彈出的圓弧
rF = (float) (Math.max(mWidth, mHeight) / 2 * widthBmpRadioRectF * touchRatioRectFF);// 餅狀圖半徑
// 餅狀圖繪製區域
rectFF = new RectF(-rF, -rF, rF, rF);
rShadowFF = new RectF(-rF - PIE_SPACING * 10, -rF - PIE_SPACING * 10, rF + PIE_SPACING * 10, rF + PIE_SPACING * 10);
//彈出的白金層圓弧
rGlodF = (float) (rF * goldRadioRectF);
rectFGlodF = new RectF(-rGlodF, -rGlodF, rGlodF, rGlodF);
//彈出的透明圓
rInF = (float) (rF * insideRadiusScale);
rectFInF = new RectF(-rInF, -rInF, rInF, rInF);
initDate(mPieData);
// showStartAnim();
}
private Bitmap bmpCenter;
private Matrix bmpMatrix;
/**
* 設定居中旋轉的bitmap
*/
public void setCenterBitmap(int resourceId, int width, int heigth) {
bmpCenter = BitmapFactory.decodeResource(getResources(), resourceId);
bmpCenter = PieUtils.zoomImage(bmpCenter, width, heigth);
bmpMatrix = new Matrix();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mPieData == null)
return;
canvas.translate(mWidth / 2, mHeight / 2);// 將畫板座標原點移動到中心位置
if (bmpCenter != null) { //展示的時候旋轉中心圖片
bmpMatrix.reset();
bmpMatrix.preTranslate(-bmpCenter.getWidth() / 2, -bmpCenter.getHeight() / 2); //將bitmap移動中心點(原本中心點位於bitmap坐上角(0,0))
bmpMatrix.preRotate(360 / PIE_VIEW_ANGLE * animatedValue * 2, bmpCenter.getWidth() / 2, bmpCenter.getHeight() / 2);//以bitmap中心點為中心 閉著眼 旋轉 跳躍
canvas.drawBitmap(bmpCenter, bmpMatrix, mPaint);
}
// canvas.drawLine(-mWidth / 2, 0, mWidth / 2, 0, mPaint); //繪製一下中心座標 x,y
// canvas.drawLine(0, mHeight / 2, 0, -mHeight / 2, mPaint);
/**
* 先畫扇形的每一模組
*/
drawPieRectF(canvas);
/**
* 再繪製扇形上的文字/圖片
*/
drawTextAndBmp(canvas);
}
/**
* 負責繪製扇形的所有 Rect , Arc
*
* @param canvas 畫板
*/
private void drawPieRectF(Canvas canvas) {
float currentStartAngle = 0;// 當前已經繪製的角度,我們從0開始,直到animatedValue
canvas.save();
canvas.rotate(startAngle);
float sweepAngle; //當前要繪製的角度
for (int i = 0; i < mPieData.size(); i++) {
PieData pie = mPieData.get(i);
sweepAngle = Math.min(pie.getAngle() - PIE_SPACING, animatedValue - currentStartAngle); //-1 是為了顯示每個模組之間的空隙
if (currentStartAngle + sweepAngle == PIE_VIEW_ANGLE - PIE_SPACING) { //防止最後一個模組缺角 ,如果是 360° 且最後一塊希望是缺覺,可以註釋這段
sweepAngle = pie.getAngle();
}
if (sweepAngle > 0) {
Log.i(TAG, "繪製角度" + (sweepAngle + currentStartAngle));
Log.i(TAG, "繪製角度 animatedValue" + animatedValue);
if (i == touchSeleteId) { //彈出觸控的板塊
drawArc(canvas, currentStartAngle, sweepAngle, pie, rectFF, rectFGlodF, rectFInF, true);
} else {
drawArc(canvas, currentStartAngle, sweepAngle, pie, rectF, rectFGold, rectFIn, false);
}
}
currentStartAngle += pie.getAngle();
}
canvas.restore();
}
/**
* 繪製所有的文字 | bitmap
* 繪製文字和繪製扇形的RectF/Arc 有所不同,繪製文字是當animatedValue達到某一值的時候講文字繪製
* 而繪製扇形的時候是根據animatedValue一個角度一個角度繪製的
*
* @param canvas 畫板
*/
private void drawTextAndBmp(Canvas canvas) {
float currentStartAngle = startAngle; //當前繪製的起始角度,使用者設定 。繪製文字我們直接加上其實角度的值即可(更便捷計算)
for (int i = 0; i < mPieData.size(); i++) {
PieData pie = mPieData.get(i);
int pivotX; //中心點X
int pivotY; //中心點Y
if (animatedValue > pieAngles[i] - pie.getAngle() / 3) { //當要繪製的角度 >當前模組的 2/3 的時候才繪製。 增強介面動畫繪製效果
if (i == touchSeleteId) { //如果當前模組是觸控的模組,繪製文字
pivotX = (int) (Math.cos(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (rF + rGlodF) / 2); //獲取當前模組中心點x,y軸座標
pivotY = (int) (Math.sin(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (rF + rGlodF) / 2);
textCenter(pie.getName(), mPaint, canvas, pivotX, pivotY);
} else {
pivotX = (int) (Math.cos(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (r + rLabel) / 2);
pivotY = (int) (Math.sin(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (r + rLabel) / 2);
canvas.save();
canvas.rotate((currentStartAngle + pie.getAngle() / 2) + 90, pivotX, pivotY); //+90° 原因是 0°的時候文字豎直繪製
textCenter(pie.getName_label(), mLabelPaint, canvas, pivotX, pivotY); //畫邊緣標籤的文字
canvas.restore();
pivotX = (int) (Math.cos(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (r + rGold) / 2);
pivotY = (int) (Math.sin(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (r + rGold) / 2);
if (bmpList != null && bmpList.size() > 0 && bmpList.get(i) != null) { //每次invalidate() 都要重新繪製一遍圖片在畫板
Log.d(TAG, "繪製圖片" + i);
canvas.drawBitmap(bmpList.get(i), (pivotX - (int) (pie.getMax_drawable_size() / 2)), (pivotY - (int) (pie.getMax_drawable_size() / 2)), mPaint);
}
}
currentStartAngle += pie.getAngle();
}
}
}
/**
* 畫出扇形 ,兩種不同情況 展示|觸控彈出
*
* @param canvas 畫板
* @param currentStartAngle 當前的扇形總角度
* @param sweepAngle 要繪製扇形(模組)的角度
* @param pie 資料,獲取其顏色
* @param outRectF 最外圈的矩形
* @param midRectF 白金的矩形
* @param inRectF 透明區域的矩形
*/
private void drawArc(Canvas canvas, float currentStartAngle, float sweepAngle, PieData pie, RectF outRectF, RectF midRectF, RectF inRectF, boolean isTouch) {
int layerID;
if (!isTouch) { //沒有touch情況 .顯示標籤
layerID = canvas.saveLayer(rectFLabel, mPaint, Canvas.ALL_SAVE_FLAG);
mXPaint.setColor(pie.getLabelColor());
//drawArc裡 useCenter的意思是畫出的弧形是否連線中心點,否則就連線頭尾兩點
canvas.drawArc(rectFLabel, currentStartAngle, sweepAngle, true, mXPaint); //標籤層寫文字
} else { //touch 情況
layerID = canvas.saveLayer(rShadowFF, mPaint, Canvas.ALL_SAVE_FLAG);
mXPaint.setColor(shadowColor);
canvas.drawArc(rShadowFF, currentStartAngle - PIE_SPACING, sweepAngle + PIE_SPACING * 2, true, mXPaint); //畫個陰影,左右邊填充滿間隙
}
mXPaint.setColor(Color.WHITE);
canvas.drawArc(outRectF, currentStartAngle, sweepAngle, true, mXPaint); //touch情況該層寫文字
mXPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawArc(midRectF, currentStartAngle - PIE_SPACING, sweepAngle + PIE_SPACING * 2, true, mXPaint); //先把白金區域 擦拭乾淨。再繪製
mXPaint.setXfermode(null);
if (sweepAngle <= (pie.getAngle() - PIE_SPACING) && !isTouch) { //不要間隔
sweepAngle += PIE_SPACING;
}
mXPaint.setColor(goldColor);
canvas.drawArc(midRectF, currentStartAngle, sweepAngle, true, mXPaint); //白金區域
mXPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawArc(inRectF, -1f, PIE_VIEW_ANGLE + 2f, true, mXPaint); //透明圈
Log.d(TAG, "drawArc currentStartAngle==" + currentStartAngle + "+sweepAngle==" + sweepAngle + " ==" + (currentStartAngle + sweepAngle));
mXPaint.setXfermode(null);
canvas.restoreToCount(layerID);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return onPieTouchEvent(event, event.getX(), event.getY());
}
/**
* 該方法可提供給外界使用
* 即設定當前螢幕觸控點對應 我們控制元件的座標,可實現功能: 觸控-->(顯示我們控制元件的btn,我們控制元件顯示)-->手指觸控不放,移動手指彈出 模組
* 使用方法為傳入 event, event.getRawX() - pieLocation[0], event.getRawY() - pieLocation[1]
*
* @param eventPivotX 在我們控制元件內對應的 x 座標
* @param eventPivotY 在我們控制元件內對應的 y 座標
*/
public boolean onPieTouchEvent(MotionEvent event, float eventPivotX, float eventPivotY) {
if (mPieData.size() > 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
getParent().requestDisallowInterceptTouchEvent(true);
float x = eventPivotX - (mWidth / 2); //因為我們已經將座標中心點(0,0)移動到 mWidth / 2,mHeight / 2
float y = eventPivotY - (mHeight / 2);
float touchAngle = 0; //Android 繪圖座標和我們的數學認知不同,安卓由第4 - 3 - 2 -1 象限繪製角度
if (x < 0) { //x<0 的時候 ,求出的角度實為 [0°,-90°) ,此時我們給它掰正
touchAngle += 180;
}
touchAngle += Math.toDegrees(Math.atan(y / x));
touchAngle = touchAngle - startAngle;
if (touchAngle < 0) {
touchAngle = touchAngle + 360;
}
float touchRadius = (float) Math.sqrt(y * y + x * x);//求出觸控的半徑
//我們用touchSeleteId來表示當前選中的模組
if (rIn < touchRadius && touchRadius < rLabel) {//觸控的範圍在繪製的區域
if (-Arrays.binarySearch(pieAngles, touchAngle) - 1 == touchSeleteId) { //如果模組有變動(手指挪到其他模組)再繪製
return true; //如果觸控的地方是已經展開的,那麼就不再重複繪製
}
touchSeleteId = -Arrays.binarySearch(pieAngles, touchAngle) - 1;
invalidate();
Log.d(TAG, " ACTION_DOWN MOVE invalidate()");
} else if (0 < touchRadius && touchRadius < rIn) { //觸控的範圍在原點bmp範圍
if (animatedValue == PIE_VIEW_ANGLE && event.getAction() == MotionEvent.ACTION_DOWN) { //如果已經全展開
showEndAnim();
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
相關推薦
我的第一篇自定義view--Menu選單
一、前言
第一次寫部落格,不知道什麼姿勢才能顯示出一副好像很老練的樣子。老大讓寫一個選單欄控制元件,借鑑了Idtk自定義view,站在巨人的肩上思路是豁然開朗。
github原始碼地址:https://github.com/qdxxxx/MenuChar
Android開發之漫漫長途 番外篇——自定義View的各種姿勢2
是個 pub water 常用 getchild mod one 它的 sdn 該文章是一個系列文章,是本人在Android開發的漫漫長途上的一點感想和記錄,我會盡量按照先易後難的順序進行編寫該系列。該系列引用了《Android開發藝術探索》以及《深入理解Android 卷
shell腳本第一篇——自定義創建用戶和批量創建用戶
pre lin 批量刪除 seq 批量創建 nbsp inux passwd 第一篇 shell腳本第一篇——自定義創建用戶和批量創建用戶1、用shell腳本建立Linux用戶# vim /root/user.sh #!/bin/bash #通過腳本自定義創建用戶rea
Android 自定義View 常用選單的中的Table
0909修改
package com.example.myapplication;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.
看完這篇還不會自定義 View ,我跪搓衣板
自定義 View
在實際使用的過程中,我們經常會接到這樣一些需求,比如環形計步器,柱狀圖表,圓形頭像等等,這時我們通常的思路是去Google 一下,看看 github 上是否有我們需要的這些控制元件,但是如果網上收不到這樣的控制元件呢?這時我們經常需要自定義 View 來滿足需求。
接下來讓我們開啟自定
java_自定義標簽,我的第一個自定義標簽!
row rar return www. AD rst lang hide exc 自定義標簽,我的第一個自定義標簽!
總共分兩步
編寫一個實現tag接口的java類,把jsp頁面中的java代碼移到這個類中,(標簽處理器類)
編寫標簽庫描述符(tld)文件,在tld文
Android 自定義View 系列文章目錄 Canvas篇
一、基礎
Android 畫筆 Paint 基本操作API:https://blog.csdn.net/huangliniqng/article/details/82588824
Android 畫布 Canvas 基本操作API:https://blog.csdn.net/
Android自定義View系列:標籤LabelView實戰篇
前言部分
本文主要介紹如何自定義一個常見的labels標籤,功能上主要支援,單選、多選、點選三種模式。因為這個使用率很高,並且這個是比較典型學習自定義ViewGroup的例子,所以特意動手實踐,加深對Android的認識。這個專案主要是為了自己學習使用,所以並不是很完善,先上一個效果
Android自定義View-Measure原理篇
在自定義View中有時需要測量View的尺寸,因此,瞭解View的Measure過成有助於我們開發自定義View。
一、目的:測量View的寬與高
在有些情況下,需要多次測量(measure)才能夠最終確定View的寬高(比如父檢視MeasureSpec使用UNSPECI
Android自定義View-Layout原理篇
Android自定義View通常需要經過measure、layout和draw過程,如果你沒有了解過measure過程,可以先看看這篇文章。
一、Layout的作用:計算檢視的位置,即Left、Top、Right、Bottom四點的位置
二、layout過程:跟measu
Android自定義View(三)-Draw原理篇
Android自定義View通常需要經過measure、layout和draw過程。
如果你沒有了解過measure過程,可以先看看這篇文章。
如果你沒有了解過layout過程,可以先看看這篇文章。
一、draw的作用:繪製View檢視
二、draw過程:類似meas
HenCoder Android 自定義 View 1-7:屬性動畫 Property Animation(進階篇)
這期是 HenCoder 自定義繪製的第 1-7 期:屬性動畫(進階篇)
簡介
上期的內容,對於大多數簡單的屬性動畫場景已經夠用了。這期的內容主要針對兩個方面:
針對特殊型別的屬性來做屬性動畫;
針對複雜的屬性關係來做屬性動畫。
TypeEvaluator
安卓自定義View進階-Path之完結篇
經歷過前兩篇 Path之基本操作 和 Path之貝塞爾曲線 的講解,本篇終於進入Path的收尾篇,本篇結束後Path的大部分相關方法都已經講解完了,但Path還有一些更有意思的玩法,應該會在後續的文章中出現。
一.Path常用方法表
為了相容性(偷懶) 本表格中去除
我的自定義View學習筆記(二)—— Paint 使用
這個一個系列,本系列講的都是本人自定義 View 的學習筆記。目的是加深影響,便於在以後工作中遇到相關問題的時候,能夠有個印象知道到哪裡去尋找答案。
這是我學習扔物線大神的自定義 View 教程,自己記錄的筆記。連結在這裡HenCoder,強烈推薦大家去原地址
我的Android進階之旅------>Android自定義View來實現解析lrc歌詞並同步滾動、上下拖動、縮放歌詞的功能
前言
最近有個專案有關於播放音樂時候,關於歌詞有以下幾個功能:
1、實現歌詞同步滾動的功能,即歌曲播放到哪句歌詞,就高亮地顯示出正在播放的這個歌詞;
2、實現上下拖動歌詞時候,可以拖動播放器的進度。即可以不停地上下拖動歌詞,
開發懸浮球SDK之自定義view(流量球)上篇 — 水波紋(波浪線)
本人開發懸浮球SDK大致流程及過程中遇到的問題和解決方法我會寫到我的部落格中。
(關於Paint 類,Path類,Canvas類相關具體詳解,請您拉到本部落格下方,點選連結方便您學習哦!)
自定義view的核心方法 onMeasure(int widthMeasureSpec,int hei
Android自定義View之仿QQ側滑選單實現
最近,由於正在做的一個應用中要用到側滑選單,所以通過查資料看視訊,學習了一下自定義View,實現一個類似於QQ的側滑選單,順便還將其封裝為自定義元件,可以實現類似QQ的側滑選單和抽屜式側滑選單兩種選單。
下面先放上效果圖:
我們這裡的側
水波紋進度條(自定義View——進階篇1)
自定義控制元件——ProgressCircleView(水波紋進度條)
最近在很多群都有提到水波紋進度條,看起來蠻唬人的,但是我們要相信毛爺爺的話,一切看起來唬人的都是紙老唬,一言不合,還是先貼效果圖
自定義View原理篇(2)- layout過程
1. 簡介
View的繪製過程分為三部分:measure、layout、draw。
measure用來測量View的寬和高。
layout用來計算View的位置。
draw用來繪製View。
經過measure之後就進入了layout過
第一章 仿支付寶芝麻信用介面製作(需要自定義View的相關知識)
//構造器//ondraw 方法是專門負責View的繪製方法,用畫筆畫出的圖形 都是在這個方法裡繪畫出來的;@Overrideprotected void onDraw(Canvas canvas) {// TODO Auto-generated method stubsuper.onDraw(canvas)