draw custom views in android - android

I want to draw something like in android, to use it as a button. How to do that? any links or suggestion? Is it a bezier curve?

As stated in the comments, you can create a PNG and use it directly. If you want the sides to scale independently from the curve, you can 9-patch your image.
According to this post, you now have the option to define a path drawable in xml. But only for Lollipop and up.
Finally, you can create a base button and use the Path object to draw a quadratic curve. Example. I haven't tried it myself, but you should be able to fill the area below the bath Example 1, Example 2.
Edit:
I had a bit a time available to create an example of a Path implementation. In order to fill the section below the path, clipping needs to be used. This isn't an exact solution for you, but you should be able to get what you want by tweaking a few of the variables (x1, y1, x2, y2, x3, y3).
In my implementation I made use of a gradient instead of a solid colour, since it doesn't make a difference to the implementation, and it serves as a more general example.
public class PathView extends View
{
private Paint paintFill;
private Paint paintLine;
private Paint paintClear;
private Path path;
private int colour;
private float x0;
private float y0;
private float x1;
private float y1;
private float x2;
private float y2;
private float x3;
private float y3;
public PathView(Context context)
{
super(context);
}
public PathView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public PathView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
public PathView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
super.onLayout(changed, left, top, right, bottom);
initialize();
}
private void initialize()
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
// Path clipping uses hardware acceleration which is unavailable from 11 to 18
// https://stackoverflow.com/questions/8895677/work-around-canvas-clippath-that-is-not-supported-in-android-any-more
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
paintFill = new Paint();
paintFill.setAntiAlias(true);
LinearGradient gradient = new LinearGradient(0, getHeight(), 0, 0, Color.WHITE, colour, Shader.TileMode.CLAMP); // Vertical gradient
paintFill.setShader(gradient);
paintLine = new Paint();
paintLine.setColor(colour);
paintLine.setStrokeWidth(1.5f);
paintLine.setStrokeCap(Paint.Cap.ROUND);
paintLine.setStyle(Paint.Style.STROKE);
paintLine.setAntiAlias(true);
paintClear = new Paint();
paintClear.setColor(Color.WHITE);
paintClear.setAntiAlias(true);
}
public int getColour()
{
return colour;
}
public void setColour(int colour)
{
this.colour = colour;
initialize();
invalidate();
}
public void setVars(float x1, float y1, float x2, float y2)
{
// When the vars changes, the path needs to be updated.
// In order to make clipping easier, we draw lines from [x0, y0] to
// [x0, getHeight] and [x3, y3] to [x3, getHeight].
// This makes the fill section of the path everything below the path.
path = new Path();
float cx = getWidth() / 2;
float cy = getHeight() / 2;
this.x0 = 0;
this.y0 = cy + y1;
this.x1 = x1;
this.y1 = cy + y1;
this.x2 = x2;
this.y2 = cy + y2;
this.x3 = getWidth();
this.y3 = cy + y2;
// Move to bottom, draw up
path.moveTo(this.x0, getHeight());
path.lineTo(this.x0 - paintLine.getStrokeMiter(), this.y0);
path.cubicTo(this.x1, this.y1, this.x2, this.y2, this.x3, this.y3);
// Draw down
path.lineTo(this.x3 + paintLine.getStrokeMiter(), getHeight());
invalidate();
}
#Override
public void draw(Canvas canvas)
{
super.draw(canvas);
if (path != null && paintFill != null)
{
// Draw gradient background first, and then clip the irrelevant section away.
// This will let our gradient be uniform irrespective of the vars used.
canvas.drawRect(x0, 0, x3, getHeight(), paintFill);
canvas.save();
canvas.clipPath(path, Region.Op.DIFFERENCE);
canvas.drawRect(x0, 0, x3, getHeight(), paintClear);
canvas.restore();
canvas.drawPath(path, paintLine);
}
}
}

Related

Android canvas - Draw a transparent hole over a rectangle [duplicate]

I am creating a pixel-hunting game. So my activity shows an ImageView. And I want to create a hint "show me where is the object". For this I need to blur whole image except a circle around a point where the object is located. Instead of blur I can show a just semitransparent black background.
There is there is no problem to draw a semitransparent rectangle on the Canvas.
But I don't know how to crop a transparent circle from it.
The result should look like like this:
Please help me to achieve same result on Android SDK.
So finally I managed to do this.
Firstly I draw a semitransparent black rectangle on whole view.
After that using PorterDuff.Mode.CLEAR I cut a transparent circle to show cat's position.
I had problem with PorterDuff.Mode.CLEAR: firstly I was getting a black circle instead of a transparent one.
Thanks to Romain Guy's comments here: comment here I understood that my window is opaque and I should draw on another bitmap. And only after draw on View's canvas.
Here is my onDraw method:
private Canvas temp;
private Paint paint;
private Paint p = new Paint();
private Paint transparentPaint;
private void init(){
Bitmap bitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
temp = new Canvas(bitmap);
paint = new Paint();
paint.setColor(0xcc000000);
transparentPaint = new Paint();
transparentPaint.setColor(getResources().getColor(android.R.color.transparent));
transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
protected void onDraw(Canvas canvas) {
temp.drawRect(0, 0, temp.getWidth(), temp.getHeight(), paint);
temp.drawCircle(catPosition.x + radius / 2, catPosition.y + radius / 2, radius, transparentPaint);
canvas.drawBitmap(bitmap, 0, 0, p);
}
I found a solution without bitmap drawing and creation. Here is the result of my implementation:
You need to create a custom FrameLayout and drawCircle with Clear paint:
mBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
Also do not forget to disable Hardware acceleration and call setWillNotDraw(false) because we will override onDraw method
setWillNotDraw(false);
setLayerType(LAYER_TYPE_HARDWARE, null);
The full example is here:
public class TutorialView extends FrameLayout {
private static final float RADIUS = 200;
private Paint mBackgroundPaint;
private float mCx = -1;
private float mCy = -1;
private int mTutorialColor = Color.parseColor("#D20E0F02");
public TutorialView(Context context) {
super(context);
init();
}
public TutorialView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TutorialView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public TutorialView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setWillNotDraw(false);
setLayerType(LAYER_TYPE_HARDWARE, null);
mBackgroundPaint = new Paint();
mBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
#Override
public boolean onTouchEvent(MotionEvent event) {
mCx = event.getX();
mCy = event.getY();
invalidate();
return true;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(mTutorialColor);
if (mCx >= 0 && mCy >= 0) {
canvas.drawCircle(mCx, mCy, RADIUS, mBackgroundPaint);
}
}
}
PS: This implementation just draws hole inside itself, you need to put background in your layout and add this TutorialView on top.
I have done this way by creating custom LinearLayout:
Check the Screenshot:
CircleOverlayView.java
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.LinearLayout;
/**
* Created by hiren on 10/01/16.
*/
public class CircleOverlayView extends LinearLayout {
private Bitmap bitmap;
public CircleOverlayView(Context context) {
super(context);
}
public CircleOverlayView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CircleOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public CircleOverlayView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (bitmap == null) {
createWindowFrame();
}
canvas.drawBitmap(bitmap, 0, 0, null);
}
protected void createWindowFrame() {
bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas osCanvas = new Canvas(bitmap);
RectF outerRectangle = new RectF(0, 0, getWidth(), getHeight());
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(getResources().getColor(R.color.colorPrimary));
paint.setAlpha(99);
osCanvas.drawRect(outerRectangle, paint);
paint.setColor(Color.TRANSPARENT);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
float centerX = getWidth() / 2;
float centerY = getHeight() / 2;
float radius = getResources().getDimensionPixelSize(R.dimen.radius);
osCanvas.drawCircle(centerX, centerY, radius, paint);
}
#Override
public boolean isInEditMode() {
return true;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
bitmap = null;
}
}
CircleDrawActivity.java:
public class CircleDrawActivity extends AppCompatActivity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_circle_draw);
}
}
activity_circle_draw.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rlParent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/lighthouse"
android:scaleType="fitXY" />
<common.customview.CircleOverlayView
android:id="#+id/cicleOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent">
</common.customview.CircleOverlayView>
</RelativeLayout>
colors.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>
dimens.xml:
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="nav_header_vertical_spacing">16dp</dimen>
<dimen name="nav_header_height">160dp</dimen>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="radius">50dp</dimen>
</resources>
Hope this will help you.
#Robert's answer actually showed me how to solve this problem but his code doesn't work. So I have updated his solution and made it work:
public class CaptureLayerView extends View {
private Bitmap bitmap;
private Canvas cnvs;
private Paint p = new Paint();
private Paint transparentPaint = new Paint();;
private Paint semiTransparentPaint = new Paint();;
private int parentWidth;
private int parentHeight;
private int radius = 100;
public CaptureLayerView(Context context) {
super(context);
init();
}
public CaptureLayerView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
transparentPaint.setColor(getResources().getColor(android.R.color.transparent));
transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
semiTransparentPaint.setColor(getResources().getColor(R.color.colorAccent));
semiTransparentPaint.setAlpha(70);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
bitmap = Bitmap.createBitmap(parentWidth, parentHeight, Bitmap.Config.ARGB_8888);
cnvs = new Canvas(bitmap);
cnvs.drawRect(0, 0, cnvs.getWidth(), cnvs.getHeight(), semiTransparentPaint);
cnvs.drawCircle(parentWidth / 2, parentHeight / 2, radius, transparentPaint);
canvas.drawBitmap(bitmap, 0, 0, p);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
parentWidth = MeasureSpec.getSize(widthMeasureSpec);
parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth, parentHeight);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
And now use this view in any layout like this:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="#+id/surfaceView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
</SurfaceView>
<com.example.myapp.CaptureLayerView
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="#+id/btnTakePicture"
android:layout_width="match_parent"
android:layout_height="80dp"
android:onClick="onClickPicture"
android:text="#string/take_picture">
</Button>
Here I wanted a semi transparent layer on the SurfaceView with transparent circle in the center.
P.S. This code is not optimized one because it creates Bitmap in onDraw method, it is because I couldn't get parent view's width and height in init method, so I only could know them in onDraw.
A simple 2020 kotlin solution without any warnings.
1. Code for custom view with hole
class HolePosition(var x: Float, var y: Float, var r: Float)
class HoleView #JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
): View(context, attrs, defStyleAttr) {
private val paint: Paint = Paint()
private var holePaint: Paint = Paint()
private var bitmap: Bitmap? = null
private var layer: Canvas? = null
//position of hole
var holePosition: HolePosition = HolePosition(0.0f, 0.0f, 0.0f)
set(value) {
field = value
//redraw
this.invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (bitmap == null) { configureBitmap() }
//draw background
layer?.drawRect(0.0f, 0.0f, width.toFloat(), height.toFloat(), paint)
//draw hole
layer?.drawCircle(holePosition.x, holePosition.y, holePosition.r, holePaint);
//draw bitmap
canvas.drawBitmap(bitmap!!, 0.0f, 0.0f, paint);
}
private fun configureBitmap() {
//create bitmap and layer
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
layer = Canvas(bitmap!!)
}
init {
//configure background color
val backgroundAlpha = 0.8
paint.color = ColorUtils.setAlphaComponent(resources.getColor(R.color.mainDark, null), (255 * backgroundAlpha).toInt() )
//configure hole color & mode
holePaint.color = resources.getColor(android.R.color.transparent, null)
holePaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
}
2. Layout example
<com.your_company.package.HoleView
android:id="#+id/hole_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.your_company.package.HoleView>
3. Hole set usage
val holeView = findViewById<HoleView>(R.id.hole_view)
holeView.holePosition = HolePosition(x = 350.0f, y = 350.0f, r = 180.0f)
4. Results
I don't have much to add to your answer, But if anyone's interested I moved the bitmap allocation and all to onSizeChanged so it's better performance wise.
Here you can find a FrameLayout with a "Hole" in the middle of it ;)
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.FrameLayout;
/**
* Created by blackvvine on 1/1/16.
*/
public class SteroidFrameLayout extends FrameLayout {
private Paint transPaint;
private Paint defaultPaint;
private Bitmap bitmap;
private Canvas temp;
public SteroidFrameLayout(Context context) {
super(context);
__init__();
}
public SteroidFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
__init__();
}
public SteroidFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
__init__();
}
private void __init__() {
transPaint = new Paint();
defaultPaint = new Paint();
transPaint.setColor(Color.TRANSPARENT);
transPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
setWillNotDraw(false);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
temp = new Canvas(bitmap);
}
#Override
protected void dispatchDraw(Canvas canvas) {
temp.drawColor(Color.TRANSPARENT);
super.dispatchDraw(temp);
temp.drawCircle(cx, cy, getWidth()/4, transPaint);
canvas.drawBitmap(bitmap, 0, 0, defaultPaint);
if (p < 1)
invalidate();
else
animRunning = false;
}
}
p.s: although it's much more efficient than the original answer it's still a relatively heavy task to do in a draw() method, so if you're using this technique in an animation like me, don't expect a 60.0fps smooth one
public class CircleBlur extends Activity implements View.OnTouchListener {
SeekBar seekBar;
ImageView image,image1;
private Paint paint;
Bitmap circle,blurimg;
private Matrix matrix = new Matrix();
private Matrix savedMatrix = new Matrix();
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mode = NONE;
private PointF start = new PointF();
private PointF mid = new PointF();
private float oldDist = 1f;
float newRot = 0f;
private float d = 0f;
private float[] lastEvent = null;
private float radius=12;
Bitmap blurbitmap;
#RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_blurimage);
image=findViewById(R.id.image);
seekBar=findViewById(R.id.seekbar);
image1=findViewById(R.id.image1);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
//here your image bind to Imageview
image.setImageResource(R.drawable.nas1);
image1.setOnTouchListener(this);
seekBar.setProgress(12);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
radius = (float) CircleBlur.this.seekBar.getProgress();
blurbitmap=createBlurBitmap(blurimg, radius);
CircleBlur();
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
#RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
private Bitmap createBlurBitmap(Bitmap src, float r) {
if (r <= 0) {
r = 0.1f;
} else if (r > 25) {
r = 25.0f;
}
Bitmap bitmap = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888);
RenderScript renderScript = RenderScript.create(this);
Allocation blurInput = Allocation.createFromBitmap(renderScript, src);
Allocation blurOutput = Allocation.createFromBitmap(renderScript, bitmap);
ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
blur.setInput(blurInput);
blur.setRadius(r);
blur.forEach(blurOutput);
blurOutput.copyTo(bitmap);
renderScript.destroy();
return bitmap;
}
#RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
public void CircleBlur()
{
Bitmap result;
// your circle image
circle = BitmapFactory.decodeResource(getResources(),R.drawable.cicleouter);
result = Bitmap.createBitmap(blurimg.getWidth(), blurimg.getHeight(), Bitmap.Config.ARGB_8888);
Canvas mCanvas = new Canvas(result);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
paint.setDither(true);
mCanvas.drawBitmap(blurimg,0,0, null);
mCanvas.drawBitmap(circle, matrix, paint);
paint.setXfermode(null);
image1.setImageBitmap(result);
}
#RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
#Override
public boolean onTouch(View v, MotionEvent event) {
image1 = (ImageView) v;
float x = event.getX(), y = event.getY();
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
start.set(x, y);
mode = DRAG;
lastEvent = null;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if (oldDist > 10f) {
savedMatrix.set(matrix);
midPoint(mid, event);
mode = ZOOM;
}
lastEvent = new float[4];
lastEvent[0] = event.getX(0);
lastEvent[1] = event.getX(1);
lastEvent[2] = event.getY(0);
lastEvent[3] = event.getY(1);
d = rotation(event);
break;
// case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
lastEvent = null;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
matrix.set(savedMatrix);
float dx = x - start.x;
float dy = y - start.y;
matrix.postTranslate(dx, dy);
} else if (mode == ZOOM) {
float newDist = spacing(event);
if (newDist > 10f) {
matrix.set(savedMatrix);
float scale = (newDist / oldDist);
matrix.postScale(scale, scale, mid.x, mid.y);
}
if (lastEvent != null && event.getPointerCount() == 2 || event.getPointerCount() == 3) {
newRot = rotation(event);
float r = newRot - d;
float[] values = new float[9];
matrix.getValues(values);
float tx = values[2];
float ty = values[5];
float sx = values[0];
float xc = (image.getWidth() / 2) * sx;
float yc = (image.getHeight() / 2) * sx;
matrix.postRotate(r, tx + xc, ty + yc);
}
}
break;
}
CircleBlur();
return true;
}
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
float s=x * x + y * y;
return (float)Math.sqrt(s);
}
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
private float rotation(MotionEvent event) {
double delta_x = (event.getX(0) - event.getX(1));
double delta_y = (event.getY(0) - event.getY(1));
double radians = Math.atan2(delta_y, delta_x);
return (float) Math.toDegrees(radians);
}
}
This worked for me:
canvas.drawCircle(x,y,radius,new Paint(Color.TRANSPARENT))
If you're having trouble getting a transparent circle cutout over a view with an opaque background, see this answer. To get it to work, I set my custom layout view to have a transparent background in XML, then drew on the background color I wanted for the layout with the line
cv.drawColor(Color.BLUE); //replace with your desired background color
The full OnDraw method from the answer I linked above:
#Override
protected void onDraw(Canvas canvas) {
int w = getWidth();
int h = getHeight();
int radius = w > h ? h / 2 : w / 2;
bm.eraseColor(Color.TRANSPARENT);
cv.drawColor(Color.BLUE);
cv.drawCircle(w / 2, h / 2, radius, eraser);
canvas.drawBitmap(bm, 0, 0, null);
super.onDraw(canvas);
}
Kotlin version without Bitmap allocation. But instead of a circle, a rounded rectangle is drawn here. I'm sure you can adapt it to circle by yourself.
private val scanAreaBorder = Paint().apply {
color = ContextCompat.getColor(context, R.color.postyellow)
style = Paint.Style.STROKE
strokeWidth = dpToPx(context, resources.getDimension(R.dimen.scan_area_border_stroke_width).toInt())
}
private val scanArea = Paint().apply {
color = ContextCompat.getColor(context, android.R.color.transparent)
xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
private val background = Paint().apply {
color = ContextCompat.getColor(context, R.color.scanner_area)
}
init {
setLayerType(LAYER_TYPE_HARDWARE, null)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.run {
drawRect(0f, 0f, width.toFloat(), height.toFloat(), background)
val top = (height / 2f) - dpToPx(context, resources.getDimension(R.dimen.scan_area_height).toInt())
val bottom = (height / 2f) + dpToPx(context, resources.getDimension(R.dimen.scan_area_height).toInt())
val margin = dpToPx(context, resources.getDimension(R.dimen.scan_area_margin).toInt())
val cornerRadius = dpToPx(context, resources.getDimension(R.dimen.scan_area_corner_radius).toInt())
drawRoundRect(margin, top, width.toFloat() - margin, bottom, cornerRadius, cornerRadius, scanArea)
drawRoundRect(margin, top, width.toFloat() - margin, bottom, cornerRadius, cornerRadius, scanAreaBorder)
}
}
This can be actually done in a much more efficient way, and with much less code using a Path object.
code:
In the constructor of a View overlay class:
path.addCircle(600,1000, 200, Path.Direction.CW);
path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
paint.setColor(0x55_00_00_00);
(The numbers for x, y and radius above should be replaces with whatever you need).
And override the view's onDraw(Canvas):
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path,paint);
}
That's it...

Draw round corners on top left top right bottom left bottom right using Path and RectF in Android

By making a custom ImageView and override the onDraw method with the following will make the ImageView to have rounded corners. Reference
#Override
protected void onDraw(Canvas canvas) {
float radius = getContext().getResources().getDimension(R.dimen.round_corner_radius);
Path path = new Path();
RectF rect = new RectF(0, 0, this.getWidth(), this.getHeight());
path.addRoundRect(rect, radius, radius, Path.Direction.CW);
canvas.clipPath(path);
super.onDraw(canvas);
}
How can I selectively make the round corners instead of making all four corners round. For example, only making the top left and top right corners round and leave the bottom corners intact. Here is a solution to do to through Bitmap. I am looking for doing it in this onDraw method and only using Path and RectF.
There is a Path#addRoundRect() overload that takes a float array of eight values wherein we can specify the x- and y-radius for each of the four corners. These values are in [x, y] pairs, starting at the top-left corner, and going clockwise around the rest. For those corners we want rounded, we set both values of the pair to the radius value, and leave them at zero for those we don't.
As an illustrative example, a simple method that will return a Path that can be used in your snippet:
private Path getPath(float radius, boolean topLeft, boolean topRight,
boolean bottomRight, boolean bottomLeft) {
final Path path = new Path();
final float[] radii = new float[8];
if (topLeft) {
radii[0] = radius;
radii[1] = radius;
}
if (topRight) {
radii[2] = radius;
radii[3] = radius;
}
if (bottomRight) {
radii[4] = radius;
radii[5] = radius;
}
if (bottomLeft) {
radii[6] = radius;
radii[7] = radius;
}
path.addRoundRect(new RectF(0, 0, getWidth(), getHeight()),
radii, Path.Direction.CW);
return path;
}
Per your example description, rounding the top-left and top-right corners:
#Override
protected void onDraw(Canvas canvas) {
float radius = getContext().getResources().getDimension(R.dimen.round_corner_radius);
Path path = getPath(radius, true, true, false, false);
canvas.clipPath(path);
super.onDraw(canvas);
}
As always, I would recommend keeping the onDraw() method as tight as possible, moving anything that doesn't absolutely have to be there elsewhere. The resource value for the radius, for instance, could be retrieved in the constructor, and kept in a field. Furthermore, the Path could be constructed only when necessary; i.e., when the View's size changes, or when the radius or chosen corners change.
Since I put together a simple custom ImageView to test this, I'll include it here, as it demonstrates the above points. This custom View also offers XML attributes that allow the corner radius and the rounded corners to be set in your layout.
public class RoundishImageView extends ImageView {
public static final int CORNER_NONE = 0;
public static final int CORNER_TOP_LEFT = 1;
public static final int CORNER_TOP_RIGHT = 2;
public static final int CORNER_BOTTOM_RIGHT = 4;
public static final int CORNER_BOTTOM_LEFT = 8;
public static final int CORNER_ALL = 15;
private static final int[] CORNERS = {CORNER_TOP_LEFT,
CORNER_TOP_RIGHT,
CORNER_BOTTOM_RIGHT,
CORNER_BOTTOM_LEFT};
private final Path path = new Path();
private int cornerRadius;
private int roundedCorners;
public RoundishImageView(Context context) {
this(context, null);
}
public RoundishImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundishImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RoundishImageView);
cornerRadius = a.getDimensionPixelSize(R.styleable.RoundishImageView_cornerRadius, 0);
roundedCorners = a.getInt(R.styleable.RoundishImageView_roundedCorners, CORNER_NONE);
a.recycle();
}
public void setCornerRadius(int radius) {
if (cornerRadius != radius) {
cornerRadius = radius;
setPath();
invalidate();
}
}
public int getCornerRadius() {
return cornerRadius;
}
public void setRoundedCorners(int corners) {
if (roundedCorners != corners) {
roundedCorners = corners;
setPath();
invalidate();
}
}
public boolean isCornerRounded(int corner) {
return (roundedCorners & corner) == corner;
}
#Override
protected void onDraw(Canvas canvas) {
if (!path.isEmpty()) {
canvas.clipPath(path);
}
super.onDraw(canvas);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setPath();
}
private void setPath() {
path.rewind();
if (cornerRadius >= 1f && roundedCorners != CORNER_NONE) {
final float[] radii = new float[8];
for (int i = 0; i < 4; i++) {
if (isCornerRounded(CORNERS[i])) {
radii[2 * i] = cornerRadius;
radii[2 * i + 1] = cornerRadius;
}
}
path.addRoundRect(new RectF(0, 0, getWidth(), getHeight()),
radii, Path.Direction.CW);
}
}
}
For the XML attributes to work, the following needs to be in your <resources>, which you can do by putting this file in your project's res/values/ folder, or adding to the one that might already be there.
attrs.xml
<resources>
<declare-styleable name="RoundishImageView">
<attr name="cornerRadius" format="dimension" />
<attr name="roundedCorners">
<flag name="topLeft" value="1" />
<flag name="topRight" value="2" />
<flag name="bottomRight" value="4" />
<flag name="bottomLeft" value="8" />
<flag name="all" value="15" />
</attr>
</declare-styleable>
</resources>
The cornerRadius is a dimension attribute, and should be specified as a dp or px value. The roundedCorners is a flag attribute, and multiple corners can be chosen using the pipe character, |. For example:
<com.mycompany.myapp.RoundishImageView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/riv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitXY"
android:src="#drawable/magritte"
app:cornerRadius="#dimen/round_corner_radius"
app:roundedCorners="topLeft|topRight" />
I also spent a half-day to solve the problem; the key point here is how to use mPath.arcTo make a corner. The basic knowledge is E direction is 0 degree, then the second param means which degree start; the third param means how many degrees to show.
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
public class RectangleConerView extends View {
private Path mPath;
private Paint mPaint;
private PathMeasure mPathMeasure;
private float mAnimatorValue;
private Path mDst;
private float mLength;
private float left = 300;
private float top = 200;
private float width = 800;
private float height = 300;
private float checkWidth = 100;
private float checkHeight = 60;
private float cornerRadius = 30;
public RectangleConerView(Context context) {
super(context);
}
public RectangleConerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public RectangleConerView(Context context, AttributeSet attrs)
{
super(context, attrs);
mPathMeasure = new PathMeasure();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPath = new Path();
mPath.moveTo(left + width, top + checkHeight/2);
//bottom-right
mPath.lineTo(left + width, top + height - cornerRadius);
mPath.arcTo(new RectF(left + width-cornerRadius, top + height - cornerRadius, left + width, top + height), 0, 90); //start degree is E direct, then CW 90 degree, which is the bottom-right corner.
//bottom-left
mPath.lineTo(left + cornerRadius, top + height);
mPath.arcTo(new RectF(left, top + height - cornerRadius, left + cornerRadius, top + height), 90, 90);//start degree is the S, then CW 90 degree, which is the bottom-left corner.
//top-left
mPath.lineTo(left, top + cornerRadius);
mPath.arcTo(new RectF(left, top, left + cornerRadius, top + cornerRadius), 180, 90);//start degree W
//top-right
mPath.lineTo(left + width - checkWidth/2, top);
mPathMeasure.setPath(mPath, false);
mLength = mPathMeasure.getLength();
mDst = new Path();
final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAnimatorValue = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
valueAnimator.setDuration(1000);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.start();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mDst.reset();
// 硬件加速的BUG
mDst.lineTo(0,0);
float stop = mLength * mAnimatorValue;
mPathMeasure.getSegment(0, stop, mDst, true);
canvas.drawPath(mDst, mPaint);
}
}

Android: Circle moves to random location when tapping it

I am writing a program that draws a circle at a random location on the phone screen. What it is supposed to do is when the circle is touched, it removes that circle and moves it to another random location.
Currently, when the screen is tapped the old circle is removed, and redrawn at another random location on the screen. It is very close, but I was wondering how I get over this hurdle.
public class Circle extends View {
private float x = 300;
private float y = 300;
private int r = 150;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Random random = new Random();
// draws circle
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.RED);
canvas.drawCircle(x, y, r, mPaint);
}
// constructors
public Circle(Context context) {
super(context);
init();
}
public Circle(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public Circle(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
void init() {
// might be used later for some data collection
}
// gets random number,,
void generateRandom() {
int w = getWidth() - r - 50;
int h = getHeight()- r - 50;
int border = r + 50;
this.x = border + random.nextInt(w-border);
this.y = border + random.nextInt(h-border);
}
// when screen is tapped, old circle removed, new circle drawn
#Override
public boolean onTouchEvent(MotionEvent event) {
generateRandom();
invalidate();
return super.onTouchEvent(event);
}
}
Change the code like this ,
#Override
public boolean onTouchEvent(MotionEvent event) {
if (isInsideCircle(event.getX(), event.getY())) {
generateRandom();
invalidate();
}
return super.onTouchEvent(event);
}
boolean isInsideCircle(float xPoint, float yPoint) {
float dx = (x - xPoint);
float dxPow = (float) Math.pow(dx, 2);
float dy = (y - yPoint);
float dyPow = (float) Math.pow(dy, 2);
float radPow = (float) Math.pow(r, 2);
return (dxPow + dyPow) < radPow || (dxPow + dyPow == radPow);
}

Android: Custom circle ProgressBar with spaces in between

I am trying to create a custom view that has a Circle and in it, I have to have sections in run time as shown in the image below. I tried a lot of stuff in onDraw method but got no luck. I even tried https://github.com/donvigo/CustomProgressControls . Basically, I want to give a number of sections and then in each section I can select colors as per my need.
I am looking for ProgressBar that should have gap/space as shown in the image; in between circles. Say if I have given 5 sections, 3 of which should be "full", it should color the first 3 in red, and the other 2 in green, for example.
To draw I am doing like:
private void initExternalCirclePainter() {
internalCirclePaint = new Paint();
internalCirclePaint.setAntiAlias(true);
internalCirclePaint.setStrokeWidth(internalStrokeWidth);
internalCirclePaint.setColor(color);
internalCirclePaint.setStyle(Paint.Style.STROKE);
internalCirclePaint.setPathEffect(new DashPathEffect(new float[]{dashWith, dashSpace}, dashSpace));
}
I might be a little late to the party, but I actually wrote a custom component that has 2 rings that look quite similar to what you're trying to achieve. You can just remove the outer ring easily. The image of what I got in the end:
Here's the class:
public class RoundedSectionProgressBar extends View {
// The amount of degrees that we wanna reserve for the divider between 2 sections
private static final float DIVIDER_ANGLE = 7;
public static final float DEGREES_IN_CIRCLE = 360;
public static final int PADDING = 18;
public static final int PADDING2 = 12;
protected final Paint paint = new Paint();
protected final Paint waitingPaint = new Paint();
protected final Paint backgroundPaint = new Paint();
private int totalSections = 5;
private int fullSections = 2;
private int waiting = 3; // The outer ring. You can omit this
private RectF rect = new RectF();
public RoundedSectionProgressBar(Context context) {
super(context);
init(context, null);
}
public RoundedSectionProgressBar(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public RoundedSectionProgressBar(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
// Can come from attrs if need be?
int strokeWidth = 3;
setupPaint(context, strokeWidth, paint, R.color.filled_color_inner_ring);
setupPaint(context, strokeWidth, waitingPaint, R.color.empty_color_inner_ring);
setupPaint(context, strokeWidth, backgroundPaint, R.color.filled_color_outer_ring);
}
private void setupPaint(Context context, int strokeWidth, Paint backgroundPaint, int colorRes) {
backgroundPaint.setStrokeCap(Paint.Cap.SQUARE);
backgroundPaint.setColor(context.getResources().getColor(colorRes));
backgroundPaint.setAntiAlias(true);
backgroundPaint.setStrokeWidth(strokeWidth);
backgroundPaint.setStyle(Paint.Style.STROKE);
}
public int getTotalSections() {
return totalSections;
}
public void setTotalSections(int totalSections) {
this.totalSections = totalSections;
invalidate();
}
public int getFullSections() {
return fullSections;
}
public void setNumberOfSections(int fullSections, int totalSections, int waiting) {
this.fullSections = fullSections;
this.totalSections = totalSections;
this.waiting = waiting;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
rect.set(getLeft() + PADDING, getTop() + PADDING, getRight() - PADDING, getBottom() - PADDING);
float angleOfSection = (DEGREES_IN_CIRCLE / totalSections) - DIVIDER_ANGLE;
// Drawing the inner ring
for (int i = 0; i < totalSections; i++) {
// -90 because it doesn't start at the top, so rotate by -90
// divider_angle/2 especially in 2 sections, it's visibly rotated by Divider angle, so we split this between last and first
float startAngle = -90 + i * (angleOfSection + DIVIDER_ANGLE) + DIVIDER_ANGLE / 2;
if (i < fullSections) {
canvas.drawArc(rect, startAngle, angleOfSection, false, paint);
} else {
canvas.drawArc(rect, startAngle, angleOfSection, false, backgroundPaint);
}
}
// Drawing the outer ring
rect.set(getLeft() + PADDING2, getTop() + PADDING2, getRight() - PADDING2, getBottom() - PADDING2);
for (int i = 0; i < waiting; i++) {
float startAngle = -90 + i * (angleOfSection + DIVIDER_ANGLE) + DIVIDER_ANGLE / 2;
canvas.drawArc(rect, startAngle, angleOfSection, false, waitingPaint);
}
}
}
Notice that this code won't give you the outer ring's 'empty' slots, since we decided against them in the end. The inner circle will have both the empty and filled slots. The whole class can be reused, and it's responsible just for the 2 rings that are drawn, the 6/6, +3 and the red circle are parts of another view.
The most important piece of the code is the onDraw method. It contains the logic for drawing the arcs in the for loop, as well as the logic for calculating the angles and adding spaces between them. Everything is rotated by -90 degrees, because I needed it to start at the top, rather than on the right, as it is the 0-degree angle in Android. It's not that complex, and you can modify it to fit your needs better should you need to.
I find it easier to do math for drawArc(operating on angle values based on number of sections) rather than computing the arc length.
Here's a quick idea, with a lot of hard-coded properties, but you should be able to get the idea:
public class MyStrokeCircleView extends View {
private Paint mPaint;
private RectF mRect;
private int mPadding;
private int mSections;
private int mFullArcSliceLength;
private int mColorArcLineLength;
private int mArcSectionGap;
public MyStrokeCircleView(Context context) {
super(context);
init(null, 0);
}
public MyStrokeCircleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public MyStrokeCircleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
private void init(AttributeSet attrs, int defStyle) {
mPaint = new Paint();
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10);
mPaint.setColor(ContextCompat.getColor(getContext(), android.R.color.darker_gray));
mPadding = 5;
mRect = new RectF(mPadding, mPadding, mPadding, mPadding);
mSections = 4;
mFullArcSliceLength = 360 / mSections;
mArcSectionGap = mFullArcSliceLength / 10;
mColorArcLineLength = mFullArcSliceLength - 2 * mArcSectionGap;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mRect.right = getWidth() - mPadding;
mRect.bottom = getHeight() - mPadding;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mSections; i++) {
canvas.drawArc(mRect, i * mFullArcSliceLength + mArcSectionGap, mColorArcLineLength, false, mPaint);
}
}
}

Custom view doesn't draw Bitmap

I´m trying to display a moving cloud over a blue sky. The blue background is displayed but the cloud doesn't appear. I've tried the different approaches in other questions here but nothing works. My code is:
public class CloudBackground extends View {
Bitmap cloud;
int x = 0;
int y = 0;
Paint paint = new Paint();
Rect rectangle = new Rect(0,0,100,100);
public CloudBackground(Context context,AttributeSet attrs) {
super(context,attrs);
cloud = BitmapFactory.decodeResource(getResources(),R.drawable.cloud1);
}
#Override
protected void onDraw (Canvas canvas){
super.onDraw(canvas);
Rect back = new Rect();
back.set(0,0,canvas.getWidth(), canvas.getHeight());
Paint pBlue = new Paint();
pBlue.setStyle(Paint.Style.FILL);
pBlue.setColor(Color.CYAN);
canvas.drawRect(back, pBlue);
drawCloud(x,y,canvas);
if (x < canvas.getWidth())
x = x + 10;
else {
y = y + 10;
x = 0;
}
invalidate();
}
private void drawCloud(int x2, int y2, Canvas canvas) {
canvas.drawBitmap(cloud, x2, y2,paint);
}
Ok. Try this:
public CloudBackground(Context context,AttributeSet attrs) {
super(context,attrs);
cloud = BitmapFactory.decodeResource(context.getApplicationContext().getResources(),R.drawable.cloud1);
}
The issue was finally solved using a.jpg instead a .png. As simple as that and with no reason.

Categories

Resources