Custom view does not draw correctly - java

I am just trying to create a horizontal line. I have painted the view red and then added a blue line which I thought should take half the height.
Since I say that my custom view is 40 dp in height I would have thought that the blue bar with 20 dp in height would fill it half ways. But it doesn't. It takes 1/4 instead of 1/2. How can I fix that?
public class MyProgressView extends View {
public MyProgressView(Context context) {
super(context);
}
public MyProgressView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(dipToPx(20));
canvas.drawLine(0, 0, 300, 0, paint);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(parentWidth, dipToPx(40));
}
private int dipToPx(int dp) {
return (int) getResources().getDisplayMetrics().density * dp;
}
}
and XML:
<view
class="com.company.MyProgressView"
android:background="#ff0000"
android:layout_height="wrap_content"
android:layout_width="match_parent"
/>

In your case drawLine method draws a line on Y=0. This means your line's center position on Y is 0. If you set stroke width to 20, it is going to fill -10 and +10.
There are 2 different solutions:
You can set stroke with to 40(which will fill -20 and +20)
You can set your Y to 10 on your drawLine method(which will fill 0 and +20).

Related

How to add stroke on outside of TextView

Any ideas, code snippets are welcome!
I have created a CustomTextView class that extends AppCompatTextView and I did this to add stroke support to boring TextView. The problem is, Paint.Style.STROKE adds stroke on the inside of TextView. There should be something which allows us to choose between outer-stroke and inner-stroke.
P.S: I can share the complete CustomTextView class if needed, not a big deal.
What we currently have:
What we want to achieve:
This is the onDraw method from our CustomTextView which is used to add stroke to textView.
#Override
protected void onDraw(Canvas canvas) {
if(_strokeWidth > 0) {
//set paint to fill mode
Paint p = getPaint();
p.setStyle(Paint.Style.FILL);
//draw the fill part of text
super.onDraw(canvas);
//save the text color
int currentTextColor = getCurrentTextColor();
//set paint to stroke mode and specify
//stroke color and width
p.setStyle(Paint.Style.STROKE);
p.setStrokeWidth(_strokeWidth);
setTextColor(_strokeColor);
//draw text stroke
super.onDraw(canvas);
//revert the color back to the one
//initially specified
setTextColor(currentTextColor);
} else {
super.onDraw(canvas);
}
}
The following draws the outline of the characters in a TextView but takes care to clip out the characters themselves so they are not drawn over.
OutlineTextView.java
public class OutlineTextView extends androidx.appcompat.widget.AppCompatTextView {
private final Paint mOutlinePaint = new Paint();
private final Path mOutlinePath = new Path();
private float mStrokeWidth = 0f;
public OutlineTextView(Context context) {
super(context);
init();
}
public OutlineTextView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public OutlineTextView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mOutlinePaint.setStrokeWidth(0f);
mOutlinePaint.setStyle(Paint.Style.STROKE);
mOutlinePaint.setColor(Color.RED);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
float xOffset = getLayout().getLineLeft(0) + getPaddingLeft();
float baseline = getLayout().getLineBaseline(0) + getPaddingTop();
getPaint().getTextPath(getText().toString(), 0, getText().length(), xOffset, baseline, mOutlinePath);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mStrokeWidth > 0) {
canvas.save();
// The following insures that we don't draw inside the characters.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
canvas.clipPath(mOutlinePath, Region.Op.DIFFERENCE);
} else {
canvas.clipOutPath(mOutlinePath);
}
canvas.drawPath(mOutlinePath, mOutlinePaint);
canvas.restore();
}
}
public void setStrokeWidth(Float strokeWidth) {
mStrokeWidth = strokeWidth;
mOutlinePaint.setStrokeWidth(strokeWidth);
invalidate();
}
}

how do i achieve this type of edit text with xml

edit text image
I am a complete newbie to android. I am currently working on a note app and i need help in achieving this type of edit text view with xml. The references i av looked upon so far didnt appear clear to me.
protected void onDraw(Canvas canvas) {
// Gets the number of lines of text in the View.
int count = getLineCount();
// Gets the global Rect and Paint objects
Rect r = mRect;
Paint paint = mPaint;
/*
* Draws one line in the rectangle for every line of text in the EditText
*/
for (int i = 0; i < count; i++) {
// Gets the baseline coordinates for the current line of text
int baseline = getLineBounds(i, r);
/*
* Draws a line in the background from the left of the rectangle to the right,
* at a vertical position one dip below the baseline, using the "paint" object
* for details.
*/
canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
}
// Finishes up by calling the parent method
super.onDraw(canvas);
}
Create a class that extends an EditText
public class LinedEditText extends EditText {
private Rect mRect;
private Paint mPaint;
public LinedEditText(Context context, AttributeSet attrs)
{
super(context, attrs);
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0xFF000000); //change this to your color
}
/**
* This is called to draw the LinedEditText object
* #param canvas The canvas on which the background is drawn.
*/
#Override
protected void onDraw(Canvas canvas)
{
int height = canvas.getHeight();
int curHeight = 0;
Rect r = mRect;
Paint paint = mPaint;
int baseline = getLineBounds(0, r);
for (curHeight = baseline + 1; curHeight < height;
curHeight += getLineHeight())
{
canvas.drawLine(r.left, curHeight, r.right, curHeight, paint);
}
super.onDraw(canvas);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
invalidate();
}
}
In your XML
<your.package.name.LinedEditText
android:id="#+id/editText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top"
android:layout_margin="10dp"
android:background="#null"
android:inputType="textMultiLine|textNoSuggestions"
android:minLines="10"
android:singleLine="false"
android:imeOptions="actionNone"/>

Custom ImageView loses transparency as soon as it goes out of screen in recycleView

I have a custom ImageView that overrides onDraw method to crop corners using Path to give rounded corner of given radius. I have a RecyclerView where I have these custom ImageView in all of the 4 items. Now the problem is this custom ImageView renders fine for the first time it shows up in the list. Only 2 accommodate in the screen at a time. As I scroll down everything is fine in all views. I can see rounded corner in all of them. But when I scroll up to previous item. Now these corner lose their transparency in corners and become black in all but third item in the list. Canvas in onDraw also has isOpaque = true. I have tried many things but nothing seem to be working. Here is the code
public class RoundedImageView extends ImageView
{
private Paint mPaint;
private int mCornerRadius = 0;
private boolean mRoundedTopLeft = true, mRoundedBottomLeft = true, mRoundedTopRight = true, mRoundedBottomRight = true;
public void setCornerRadius(int mCornerRadius)
{
this.mCornerRadius = mCornerRadius;
}
public void RoundCorners(boolean isRoundedTopLeft, boolean isRoundedTopRight, boolean isRoundedBottomLeft, boolean isRoundedBottomRight)
{
mRoundedTopLeft = isRoundedTopLeft;
mRoundedBottomLeft = isRoundedBottomLeft;
mRoundedBottomRight = isRoundedBottomRight;
mRoundedTopRight = isRoundedTopRight;
}
public RoundedImageView(Context context)
{
super(context);
if (Build.VERSION.SDK_INT >= 11)
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
setupPaint();
}
public RoundedImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
if (Build.VERSION.SDK_INT >= 11)
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
setupPaint();
}
public RoundedImageView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
if (Build.VERSION.SDK_INT >= 11)
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
setupPaint();
}
#TargetApi(21)
public RoundedImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
if (Build.VERSION.SDK_INT >= 11)
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
setupPaint();
}
private Paint setupPaint()
{
mPaint = new Paint();
mPaint.setColor(Color.TRANSPARENT);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStrokeWidth(1);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
return mPaint;
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Path path = RoundedRect(0, 0, getWidth(), getHeight(), mCornerRadius, mCornerRadius,
mRoundedTopLeft, mRoundedTopRight, mRoundedBottomRight, mRoundedBottomLeft);
canvas.drawPath(path, mPaint);
}
public static Path RoundedRect(
float left, float top, float right, float bottom, float rx, float ry,
boolean tl, boolean tr, boolean br, boolean bl)
{
Path path = new Path();
if (rx < 0) rx = 0;
if (ry < 0) ry = 0;
float width = right - left;
float height = bottom - top;
if (rx > width / 2) rx = width / 2;
if (ry > height / 2) ry = height / 2;
float widthMinusCorners = (width - (2 * rx));
float heightMinusCorners = (height - (2 * ry));
path.moveTo(right, top + ry);
if (tr)
path.rQuadTo(0, -ry, -rx, -ry);//top-right corner
else
{
path.rLineTo(0, -ry);
path.rLineTo(-rx, 0);
}
path.rLineTo(-widthMinusCorners, 0);
if (tl)
path.rQuadTo(-rx, 0, -rx, ry); //top-left corner
else
{
path.rLineTo(-rx, 0);
path.rLineTo(0, ry);
}
path.rLineTo(0, heightMinusCorners);
if (bl)
path.rQuadTo(0, ry, rx, ry);//bottom-left corner
else
{
path.rLineTo(0, ry);
path.rLineTo(rx, 0);
}
path.rLineTo(widthMinusCorners, 0);
if (br)
path.rQuadTo(rx, 0, rx, -ry); //bottom-right corner
else
{
path.rLineTo(rx, 0);
path.rLineTo(0, -ry);
}
path.rLineTo(0, -heightMinusCorners);
path.close();//Given close, last lineto can be removed.
path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
return path;
}
}
What I have already tried:
- setting isRecyclable false in ViewHolder
setting different PorterDuff mode in paint
LAYER_TYPE_HARDWARE (hardware acceleration) for this view
setDrawingCacheBackgroundColor(0x00000000); in constructor
setLayerType(View.LAYER_TYPE_SOFTWARE, paint); passing paint in this function
tried making canvas transparent before super.onDraw()
UPDATE:
After a lot of fiddling around I have concluded that whenever my viewHolder's view goes out of screen. All it's alpha channel become black. I have a feeling it has to do something with the setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Worked by making the parent software accelerated.
I solved it by putting
if (Build.VERSION.SDK_INT >= 11)
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
in constructor of my ViewHolder. It worked even if I enabled software acceleration for any parent container view.
Now I don't know why this worked.

Using Seek Bar to zoom android Canvas

this is my first question in stackoverflow. any help would be very much appreciated.
I am trying zoom a canvas using a seek bar, so depend on the progress of the seekbar, the canvas will zoom in and zoom out.
here is my code in the main activity:
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// TODO Auto-generated method stub
Toast.makeText(MainActivity.this, "Seekbar Value : " + progress, Toast.LENGTH_SHORT).show();
CPaintView paint = (CPaintView) this.findViewById(R.id.cPaintView1);
paint.zoomX = (float)progress;
paint.zoomY = (float)progress;
paint.invalidate();
}
then it will pass the progress value to the CPaintView class which contain the canvas:
public class CPaintView extends View implements OnTouchListener {
final Paint paint = new Paint();
int color = Color.parseColor("#FF0000");
Bitmap offScreenBitmap;
Canvas offScreenCanvas;
final int CIRCLE = 0;
final int TRIANGLE = 1;
final int SQUARE = 2;
int shape = CIRCLE;
private int mActivePointerId;
float zoomX = 0;
float zoomY = 0;
public CPaintView(Context context) {
super(context);
setup();
}
public CPaintView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setup();
}
public CPaintView(Context context, AttributeSet attrs) {
super(context, attrs);
setup();
}
public void setup()
{
setOnTouchListener(this); // define event listener and start intercepting events
}
#Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
// draw the off screen bitmap
if(zoomX > 0)
{
canvas.save();
canvas.scale(zoomX, zoomY);
canvas.drawBitmap(offScreenBitmap, 0, 0, paint);
canvas.restore();
}
else
canvas.drawBitmap(offScreenBitmap, 0, 0, paint);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
// get the x,y coordinates of the MotionEvent.ACTION_MOVE event
int pointerIndex = 0;
for(int i = 0; i < event.getPointerCount(); i++)
{
mActivePointerId = event.getPointerId(i);
pointerIndex = event.findPointerIndex(mActivePointerId);
float x = event.getX(pointerIndex);
float y = event.getY(pointerIndex);
float y1 = y + 20;
float x2 = x + 20;
float y2 = y1 - 10;
paint.setColor(color);
float verts[] = {x, y, x, y1, x2, y2};
int[] vertsColors = {color, color, color, color, color, color};
switch(shape)
{
case CIRCLE:
offScreenCanvas.drawCircle(x, y, 10, paint); break;// draw a red circle at the x,y coordinates specified by the user
case TRIANGLE:
offScreenCanvas.drawVertices(Canvas.VertexMode.TRIANGLES, verts.length, verts, 0, null, 0, vertsColors, 0, null, 0, 0, paint);break;
case SQUARE:
offScreenCanvas.drawRect(x-10, y-10, x+10, y+10, paint); break;
}
invalidate();
}
return true;
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// create / re-create the off screen bitmap to capture the state of our drawing
// this operation will reset the user's drawing
offScreenBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
offScreenCanvas = new Canvas(offScreenBitmap);
}
}
I am having problem with the onDraw method. The problem is, when I increase the seekbar progress, the whole canvas disappear and I try to draw on it, it seems like it doesn't draw. But when I decrease the seekbar progress back to 0, all the drawing comes back and I can see it again, including the drawing that seems like not working.
I am sorry for my bad english.
Thank you very much.
Your problem is conceptual with how scale works. Scaling at 0,0 is undefined. Scaling at 1f,1f is the default. The scaling isn't working much at all,
if(zoomX > 0)
{
canvas.save();
canvas.scale(zoomX, zoomY);
canvas.drawBitmap(offScreenBitmap, 0, 0, paint);
canvas.restore();
}
else
canvas.drawBitmap(offScreenBitmap, 0, 0, paint);
if the zoom is 0, then it'll properly draw the scaled bitmap. Because it'll skip your zoom step. else you have a scaling of like 50 which is basically going to maybe have the bitmap be a dot. Try getting the zoom to hover around 1. Something like zoom = 12 / (1 + progress) Then the zoom can range from like 12x to values less than zero. Your drawing seems like it should be there, it's just WAAAAY small. Except at zero where you skip that step and make it 1.

Scrolling over large canvas

I need some help understanding the fundamentals of scrolling over items that are drawn to a canvas in Android. Suppose I want to create a timeline where time at 0 is the top of a visualization and as time increased the timeline continues to be rendered below the previous point. If I wish to render this on Android I know I could simply create a bunch of items on a canvas by overriding onDraw(). However, let's suppose the visualization is bigger than the screen allows.
For example in the first picture below the large black box contains the entire canvas as I render it. I create a blue line that runs vertically up and down as well as several yellow, green and blue rectangles. The red box represents the screen of the Android that is rendering the visualization. As it initially opens all items are drawn but only the items contained within the red box show up on the screen.
Now if the user is to scroll down, items that initially appeared below the red box are in view while items that have gone out of the confines of the red box are no longer visable, as represented in the second picture.
I believe I need to use scrollables but I'm quite lost how to do so. I've read over this page http://developer.android.com/training/custom-views/custom-drawing.html explaining how to create your own customer images and this page http://developer.android.com/training/custom-views/making-interactive.html explaining how to make the UI interactive, but I think I'm missing something.
A sample code that illustrates this problem (this is basic, assume there is logic dictating WHERE the boxes/lines go, etc.) is as follows:
package com.example.scrolltest;
import com.example.scrolltest.Draw;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Color;
public class MainActivity extends Activity {
Draw draw;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
draw = new Draw(this);
draw.setBackgroundColor(Color.WHITE);
setContentView(draw);
}
}
and
package com.example.scrolltest;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
public class Draw extends View {
Paint paint = new Paint();
public Draw(Context context) {
super(context);
}
#Override
public void onDraw(Canvas canvas) {
paint.setColor(Color.GREEN);
canvas.drawRect(30, 30, 90, 200, paint);
paint.setColor(Color.BLUE);
canvas.drawLine(100, 20, 100, 1900, paint);
paint.setColor(Color.GREEN);
canvas.drawRect(200, 2000, 400, 3000, paint);
}
}
What I cannot figure out though, is how I use a scrollable to scroll down to the rectangles that are off of the screen. I'm also unsure if I started this correctly or should use drawables instead...
Simple Method (If the height required is not very large).
Use a ScrollView and add your Draw view in it. Compute the required height for that view in onMeasure.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
draw = new Draw(this);
draw.setBackgroundColor(Color.WHITE);
ScrollView scrollView = new ScrollView(this);
scrollView.addView(draw);
setContentView(scrollView);
}
public class Draw extends View {
Paint paint = new Paint();
public Draw(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Compute the height required to render the view
// Assume Width will always be MATCH_PARENT.
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = 3000 + 50; // Since 3000 is bottom of last Rect to be drawn added and 50 for padding.
setMeasuredDimension(width, height);
}
#Override
public void onDraw(Canvas canvas) {
paint.setColor(Color.GREEN);
canvas.drawRect(30, 30, 90, 200, paint);
paint.setColor(Color.BLUE);
canvas.drawLine(100, 20, 100, 1900, paint);
paint.setColor(Color.GREEN);
canvas.drawRect(200, 2000, 400, 3000, paint);
}
}
An alternative can be to use offset X/Y values.
How you handle the offset values is up to you, all though I prefer using a class I call Camera. The access should be static.
public void render(Canvas c){
c.drawRect(100 - offsetX, 100 - offsetY, 120 - offsetX, 120 - offsetY, Paints.BLUE);
}
And to pan the canvas:
float x, y;
#Override
public boolean onTouchEvent(MotionEvent ev) {
if(ev.getAction() == MotionEvent.ACTION_DOWN){
x = ev.getX();
y = ev.getY();
}else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
float currX = ev.getX();
float currY = ev.getY();
float newOffsetX = (x - currX),
newOffsetY = (y - currY);
//The next two if-statements are to add sensitivity to the scrolling.
//You can drop these if you don't plan on having touch events
//with pressing. Pressing works without this as well, but the canvas may move slightly
//I use DP to handle different screen sizes but it can be replaced with pixels. c is a context-instance
if (newOffsetY < Maths.convertDpToPixel(2, c) && newOffsetY > -Maths.convertDpToPixel(2, c))
newOffsetY = 0;
if (newOffsetX < Maths.convertDpToPixel(2, c) && newOffsetX > -Maths.convertDpToPixel(2, c))
newOffsetX = 0;
offsetX += newOffsetX;
offsetY += newOffsetY;
x = ev.getX();
y = ev.getY();
}
return true;
}
And a sample Camera-class:
public class Camera{
public static float offsetX, offsetY;
//The constructor. ix and iy is the initial offset. Useful if you are creating a game and need to change the initial offset to center around a starting position.
//Most of the time it will be enough to set the values to 0
public Camera(float ix, float iy){
this.offsetX = ix;
this.offsetY = iy;
}
}

Categories

Resources