前段時(shí)間很火的一款貪吃蛇游戲,可玩性很高,幾點(diǎn)規(guī)則改造就將傳統(tǒng)的貪吃蛇改活了,當(dāng)時(shí)我拿過(guò)13000多分,還嘚瑟了很久。今天來(lái)個(gè)教程10分鐘實(shí)現(xiàn)它。。。額,不是,實(shí)現(xiàn)它的方向操作按鈕效果,看下圖左下角的那兩個(gè)同心圓。
用戶手指觸碰屏幕任意位置,內(nèi)圓就往用戶手指那個(gè)方向移動(dòng)至外圓邊界內(nèi)切,實(shí)現(xiàn)后效果圖如下所示。
先看兩張圖,分別是Android坐標(biāo)系與Android View尺寸函數(shù)的含義,其中,Android坐標(biāo)系往右x軸遞增,往下y軸遞增,不多說(shuō)。
下面開(kāi)始編碼
1)創(chuàng)建HandleView類,繼承自View
/**
* 貪吃蛇大作戰(zhàn)方向控制按鈕效果
*/
public class HandleView extends View {
public HandleView(Context context) {
this(context, null);
}
public HandleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HandleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// TODO
}
}
上述代碼可以作為幾乎所有自定義View的初始代碼模板。
方向操作按鈕等寬等高,我們不想它在xml布局時(shí)被設(shè)置成寬高不等的長(zhǎng)方形,所以需要在onMeasure函數(shù)里進(jìn)行處理。
2)重載onMeasure
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize2(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize2(getSuggestedMinimumHeight(), heightMeasureSpec));
int childWidthSize = getMeasuredWidth();
widthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* Compare to: {@link android.view.View#getDefaultSize(int, int)}
* If mode is AT_MOST, return the child size instead of the parent size
* (unless it is too big).
*/
private static int getDefaultSize2(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
result = Math.min(size, specSize);
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
3)畫(huà)外圓
public class HandleView extends View {
private Paint mPaintForCircle;
// ...
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 背景透明
canvas.drawColor(Color.TRANSPARENT);
// 外圓半徑
int radiusOuter = getWidth() / 2;
// 內(nèi)圓半徑
int radiusInner = getWidth() / 5;
// 圓心坐標(biāo)(cx,cy)
float cx = getWidth() / 2;
float cy = getHeight() / 2;
if (null == mPaintForCircle) {
mPaintForCircle = new Paint();
}
mPaintForCircle.setAntiAlias(true);
mPaintForCircle.setStyle(Paint.Style.FILL);
// 畫(huà)外圓
mPaintForCircle.setColor(Color.argb(0x7f, 0x11, 0x11, 0x11));
canvas.drawCircle(cx, cy, radiusOuter, mPaintForCircle);
// TODO 畫(huà)內(nèi)圓
}
}
4)畫(huà)內(nèi)圓
內(nèi)圓是運(yùn)動(dòng)的,它的位置與用戶的手指觸摸坐標(biāo)有關(guān),按照效果,用戶可以觸摸的范圍是包裹HandleView的ViewGroup(FrameLayout之類的),這里先寫(xiě)個(gè)接口用于獲取手指觸摸坐標(biāo)。
public class HandleView extends View {
private HandleReaction mHandleReaction;
public void setHandleReaction(HandleReaction handleReaction) {
mHandleReaction = handleReaction;
}
public interface HandleReaction {
/**
* 獲取用戶觸摸坐標(biāo)
* @return
*/
float[] getTouchPosition();
}
// ...
}
內(nèi)圓半徑固定,位置由圓心的坐標(biāo)決定,所以關(guān)鍵是得出內(nèi)圓的圓心坐標(biāo)隨用戶手指的觸摸坐標(biāo)的變化而變化的函數(shù)關(guān)系,讓我們建立方程式:(用工具畫(huà)太花時(shí)間,將就手畫(huà),見(jiàn)諒!P.S.好像回到中學(xué)有木有)
由公式可得出,分母(開(kāi)平方根那個(gè)數(shù)的值)是cx2和cy2都需要的公用的值,命名為ratio,計(jì)算代碼如下。
float[] touchPosition = mHandleReaction.getTouchPosition();
double ratio = (radiusOuter - radiusInner) /
Math.sqrt(
Math.pow(touchPosition[0] - cx, 2) +
Math.pow(touchPosition[1] - cy, 2));
float cx2 = (float) (ratio * (touchPosition[0] - cx) + cx);
float cy2 = (float) (ratio * (touchPosition[1] - cy) + cy);
mPaintForCircle.setColor(Color.argb(0xff, 0x11, 0x11, 0x11));
canvas.drawCircle(cx2, cy2, radiusInner, mPaintForCircle);
5)獲取觸摸坐標(biāo)
建立MainActivity,布局文件就不給出了,文末附帶源碼地址。
public class MainActivity extends AppCompatActivity
implements HandleView.HandleReaction, View.OnTouchListener {
private float[] mTouchPosition = null;
private HandleView mHandleView;
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FrameLayout frameLayout = (FrameLayout) findViewById(R.id.frameLayout);
frameLayout.setOnTouchListener(this);
mHandleView = (HandleView) findViewById(R.id.handleView);
mHandleView.setHandleReaction(this);
}
@Override public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE: {
mTouchPosition = new float[2];
mTouchPosition[0] = motionEvent.getX();
mTouchPosition[1] = motionEvent.getY();
mHandleView.invalidate();
return true;
}
case MotionEvent.ACTION_UP: {
mTouchPosition = null;
mHandleView.invalidate();
return true;
}
}
return false;
}
@Override public float[] getTouchPosition() {
return mTouchPosition;
}
}
6)坐標(biāo)修正
表面上,上面內(nèi)圓圓心計(jì)算代碼是正確的,但實(shí)際上,由于我們的HandleView通過(guò)接口從它的父布局那里拿到了觸摸坐標(biāo)與HandleView內(nèi)部坐標(biāo)的參考坐標(biāo)系不是同一個(gè),他們相差一個(gè)HandleView相對(duì)于它父布局的getLeft與getTop的偏移,參照?qǐng)DAndroid-View-Size,所以需要對(duì)計(jì)算代碼進(jìn)行修正,如下:
// 經(jīng)過(guò)修正后的內(nèi)圓圓心坐標(biāo)代碼
double ratio = (radiusOuter - radiusInner) /
Math.sqrt(
Math.pow(touchPosition[0] - cx - getLeft(), 2) +
Math.pow(touchPosition[1] - cy - getTop(), 2));
float cx2 = (float) (ratio * (touchPosition[0] - cx - getLeft()) + cx);
float cy2 = (float) (ratio * (touchPosition[1] - cy - getTop()) + cy);
最后附上源碼地址
GitHub源碼