package com.lfk.justweengine.utils.joystick;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.lfk.justweengine.R;
/**
* Joystick
*
* @author liufengkai
* Created by liufengkai on 2016/12/16.
*/
public class JoystickView extends View implements Runnable {
///////////////////////////////////////////////////////////////////////////
// interface
///////////////////////////////////////////////////////////////////////////
public interface OnMovedListener {
/**
* CallBack For Moved
*
* @param angle angle of the button
* @param length the length of you moved
*/
void onMoved(int angle, int length);
}
private OnMovedListener mOnMovedListener;
private int mPosX = 0;
private int mPosY = 0;
/**
* the center of the button
*/
private int mCenterX = 0;
private int mCenterY = 0;
/**
* button radius
*/
private int mButtonRadius;
/**
* boarder radius
*/
private int mBorderRadius;
private int mBackgroundColor = Color.WHITE;
private boolean isCircleWithBackground = false;
private Bitmap mCircleBackground = null;
private Paint mPaintBackground;
private Paint mPaintCircleBorder;
private Paint mPaintBitmapBackground;
private Paint mPaintCircleButton;
private int mCurrentPointAction = MotionEvent.ACTION_CANCEL;
/**
* border default color
*/
private static final int DEFAULT_COLOR = Color.GREEN;
/**
* background default color
*/
private static final int DEFAULT_BACKGROUND_COLOR = Color.TRANSPARENT;
/**
* default view size
*/
private static final int DEFAULT_SIZE = 200;
/**
* default button size
*/
private static final double RATIO_SIZE_BUTTON = 0.25;
/**
* default border size
*/
private static final double RATIO_SIZE_BORDER = 0.75;
/**
* default border 's width
*/
private static final int DEFAULT_WIDTH_BORDER = 3;
private static final int DEFAULT_LOOP_INTERVAL = 50; // in milliseconds
private OnMovedListener mCallback = null;
private long mLoopInterval = DEFAULT_LOOP_INTERVAL;
private boolean isRunning = false;
private Thread mThread = null;
public JoystickView(Context context, AttributeSet attrs) {
super(context, attrs);
int buttonColor = DEFAULT_COLOR;
int borderColor = DEFAULT_COLOR;
int backgroundColor = DEFAULT_BACKGROUND_COLOR;
int borderWidth = DEFAULT_WIDTH_BORDER;
if (attrs != null) {
TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.JoystickView,
0, 0
);
buttonColor = styledAttributes.getColor(R.styleable.JoystickView_JV_buttonColor, DEFAULT_COLOR);
borderColor = styledAttributes.getColor(R.styleable.JoystickView_JV_borderColor, DEFAULT_COLOR);
backgroundColor = styledAttributes.getColor(R.styleable.JoystickView_JV_backgroundColor, DEFAULT_BACKGROUND_COLOR);
borderWidth = styledAttributes.getDimensionPixelSize(R.styleable.JoystickView_JV_borderWidth, DEFAULT_WIDTH_BORDER);
// mFixedCenter = styledAttributes.getBoolean(R.styleable.JoystickView_JV_fixedCenter, DEFAULT_FIXED_CENTER);
styledAttributes.recycle();
}
mPaintCircleButton = new Paint();
mPaintCircleButton.setAntiAlias(true);
mPaintCircleButton.setColor(buttonColor);
mPaintCircleButton.setStyle(Paint.Style.FILL);
mPaintCircleBorder = new Paint();
mPaintCircleBorder.setAntiAlias(true);
mPaintCircleBorder.setColor(borderColor);
mPaintCircleBorder.setStyle(Paint.Style.STROKE);
mPaintCircleBorder.setStrokeWidth(borderWidth);
mPaintBackground = new Paint();
mPaintBackground.setAntiAlias(true);
mPaintBackground.setColor(backgroundColor);
mPaintBackground.setStyle(Paint.Style.FILL);
}
public JoystickView(Context context) {
this(context, null);
}
///////////////////////////////////////////////////////////////////////////
// initial message
///////////////////////////////////////////////////////////////////////////
private void initial() {
mCenterX = mPosX = getWidth() / 2;
mCenterY = mPosY = getWidth() / 2;
downPosX = 0;
downPosY = 0;
deltaX = 0;
deltaY = 0;
setRunning(false);
}
///////////////////////////////////////////////////////////////////////////
// draw
///////////////////////////////////////////////////////////////////////////
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw background color
canvas.drawColor(mBackgroundColor);
// if (isCircleWithBackground && mCircleBackground != null) {
//
// }
// border background
canvas.drawCircle(mCenterX, mCenterY, mBorderRadius, mPaintBackground);
// circle border background
canvas.drawCircle(mCenterX, mCenterY, mBorderRadius, mPaintCircleBorder);
// circle button
canvas.drawCircle(mPosX, mPosY, mButtonRadius, mPaintCircleButton);
}
///////////////////////////////////////////////////////////////////////////
// measure size
///////////////////////////////////////////////////////////////////////////
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int d = Math.min(measure(widthMeasureSpec), measure(heightMeasureSpec));
setMeasuredDimension(d, d);
}
private int measure(int measureSpec) {
if (MeasureSpec.getMode(measureSpec) == MeasureSpec.UNSPECIFIED) {
// if no bounds are specified return a default size (200)
return DEFAULT_SIZE;
} else {
// As you want to fill the available space
// always return the full available bounds.
return MeasureSpec.getSize(measureSpec);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
initial();
// radius based on smallest size : height OR width
int d = Math.min(w, h);
mButtonRadius = (int) (d / 2 * RATIO_SIZE_BUTTON);
mBorderRadius = (int) (d / 2 * RATIO_SIZE_BORDER);
}
///////////////////////////////////////////////////////////////////////////
// onTouchEvent
///////////////////////////////////////////////////////////////////////////
int downPosX = 0, downPosY = 0;
int deltaX = 0, deltaY = 0;
boolean absLargeFlag = false;
@Override
public boolean onTouchEvent(MotionEvent event) {
// to move the button according to the finger coordinate
mCurrentPointAction = event.getAction();
int currentPosX = (int) (event.getX());
int currentPosY = (int) (event.getY());
switch (mCurrentPointAction) {
case MotionEvent.ACTION_DOWN:
downPosX = currentPosX;
downPosY = currentPosY;
if (mThread == null) {
mThread = new Thread(this);
setRunning(true);
mThread.start();
}
break;
case MotionEvent.ACTION_MOVE:
deltaX = currentPosX - downPosX;
deltaY = currentPosY - downPosY;
mPosX += deltaX;
mPosY += deltaY;
double abs = getAbs(mPosX, mPosY);
if (absLargeFlag && abs > mBorderRadius) {
mPosX = currentPosX;
mPosY = currentPosY;
abs = getAbs(mPosX, mPosY);
}
if (abs > mBorderRadius) {
absLargeFlag = true;
mPosX = (int) ((mPosX - mCenterX) * mBorderRadius / abs + mCenterX);
mPosY = (int) ((mPosY - mCenterY) * mBorderRadius / abs + mCenterY);
downPosX = mPosX;
downPosY = mPosY;
break;
} else {
absLargeFlag = false;
}
downPosX = currentPosX;
downPosY = currentPosY;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
initial();
invalidate();
return true;
}
invalidate();
return true;
}
private double getAbs(int posX, int posY) {
return Math.sqrt((posX - mCenterX) * (posX - mCenterX)
+ (posY - mCenterY) * (posY - mCenterY));
}
/**
* Process the angle following the 360° counter-clock protractor rules.
*
* @return the angle of the button
*/
private int getAngle() {
int angle = (int) Math.toDegrees(Math.atan2(mCenterY - mPosY, mPosX - mCenterX));
return angle < 0 ? angle + 360 : angle; // make it as a regular counter-clock protractor
}
/**
* Process the strength as a percentage of the distance between the center and the border.
*
* @return the strength of the button
*/
private int getStrength() {
return (int) (100 * Math.sqrt((mPosX - mCenterX)
* (mPosX - mCenterX) + (mPosY - mCenterY)
* (mPosY - mCenterY)) / mBorderRadius);
}
///////////////////////////////////////////////////////////////////////////
// getter && setter
///////////////////////////////////////////////////////////////////////////
public OnMovedListener getOnMovedListener() {
return mOnMovedListener;
}
public void setOnMovedListener(OnMovedListener onMovedListener) {
mOnMovedListener = onMovedListener;
}
public int getBackgroundColor() {
return mBackgroundColor;
}
@Override
public void setBackgroundColor(int backgroundColor) {
mBackgroundColor = backgroundColor;
}
public void setCircleBackground(Bitmap circleWithBackground) {
setCircleWithBackground(true);
this.mCircleBackground = circleWithBackground;
}
public void setCircleWithBackground(boolean circleWithBackground) {
isCircleWithBackground = circleWithBackground;
}
public OnMovedListener getCallback() {
return mCallback;
}
public void setCallback(OnMovedListener callback) {
mCallback = callback;
}
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean running) {
isRunning = running;
}
private Runnable runnable = new Runnable() {
public void run() {
if (mCallback != null)
mCallback.onMoved(getAngle(), getStrength());
}
};
@Override
public void run() {
while (isRunning) {
post(runnable);
try {
Thread.sleep(mLoopInterval);
} catch (InterruptedException e) {
break;
}
}
}
}