I'm developing a "settlers of catan" app and I got a big graphic problem: the game board contains a hexagons which have to be buttons. I searched on youtube but I didn't find any way to draw a hex button.
Does anyone know how to build a hexagon button?
related to : How to draw hexagons in android? Try this class :
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.Region;
import android.util.AttributeSet;
import android.view.View;
public class HexagonMaskView extends View {
private Path hexagonPath;
private Path hexagonBorderPath;
private float radius;
private float width, height;
private int maskColor;
public HexagonMaskView(Context context) {
super(context);
init();
}
public HexagonMaskView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HexagonMaskView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
hexagonPath = new Path();
hexagonBorderPath = new Path();
maskColor = 0xFF01FF77;
}
public void setRadius(float r) {
this.radius = r;
calculatePath();
}
public void setMaskColor(int color) {
this.maskColor = color;
invalidate();
}
private void calculatePath() {
float triangleHeight = (float) (Math.sqrt(3) * radius / 2);
float centerX = width/2;
float centerY = height/2;
hexagonPath.moveTo(centerX, centerY + radius);
hexagonPath.lineTo(centerX - triangleHeight, centerY + radius/2);
hexagonPath.lineTo(centerX - triangleHeight, centerY - radius/2);
hexagonPath.lineTo(centerX, centerY - radius);
hexagonPath.lineTo(centerX + triangleHeight, centerY - radius/2);
hexagonPath.lineTo(centerX + triangleHeight, centerY + radius/2);
hexagonPath.moveTo(centerX, centerY + radius);
float radiusBorder = radius - 5;
float triangleBorderHeight = (float) (Math.sqrt(3) * radiusBorder / 2);
hexagonBorderPath.moveTo(centerX, centerY + radiusBorder);
hexagonBorderPath.lineTo(centerX - triangleBorderHeight, centerY + radiusBorder/2);
hexagonBorderPath.lineTo(centerX - triangleBorderHeight, centerY - radiusBorder/2);
hexagonBorderPath.lineTo(centerX, centerY - radiusBorder);
hexagonBorderPath.lineTo(centerX + triangleBorderHeight, centerY - radiusBorder/2);
hexagonBorderPath.lineTo(centerX + triangleBorderHeight, centerY + radiusBorder/2);
hexagonBorderPath.moveTo(centerX, centerY + radiusBorder);
invalidate();
}
#Override
public void onDraw(Canvas c){
super.onDraw(c);
c.clipPath(hexagonBorderPath, Region.Op.DIFFERENCE);
c.drawColor(Color.WHITE);
c.save();
c.clipPath(hexagonPath, Region.Op.DIFFERENCE);
c.drawColor(maskColor);
c.save();
}
// getting the view size and default radius
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
radius = height / 2 - 10;
calculatePath();
}
}
Related
Im making a virtual joystick controller using SurfaceView but the drawn shapes in my java code is not rendered in the design view, im very new to java with just a few months experience.
public class JoystickView extends SurfaceView
implements SurfaceHolder.Callback,
View.OnTouchListener
{
private float centerX;
private float centerY;
private float baseRadius;
private float hatRadius;
private JoystickListener joystickCallback;
private void setupDimensions()
{
centerX = (float)getWidth()/2;
centerY = (float)getHeight()/2;
baseRadius = (float)Math.min(getWidth(), getHeight())/3;
hatRadius = (float)Math.min(getWidth(), getHeight())/5;
}
public JoystickView(Context context)
{
super(context);
getHolder().addCallback(this);
setOnTouchListener(this);
if(context instanceof JoystickListener){
joystickCallback = (JoystickListener) context;}
}
public JoystickView(Context context, AttributeSet attributes, int style)
{
super(context, attributes, style);
getHolder().addCallback(this);
setOnTouchListener(this);
}
public JoystickView(Context context, AttributeSet attributes)
{
super(context, attributes);
getHolder().addCallback(this);
setOnTouchListener(this);
}
This is where the joystick circle base and top(hat) where drawn using myCanvas.drawCircle()enter image description here
private void drawJoystick(float newX, float newY)
{
if(getHolder().getSurface().isValid())
{
Canvas myCanvas = this.getHolder().lockCanvas();
Paint colors = new Paint();
myCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
//These equations determine the sin and cos of the angle that the touched point is at relative to the center of the joystick
float hypotenuse = (float) Math.sqrt(Math.pow(newX - centerX, 2) + Math.pow(newY - centerY, 2));
float sin = (newY - centerY)/ hypotenuse;
float cos = (newX - centerX)/ hypotenuse;
//Drawing the base then shading
colors.setARGB(255, 50, 50, 50);
myCanvas.drawCircle(centerX, centerY, baseRadius, colors);
int ratio = 5;
for(int i = 1; i <= (int)(baseRadius/ ratio); i++)
{
colors.setARGB(150/i, 255, 0, 0); // Gradually reduce the black drawn to create an nice effect
myCanvas.drawCircle(newX- cos * hypotenuse * (ratio /baseRadius) * i,
newY - sin * hypotenuse * (ratio /baseRadius) * i, i * (hatRadius* ratio /baseRadius),colors); //gradually increase the size of the shading effect
}
//Drawing the joystick hat
for(int i = 1; i <= (int) (hatRadius / ratio); i++)
{
colors.setARGB(255, (int) (i * (255 * ratio / hatRadius)),(int)(i * (255 * ratio / hatRadius)), 255);//Change the joystick color for shading purposes
myCanvas.drawCircle(newX,newY, hatRadius - (float) i*(ratio) / 2, colors); // Drawing the shading of the hat
}
getHolder().unlockCanvasAndPost(myCanvas); //This writes the new drawing to the SurfaceView
}
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
setupDimensions();
drawJoystick(centerX, centerY);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
}
public boolean onTouch(View v, MotionEvent e)
{
if(v.equals(this)) {
if (e.getAction() != MotionEvent.ACTION_UP)
{
float displacement = (float) Math.sqrt((Math.pow(e.getX() - centerX, 2)) + Math.pow(e.getY() - centerY, 2));
if (displacement < baseRadius)
{
drawJoystick(e.getX(), e.getY());
joystickCallback.onJoystickMoved((e.getX() - centerX)/baseRadius, (e.getY() - centerY)/baseRadius, getId());
}
else {
float ratio = baseRadius / displacement;
float constrainedX = centerX + (e.getX() - centerX) * ratio;
float constrainedY = centerY + (e.getY() - centerY) * ratio;
drawJoystick(constrainedX, constrainedY);
joystickCallback.onJoystickMoved((constrainedX-centerX)/baseRadius, (constrainedY-centerY)/baseRadius, getId());
}
}
else
drawJoystick(centerX, centerY);
joystickCallback.onJoystickMoved(0,0, getId());
}
return true;
}
public interface JoystickListener
{
void onJoystickMoved(float xPercent, float yPercent, int id);
}
}
I'm trying to achieve this custom wave animation with circle in the middle of the wave.
Below is my custom view. It runs in a different direction and the draw has a line in the middle of the wave that results in a bad UX.
I try to follow some related tutorials but I cannot get the same animation.
If there is any library o code sample to follow it could help me a lot.
Thanks.
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
import com.guille.stressmeterapp.R;
import org.jetbrains.annotations.Nullable;
public class WaveCustomView extends View {
private int mWidth = 0;
private int mHeight = 0;
private Path path;
private Paint paint;
private int waveHeight = 300;
private int waveWidth = 600;
private int originalY = 750;
private Region region;
private int dx = 0;
private Bitmap mBitmap;
private int animationDuration = 3000;
public WaveCustomView(Context context) {
super(context, null);
initUi();
}
public WaveCustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs, 0);
initUi();
}
public WaveCustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initUi();
}
private void initUi() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.parseColor("#000000"));
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(15);
path = new Path();
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.circle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
int desired = (int) (getPaddingLeft() + getPaddingRight());
width = desired;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
int desired = (int) (getPaddingTop() + getPaddingBottom());
height = desired;
}
mWidth = width;
mHeight = height;
waveWidth = mWidth / 2;
setMeasuredDimension(width, height);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, paint);
setDrawData();
Rect bounds = region.getBounds();
if (bounds.top < originalY) {
canvas.drawBitmap(mBitmap, bounds.right - (mBitmap.getWidth() >> 1), bounds.top - (mBitmap.getHeight() >> 1), paint);
} else {
canvas.drawBitmap(mBitmap, bounds.right - (mBitmap.getWidth() >> 1), bounds.bottom - (mBitmap.getHeight() >> 1), paint);
}
}
private void setDrawData() {
path.reset();
int halfWaveWidth = waveWidth / 2;
path.moveTo(-waveWidth + dx, originalY);
for (int i = -waveWidth; i < mWidth + waveWidth; i = i + waveWidth) {
path.rQuadTo(halfWaveWidth >> 1, -waveHeight, halfWaveWidth, 0);
path.rQuadTo(halfWaveWidth >> 1, waveHeight, halfWaveWidth, 0);
}
region = new Region();
Region clip = new Region((int) (mWidth / 2 - 0.1), 0, mWidth / 2, mHeight * 2);
region.setPath(path, clip);
path.close();
}
public void startAnimate() {
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float factor = (float) valueAnimator.getAnimatedValue();
dx = (int) ((waveWidth) * factor);
invalidate();
}
});
animator.setDuration(animationDuration);
animator.start();
}
Your code looks OK. Just remove this line from setDrawData method.
path.close();
This line closes path. It means it connect path begginnig with path end. That's why you see line in the middle of the wave.
Here is result without middle line:
If you want to change the direction of animation just change sign of dx variable. Change this:
dx = (int) ((waveWidth) * factor);
To this:
dx = - (int) (waveWidth * factor);
Or instead of this:
path.moveTo(-waveWidth + dx, originalY);
Do this:
path.moveTo(-waveWidth - dx, originalY);
Final result:
I have a custom Imageview class like below. I have a field called timeInterval which I pass it in constructor, but in onDraw it's value is 0. What is the proper way to pass this? also I don't know if I can pass it dynamically as attribute.
public class ProgressBarView extends AppCompatImageView {
private final Handler mHandler = new Handler();
private int mTimeInterval;
private int mColor;
private int[] mLocations = new int[2];
public ProgressBarView(Context context, int timeInterval) {
super(context);
mTimeInterval = timeInterval;
init(null);
}
public ProgressBarView(Context context, AttributeSet attrst) {
super(context, attrst);
init(attrst);
}
public ProgressBarView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
private void init(AttributeSet attrs) {
if (attrs == null) {
return;
}
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ProgressBarView);
mColor = typedArray.getColor(R.styleable.ProgressBarView_overlay_color, Color.BLACK);
}
public void updatePainting() {
mHandler.postDelayed(this::invalidate, mTimeInterval / 360);
}
#Override
public void onDraw(final Canvas canvas) {
if (mTimeInterval == 0) {
return;
}
Calendar now = Calendar.getInstance();
int seconds = now.get(Calendar.SECOND);
int mStartAngel = (int) (((float) (seconds % (mTimeInterval / 1000))
/ (mTimeInterval / 1000)) * 360);
final Paint paint = new Paint(Paint.DITHER_FLAG);
paint.setColor(mColor);
paint.setStyle(Paint.Style.FILL);
paint.setAlpha(-50);
getLocationOnScreen(mLocations);
int radius = getWidth() / 2;
float centreX = this.getX() + radius;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawArc(centreX - radius, centreX - radius, centreX + radius, centreX + radius, 0, mStartAngel, true, paint);
} else {
final RectF oval = new RectF();
oval.set(centreX - radius, centreX - radius, centreX + radius, centreX + radius);
canvas.drawArc(oval, 0, mStartAngel, true, paint);
}
updatePainting();
}
}
I instantiate it like this:
mProgressBarView = new ProgressBarView(getContext(), timeInterval);
You forgot about "this"
public ProgressBarView(Context context, int timeInterval) {
super(context);
this.mTimeInterval = timeInterval;
init(null);
}
I'am try add the frame to video.
The first ,i add the frame bellow video in my layout.:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/bg"
android:orientation="vertical"
android:gravity="center"
tools:context="com.example.hdadmin.demolove.MainActivity">
<FrameLayout
android:layout_width="300dp"
android:layout_height="400dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/frame2"/>
<com.example.hdadmin.demolove.CircleSurface
android:id="#+id/surface"
android:gravity="center"
android:layout_width="300dp"
android:layout_height="400dp" />
</FrameLayout>
</RelativeLayout>
I draw mask with file my svg :
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Path;
import android.media.MediaPlayer;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.example.hdadmin.demolove.path.SvgUtil;
import com.example.hdadmin.demolove.path.parser.PathInfo;
public class CircleSurface extends SurfaceView implements SurfaceHolder.Callback{
private final Path path = new Path();
private final Matrix pathMatrix = new Matrix();
private final float[] pathDimensions = new float[2];
protected final Matrix matrix = new Matrix();
private PathInfo shapePath;
private int resId;
protected int viewWidth;
protected int viewHeight;
MediaPlayer player;
public CircleSurface(Context context) {
super(context);
init(context);
}
public CircleSurface(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public CircleSurface(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
onSizeChanged(w , h);
}
public void onSizeChanged(int width, int height) {
if (viewWidth == width && viewHeight == height) return;
viewWidth = width;
viewHeight = height;
calculateDrawableSizes();
}
public void calculateDrawableSizes() {
int bitmapWidth = getWidth();
int bitmapHeight = getHeight();
if (bitmapWidth > 0 && bitmapHeight > 0) {
float width = Math.round(viewWidth);
float height = Math.round(viewHeight);
float scale;
float translateX = 0;
float translateY = 0;
if (bitmapWidth * height > width * bitmapHeight) {
scale = height / bitmapHeight;
translateX = Math.round((width / scale - bitmapWidth) / 2f);
} else {
scale = width / (float) bitmapWidth;
translateY = Math.round((height / scale - bitmapHeight) / 2f);
}
matrix.setScale(scale, scale);
calculate(bitmapWidth, bitmapHeight, width, height, scale, translateX, translateY);
reset();
}
}
public void calculate(int bitmapWidth, int bitmapHeight, float width, float height, float scale, float translateX, float translateY) {
path.reset();
pathDimensions[0] = shapePath.getWidth();
pathDimensions[1] = shapePath.getHeight();
pathMatrix.reset();
scale = Math.min(width / pathDimensions[0], height / pathDimensions[1]);
translateX = Math.round((width - pathDimensions[0] * scale) * 0.5f);
translateY = Math.round((height - pathDimensions[1] * scale) * 0.5f);
pathMatrix.setScale(scale, scale);
pathMatrix.postTranslate(translateX, translateY);
shapePath.transform(pathMatrix, path);
pathMatrix.reset();
matrix.invert(pathMatrix);
path.transform(pathMatrix);
}
public void reset() {
path.reset();
}
private void init(Context context) {
//TODO: define the circle you actually want
player = MediaPlayer.create(context, R.raw.video);
player.setVolume(0 , 0);
shapePath = SvgUtil.readSvg(context, R.raw.heat2);
getHolder().addCallback(this);
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.clipPath(shapePath.getPath());
super.dispatchDraw(canvas);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Canvas canvas = null;
player.setDisplay(holder);
player.start();
player.setLooping(true);
}
#Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
}
}
I want my video to fit in the frame.But the result was not as I wanted the video deviate with the frame : result
i'm developing an android drawing application, it has a relativelayout with a custom view in it for drawing with. So far it all works except when I scale the canvas the drawing happens in the wrong spot, it's probably best to show the code rather than try to explain it. Yes I have tried with using path.offset but with no success. The main issue is that when you draw on it when it's been scaled in, while it paints in the right spot, the path is too sensitive, when your finger is centered it's fine but if you go up/down or side to side the path moves faster than your finger.
package com.nick.sketchr;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.View;
public class CustomCanvas extends View {
private Bitmap mBitmap;
private Canvas mCanvas;
private Path mPath;
private Paint mBitmapPaint;
private Paint mPaint;
private float old_scale;
private float dmx,dmy;
private double scale;
private long timevar2;
private Rect bounds;
public CustomCanvas(Context c) {
super(c);
mPath = new Path();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(0xFF000000);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(3);
dmx = Main_App.w/2/2;
dmy = Main_App.h/2/2;
scale = 1;
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmap = Bitmap.createBitmap(Main_App.w/2, Main_App.h/2, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mBitmap.eraseColor(Color.WHITE);
}
#Override
protected void onDraw(Canvas canvas) {
float sc = (float) scale;
canvas.scale(sc, sc, Main_App.w/2, Main_App.h/2);
canvas.drawBitmap(mBitmap, dmx, dmy, mBitmapPaint);
canvas.drawPath(mPath, mPaint);
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private void touch_start(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
mX = x;
mY = y;
}
}
private void touch_up() {
mPath.lineTo(mX, mY);
mPath.offset(-dmx, -dmy); //this is the problem area
//also the path shows wrong when canvas is scaled... o_o
mCanvas.drawPath(mPath, mPaint);
mPath.reset();
}
#Override
public boolean onTouchEvent(MotionEvent event){
float x = event.getX()/(float)scale+bounds.left;
float y = event.getY()/(float)scale+bounds.top;
if(event.getPointerCount() == 2){
timevar2 = event.getEventTime();
float newx = event.getX(0);
float newy = event.getY(0);
float oldx = event.getX(1);
float oldy = event.getY(1);
mPath.reset();
float equa = (float) (Math.pow(newx - oldx, 2) + Math.pow(newy - oldy, 2));
float cscale = (float) Math.sqrt(equa)/100;
float scaled = (cscale - old_scale);
if(scaled < -0.1){
if(scale > 0.1){
scale -= 0.03;
}
}
if(scaled > 0.1){
scale += 0.03;
}
dmx = (float) (((newx + oldx)/2)-(Main_App.w/2/2)*scale);
dmy = (float) (((newy + oldy)/2)-(Main_App.h/2/2)*scale);
old_scale = cscale;
invalidate();
return true;
}
long dt = event.getEventTime() - timevar2;
if(dt < 100){
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
public void clear(){
mBitmap.eraseColor(Color.TRANSPARENT);
invalidate();
System.gc();
}
}
Not entirely sure but you may be scaling the Canvas incorrectly. Instead try:
canvas.scale(width_of_canvas, height_of_canvas)
in your onDraw() method.Currently you are using a scale value of 1.