Custom View disappears when aligned within a RelativeLayout - android

I have a custom view that simply draws a circle onto a canvas (so far).
Using it within a RelativeLayout is fine until I tell it specifically where to be aligned within the RelativeLayout.
i.e. as soon as I add something like: android:layout_centerInParent="true"
it will no longer appear.
The following is the relevant code:
public class CircleProgressBar extends ProgressBar {
int radius;
float centerX;
float centerY;
Paint progressPaint;
public CircleProgressBar(Context context) {
super(context);
init(context);
}
public CircleProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public CircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context) {
progressPaint = new Paint();
progressPaint.setColor(ContextCompat.getColor(context, R.color.colorAccent));
}
private void init(Context context, AttributeSet attributeSet) {
progressPaint = new Paint();
progressPaint.setColor(ContextCompat.getColor(context, R.color.colorAccent));
}
#Override
protected synchronized void onDraw(Canvas canvas) {
radius = Math.max(getMeasuredWidth(), getWidth()) / 2;
centerX = getLeft() + radius;
centerY = getTop() + radius;
canvas.drawCircle(centerX, centerY, radius, progressPaint);
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.tomtaila.circleprogressbar.CircleProgressBar
android:layout_centerInParent="true"
android:layout_width="100dp"
android:layout_height="100dp"/>
</RelativeLayout>

Try like this. This will fix your problem
Remove "android:layout_centerInParent="true"" from "CircleProgressBar" and
set "android:gravity="center"" to the "activity_main" RelativeLayout
OR
#Override
protected synchronized void onDraw(Canvas canvas) {
radius = Math.max(getMeasuredWidth(), getWidth()) / 2;
centerX = radius;
centerY = radius;
canvas.drawCircle(centerX, centerY, radius, progressPaint);
}
While CircleProgressBar tag without centerInParent attribute, Its position is Top left (0,0). So in "onDraw" method you are using getLeft() return 0 and getTop() return 0.
But While CircleProgressBar tag with centerInParent attribute, Its position is center of the parent (parent_width/2,parent_height/2). So In "onDraw" method getLeft() will return parent_width/2() (if parent width is 480, then it will return 240) and simmilarlly getTop() will return parent_height/2. Now centerX, centerY values become too big.

Related

How to use android canvas to draw a striped rectangle?

rectangles
I already drew the left rounded rectangle. I need to insert some lines horizontally for it to become a striped rectangle. How can I achieve this?
I already tried to use path and draw a line. But nothing worked how it was supposed to. Can someone help me?
Here's a sample code for drawing a striped rectangle in Android Canvas:
public class StripeRectView extends View {
private Paint mPaint;
private int mStripeWidth = 10;
private int mStripeHeight = 10;
public StripeRectView(Context context) {
super(context);
init();
}
public StripeRectView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public StripeRectView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.FILL);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int x = 0;
int y = 0;
while (y <= getHeight()) {
canvas.drawRect(x, y, x + mStripeWidth, y + mStripeHeight, mPaint);
x += mStripeWidth * 2;
if (x > getWidth()) {
x = 0;
y += mStripeHeight * 2;
}
}
}
}
the Canvas is used to draw rectangles, with alternating stripes of width mStripeWidth and height mStripeHeight until the entire height of the View is filled. You can modify it according to your needs.

onDraw on View extends ImageButton not work

I'm trying to draw a custom text on an ImageButton. I have overwritten the method onDraw(Canvas) but it didn't work. This is the code:
public class CustomImageButton extends ImageButton {
private static final String TAG = "CustomImageButton";
private String text = "";
private float textSize;
private int textColor;
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CustomImageButton(Context context) {
super(context);
initialize(null);
}
public CustomImageButton(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(attrs);
}
public CustomImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(attrs);
}
private void initialize(AttributeSet attrs) {
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CustomImageButton);
text = a.getString(R.styleable.CustomImageButton_text);
textSize = a.getDimension(R.styleable.CustomImageButton_textSize, 20);
textColor = a.getColor(R.styleable.CustomImageButton_textColor, getResources().getColor(R.color.white));
//setBackgroundColor(getResources().getColor(R.color.transparent));
a.recycle();
invalidate();
}
}
#Override
protected void onDraw(Canvas canvas) {
Typeface font = Typefaces.get(getContext(), "fonts/my-font.ttf");
paint.setColor(Color.WHITE);
paint.setTypeface(font);
paint.setTextSize(100);
paint.setTextAlign(Paint.Align.CENTER);
int xPos = canvas.getWidth() / 2;
int yPos = (int) ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2));
canvas.drawText(text, xPos, yPos, paint);
}
}
And XML Layout:
<RelativeLayout
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"
android:background="#OOOOOO"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.my.app.CustomImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:text="Hello!"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"/>
. . .
</RelativeLayout>
I used setWillNotDraw(false) but it seems that does not work. I have verified that my custom view has a few correct dimensions.

Lollipop elevation on concave outline

I have a custom view which displays a star shape by using a path. This view works as expected, but now I want to shift it's implementation to the new Google Material recommendation.
Unfortunately elevation depends on a convex outline, and I haven't found a solution yet.
Are there any known workarounds or any other creative solution that any of you know?
This is my concave path:
double outerSize = w / 2;
double innerSize = w / 5;
double delta = 2.0*Math.PI/5.0;
double rotation = Math.toRadians(-90);
double xpos = w/2.0;
double ypos = h/2.0;
mPath = new Path();
mPath.moveTo((float)(outerSize * Math.cos(delta + rotation) + xpos),
(float)(outerSize * Math.sin(delta + rotation) + ypos));
for(int point= 0;point<6;point++)
{
mPath.lineTo((float) (innerSize * Math.cos(delta * (point + 0.5) + rotation) + xpos),
(float) (innerSize * Math.sin(delta * (point + 0.5) + rotation) + ypos));
mPath.lineTo((float) (outerSize * Math.cos(delta * (point + 1.0) + rotation) + xpos),
(float) (outerSize * Math.sin(delta * (point + 1.0) + rotation) + ypos));
}
mPath.close();
I've tried this code, without success, which works fine on convex views.
#TargetApi(21)
private class StarOutline extends ViewOutlineProvider {
#Override
public void getOutline(View view, Outline outline) {
StartView r = (StartView) view;
// i know here say setConvexPath not setConcavePath
outline.setConvexPath(r.mPath);
}
}
But as expected, I'm getting an exception:
java.lang.IllegalArgumentException: path must be convex
at android.graphics.Outline.setConvexPath(Outline.java:216)
Any idea how to achieve this aim?
As some of the comments and answer point out, native android shadow only works with convex outlines.
So you are left with either drawing a fake shadow manually on your own (via canvas, bitmap etc) or rely on someone else's library to draw fake shadow for you (Google's Material Components library etc).
Are there any known workarounds or any other creative solution that any of you know?
If you must rely on native android shadow, you can try to break down the shape into multiple convex shape and draw these individually.
Here is an example:
I broke down the star shape into 1 pentagon and 5 triangle polygons (all of them have convex outline) and draw them individually.
TriangleView:
public class TriangleView extends View {
private final Path path = new Path();
private final Paint paint = new Paint();
public TriangleView(Context context) {
super(context);
init(context, null, 0,0);
}
public TriangleView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0,0);
}
public TriangleView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr, 0);
}
public TriangleView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr, defStyleRes);
}
private void init(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes){
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.argb(255, 100, 100, 255));
setOutlineProvider(new OutlineProvider());
}
public void setPoints(float x1, float y1, float x2, float y2, float x3, float y3){
path.reset();
path.moveTo(x1, y1);
path.lineTo(x2, y2);
path.lineTo(x3, y3);
path.close();
postInvalidate();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, paint);
}
private static class OutlineProvider extends ViewOutlineProvider{
#Override
public void getOutline(View view, Outline outline) {
Path path = ((TriangleView)view).path;
outline.setConvexPath(path);
}
}
}
PentagonView:
public class PentagonView extends View {
private final Path path = new Path();
private final Paint paint = new Paint();
public PentagonView(Context context) {
super(context);
init(context, null, 0,0);
}
public PentagonView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0,0);
}
public PentagonView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr, 0);
}
public PentagonView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr, defStyleRes);
}
private void init(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes){
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.argb(255, 150, 150, 255));
setOutlineProvider(new OutlineProvider());
}
public void setPoints(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float x5, float y5){
path.reset();
path.moveTo(x1, y1);
path.lineTo(x2, y2);
path.lineTo(x3, y3);
path.lineTo(x4, y4);
path.lineTo(x5, y5);
path.close();
postInvalidate();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, paint);
}
private static class OutlineProvider extends ViewOutlineProvider {
#Override
public void getOutline(View view, Outline outline) {
Path path = ((PentagonView)view).path;
outline.setConvexPath(path);
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
tools:context=".MainActivity">
<app.eccweizhi.concaveshadow.PentagonView
android:id="#+id/pentagonView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="4dp" />
<app.eccweizhi.concaveshadow.TriangleView
android:id="#+id/triangle1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="4dp" />
<app.eccweizhi.concaveshadow.TriangleView
android:id="#+id/triangle2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="4dp" />
<app.eccweizhi.concaveshadow.TriangleView
android:id="#+id/triangle3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="4dp" />
<app.eccweizhi.concaveshadow.TriangleView
android:id="#+id/triangle4"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="4dp" />
<app.eccweizhi.concaveshadow.TriangleView
android:id="#+id/triangle5"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="4dp" />
</FrameLayout>
I then use them like this
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
pentagonView.setPoints(
520f,
520f,
640f,
520f,
677.0818f,
634.1266f,
580f,
704.6608f,
482.9182f,
634.1266f
)
triangle1.setPoints(520f, 520f, 640f, 520f, 580f, 400f)
triangle2.setPoints(640f, 520f, 677.0818f, 634.1266f, 777f, 520f)
triangle3.setPoints(677.0818f, 634.1266f, 580f, 704.6608f, 697f, 750f)
triangle4.setPoints(580f, 704.6608f, 482.9182f, 634.1266f, 440f, 750f)
triangle5.setPoints(482.9182f, 634.1266f, 520f, 520f, 400f, 520f)
}
}
There is a new drawable called MaterialShapeDrawable in AndroidX package. Given a path, it can render shadow to both concave and convex shapes.
https://developer.android.com/reference/com/google/android/material/shape/MaterialShapeDrawable
This is how you would provide shadow to your concave shape WITHOUT MaterialShapeDrawable:
Create a new bitmap
Modify the bitmap ( draw star shape path on it using a new Canvas object )
Blur the bitmap so it actually looks like a shadow. ( Blurring should be done with RenderScript for performance reasons )
Draw the bitmap on views Canvas.

Prevent ImageView redraw state after hide keyboard

First, I have a view like this:
When user press some text in edittext, I try to draw some rectangles in ImageView with specific coordinates. The problem is when the soft keyboard is hidden, all draw state of Imageview display wrong coordinates, look like it invalidated the imageview. I wonder is there any way to stop this.
Here is my Custom Drawable Imageview:
public class DrawableImageView extends ImageView {
private Paint ptBlur;
private List<Rect> rects;
private int originWidth;
private int originHeight;
public DrawableImageView(Context context) {
super(context);
initPaint();
}
public DrawableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
public DrawableImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initPaint();
}
public void setRects(List<Rect> rects, int originWidth, int originHeight) {
this.originHeight = originHeight;
this.originWidth = originWidth;
this.rects = rects;
invalidate();
}
private void initPaint() {
ptBlur = new Paint();
ptBlur.setColor(Color.YELLOW);
ptBlur.setStyle(Paint.Style.STROKE);
ptBlur.setStrokeWidth(5);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (rects != null) {
int width = getWidth();
int height = getHeight();
for (Rect rect : rects) {
rect.bottom = Math.round(height * rect.bottom / originHeight);
rect.left = Math.round(width * rect.left / originWidth);
rect.right = Math.round(width * rect.right / originWidth);
rect.top = Math.round(height * rect.top / originHeight);
canvas.drawRect(rect, ptBlur);
}
}
}
}
The OnEditorActionListener for EditText:
// get the rectangles and draw
List<Rect> rects = DataUtil.getHighlightRectList(searchText);
drawableImageView.setRects(rects, pageWidth, pageHeight);

Draw text on ImageView doesn't work

I have extended class from ImageView and want some text to be drawn on it. This doesn't work, do you know why? Thank you.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int imgWidth = getMeasuredWidth();
int imgHeight = getMeasuredHeight();
float txtWidth = mTextPaint.measureText("your text");
int x = Math.round(imgWidth/2 - txtWidth/2);
int y = imgHeight/2 - 6; // 6 is half of the text size
canvas.drawText("your text", x, y, mTextPaint);
}
private void init(Context context, AttributeSet attrs, int defStyle) {
mTextPaint = new Paint();
mTextPaint.setColor(android.R.color.black);
mTextPaint.setTextSize(12);
mTextPaint.setTextAlign(Paint.Align.LEFT);}
I ran your code, and immediately got a Lint error. In init() you are setting your mTextPaint to android.R.color.black. Because it's a static value, I could immediately see that the actual int value of that variable is 0x0106000c, which is almost completely transparent. You should use getResources().getColor(android.R.color.black) or plain ol' Color.BLACK.
Note that a textSize of 12 is very, very small. This code shows 12 (albeit very small).
public class MyImageView extends ImageView {
public MyImageView(Context context, AttributeSet attributeSet, int defStyle) {
super(context, attributeSet, defStyle);
init();
}
public MyImageView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
init();
}
public MyImageView(Context context) {
super(context);
init();
}
Paint mTextPaint;
private void init() {
mTextPaint = new Paint();
mTextPaint.setColor(Color.BLACK);
mTextPaint.setTextSize(12);
mTextPaint.setTextAlign(Paint.Align.LEFT);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int imgWidth = getMeasuredWidth();
int imgHeight = getMeasuredHeight();
float txtWidth = mTextPaint.measureText("your text");
int x = Math.round(imgWidth/2 - txtWidth/2);
int y = imgHeight/2 - 6;
canvas.drawText("12", x, y, mTextPaint);
}
}
xml:
<RelativeLayout 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">
<com.example.mytest.MyImageView
android:layout_width="100dp"
android:layout_height="100dp"/>
</RelativeLayout>
Copy/paste, if problem persist, start logging. Again, this code works, I see 12 on my screen.
You should be able to accomplish the desire effect without creating your own class. Just set the background of a TextView with an image drawable. See my example with a text in a speech bubble.
<TextView
android:id="#+id/textOverImage"
android:background="#drawable/speech_bubble"
android:text="#string/hello_world"
android:gravity="center"
android:layout_width="..."
android:layout_height="..."
/>

Categories

Resources