How do I properly add a custom view to an activity - android

I'm trying to build a custom view but for some reason it's not displaying at all.
To save you reading both constructors, I am calling the View constructor without the attr parameter because these should be fetched from the Layout file. Any values that are not fetched from here are set in the view class itself.
My view class:
package mrl233.campustour.AugmentedReality;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.hardware.SensorManager;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import org.w3c.dom.Text;
import java.util.ArrayList;
import java.util.List;
import mrl233.campustour.R;
/**
* TODO: document your custom view class.
*/
public class CameraOverlay extends View {
private float mAzimuth;
private float mPitch;
private float mRoll;
private String mTextString;
private int mTextColor = Color.RED;
private float mTextDimension = 80;
private Drawable mTextDrawable;
private float mTextSize = 29;
private TextPaint mTextPaint;
private float mTextHeight = 0;
private float mTextWidth;
public CameraOverlay(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.CameraOverlay,
0, 0);
try {
mTextString = a.getString(R.styleable.CameraOverlay_exampleString);
mAzimuth = a.getFloat(R.styleable.CameraOverlay_exampleFloat_X, 0);
mPitch = a.getFloat(R.styleable.CameraOverlay_exampleFloat_Y, 0);
mRoll = a.getFloat(R.styleable.CameraOverlay_exampleFloat_Z, 0);
} finally {
a.recycle();
}
init();
}
public CameraOverlay(Context con, float azimuth, float pitch, float roll) {
this(con,null);
this.mAzimuth = azimuth;
this.mPitch = pitch;
this.mRoll = roll;
TypedArray a = con.getTheme().obtainStyledAttributes(
null,
R.styleable.CameraOverlay,
0, 0);
try {
mTextString = a.getString(R.styleable.CameraOverlay_exampleString);
mAzimuth = a.getFloat(R.styleable.CameraOverlay_exampleFloat_X, 0);
mPitch = a.getFloat(R.styleable.CameraOverlay_exampleFloat_Y, 0);
mRoll = a.getFloat(R.styleable.CameraOverlay_exampleFloat_Z, 0);
} finally {
a.recycle();
}
init();
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
invalidate();
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
int contentWidth = getWidth() - paddingLeft - paddingRight;
int contentHeight = getHeight() - paddingTop - paddingBottom;
canvas.drawText("wsfsefseefsfsef",
paddingLeft + (contentWidth - mTextWidth),
paddingTop + (contentHeight + mTextHeight)
,mTextPaint);
}
}
This is my view layout:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:id="#+id/overlay" >
<mrl233.campustour.AugmentedReality.CameraOverlay
android:background="#ccc"
android:layout_width="300dp" android:layout_height="300dp" android:paddingLeft="20dp"
android:paddingBottom="40dp" custom:exampleDimension="24sp" custom:exampleColor="#33b5e5"
custom:exampleString="Hello, CameraOverlay"
custom:exampleFloat_X="0.1"
custom:exampleFloat_Y="0.5"
custom:exampleFloat_Z="1"/>
</FrameLayout>
I am adding this view to to an activity which has it's own view. This is the Activity class's onCreate method which is where I try to add the view.
#Override
#SuppressWarnings("deprecation")
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content_augment);
preview = (FrameLayout) findViewById(R.id.camera_preview);
mPreview = new CameraPreview(this, camera);
mCameraOverlay = new CameraOverlay(this, 0, 0, 0);
preview.addView(mPreview);
preview.addView(mCameraOverlay);
preview.bringChildToFront(mCameraOverlay);
}
The layout of this activity class:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" tools:showIn="#layout/augment"
tools:context="mrl233.campustour.Activities.Augment">
<FrameLayout
android:id="#+id/camera_preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
<!--<SurfaceView-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="match_parent"-->
<!--android:id="#+id/surfaceView"/>-->
<mrl233.campustour.AugmentedReality.CameraOverlay
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
</RelativeLayout>

I see two problems here. You shouldn't be calling invalidate() in onDraw method cause it causes view to redraw itself(infinite loop). Secondly getWidth() might be 0 there. You might want to get canvas width from onSizeChanged method
private int width;
private int height;
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
...
int contentWidth = width - paddingLeft - paddingRight;
...
}
try playing with hardcoded sizes cause currently you might be drawing that text offscreen or giving it too little space(it might help you find the problem).
Smallest customView I can Think of would be:
public class CustomView extends View {
private TextPaint paint;
public CustomView(Context context) {
super(context);
paint = new TextPaint(Paint.LINEAR_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.RED);
paint.setTextSize(20);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText("Hello", 20, 20, paint);
}
}
and init it:
preview.addView(new CustomView(this));
There are few more smaller problems but it might get you going

Related

Android Canvas Path to Bitmap

I have an bitmap that I want to apply an alpha mask to. I know I can easily use Porter Duff mode to do that with another bitmap, but I want to programmatically create the alpha mask because it needs to change continually. So, I'm trying to use a path to create it, but I can't make the connection between the finished path and creating the bitmap to use from that path.
Here is my 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"
tools:context="${relativePackage}.${activityClass}" >
<SeekBar
android:id="#+id/seekBar_percent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:progress="50" />
<ImageView
android:id="#+id/imageView_canvas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="#+id/seekBar_percent"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
Here is my main activity:
package com.wheeler.daniel.xxx;
import android.app.Activity;
import android.os.Bundle;
import android.view.Display;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
public class MainActivity extends Activity {
PathRectangle alphaPath;
ImageView canvas;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);
canvas = (ImageView) findViewById(R.id.imageView_canvas);
canvas.post(new Runnable()
{
#Override
public void run() {
Display display = getWindowManager().getDefaultDisplay();
int canvasWidth = display.getWidth();
int canvasHeight = display.getHeight();
alphaPath = new PathRectangle(getApplicationContext(), canvas, canvasWidth, canvasHeight);
}
});
SeekBar percentBar = (SeekBar) findViewById(R.id.seekBar_percent);
percentBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser){
alphaPath.updatePercent(progress);
}
public void onStartTrackingTouch(SeekBar seekBar) {
}
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
}
I created a class to draw my path (a basic rectangle to cover the image) called PathRectangle:
public class PathRectangle extends View {
ImageView layoutCanvas;
Bitmap canvasBitmap;
Paint paint;
Path path;
float width;
float height;
int percent;
Canvas tempCanvas;
public PathRectangle(Context context, ImageView canvasImage, float width, float height)
{
super(context);
layoutCanvas = canvasImage;
path = new Path();
paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL);
this.width = width;
this.height = height;
percent = 50;
Bitmap.Config config = Bitmap.Config.ARGB_8888;
canvasBitmap = Bitmap.createBitmap((int)width, (int)height, config);
tempCanvas = new Canvas( canvasBitmap );
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
path.reset();
path.moveTo(0,0);
path.lineTo(percent, 0);
path.lineTo(percent, height);
path.lineTo(0, height);
tempCanvas.drawPath(path, paint);
layoutCanvas.setImageBitmap(canvasBitmap);
}
public void updatePercent(int newPercent)
{
percent = (int) (width * (newPercent * .01));
invalidate();
}
}
This is just my testing to generate the Bitmap, but nothing is displayed in my ImageView. I feel like by drawing the path to the tempCanvas isn't actually drawing it to the bitmap I used in the creation of the tempCanvas.
Example of what I want:
Source Bitmap
Alpha Mask to create
Result

Custom Android View displaying differently in Linear and Relative Layouts

I've created a custom view (ArrowContainer) to wrap around other elements giving them an arrow shaped background. However, my view displays differently when contained in a Relative Layout to how it displays when contained in a Linear Layout.
Here is the problem, the top ArrowContainer is contained in a LinearLayout and behaves correctly, the bottom ArrowContainer is contained in a RelativeLayout and behaves incorrectly.
Has anyone seen something like this before? The debug code I have inserted in ArrowContainer.java suggests that the problem arises from RelativeLayout measuring the view twice, but I'm not sure why this causes a problem...
Below is the code:
ArrowContainer.java
package com.example.arrowcontainertest;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
public class ArrowContainer extends ViewGroup {
private static final int ARROW_LEFT = 0;
private static final int ARROW_RIGHT = 1;
private static final int ARROW_BOTH = 2;
private static final int DEFAULT_COLOUR = 0xFFFF0000;
private static final int HORIZONTAL_PADDING = 150;
private Path path;
private Paint paint;
private int arrowSide = ARROW_RIGHT;
private int colour = DEFAULT_COLOUR;
private int downColour;
private Paint downPaint;
private Boolean isButton = false;
private View child;
public ArrowContainer(Context context) {
super(context);
init();
}
public ArrowContainer(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArrowContainer, 0, 0);
try {
arrowSide = a.getInteger(R.styleable.ArrowContainer_arrowSide, ARROW_RIGHT);
colour = a.getColor(R.styleable.ArrowContainer_colour, DEFAULT_COLOUR);
isButton = a.getBoolean(R.styleable.ArrowContainer_isButton, false);
} finally {
a.recycle();
}
init();
}
public ArrowContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArrowContainer, 0, 0);
try {
arrowSide = a.getInteger(R.styleable.ArrowContainer_arrowSide, ARROW_RIGHT);
colour = a.getColor(R.styleable.ArrowContainer_colour, DEFAULT_COLOUR);
isButton = a.getBoolean(R.styleable.ArrowContainer_isButton, false);
} finally {
a.recycle();
}
init();
}
private void init() {
paint = new Paint();
paint.setColor(colour);
paint.setStyle(Style.FILL);
setWillNotDraw(false);
if (isButton) {
setFocusable(true);
setClickable(true);
downColour = 0xFF00FF00;
downPaint = new Paint();
downPaint.setColor(downColour);
downPaint.setStyle(Style.FILL);
}
}
#Override
protected void onFinishInflate() {
// Must have exactly 1 child
assert getChildCount()==1;
if (getChildCount() == 1) {
child = getChildAt(0);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Debug
Log.e("DEBUG", "Type:" + getParent().getClass());
Log.e("DEBUG", "Width Mode: " + MeasureSpec.getMode(widthMeasureSpec));
Log.e("DEBUG", "Height Mode: " + MeasureSpec.getMode(heightMeasureSpec));
Log.e("DEBUG", "Width Size: " + MeasureSpec.getSize(widthMeasureSpec));
Log.e("DEBUG", "Height Size: " + MeasureSpec.getSize(heightMeasureSpec));
// Restrict the childs width to at most this components size minus a fixed value (HORIZONTAL_PADDING*numArrows)
int numArrows=0;
switch (arrowSide) {
case ARROW_RIGHT:
numArrows = 1;
break;
case ARROW_LEFT:
numArrows = 1;
break;
case ARROW_BOTH:
numArrows = 2;
break;
}
int widthSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec)-HORIZONTAL_PADDING*numArrows, MeasureSpec.AT_MOST);
int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
child.measure(widthSpec, heightSpec);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
setMeasuredDimension(width + (int) (numArrows*height/2f), height);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
switch (arrowSide) {
case ARROW_RIGHT:
// Hug left
child.layout(0, height/2 - childHeight/2, width - height/2, height/2 + childHeight/2);
break;
case ARROW_LEFT:
// Hug right
child.layout(height/2, height/2 - childHeight/2, width, height/2 + childHeight/2);
break;
case ARROW_BOTH:
// Center
child.layout(width/2 - childWidth/2, height/2 - childHeight/2, width/2 + childWidth/2, height/2 + childHeight/2);
break;
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
path = new Path();
switch (arrowSide) {
case ARROW_RIGHT:
path.lineTo(0, h);
path.lineTo(w-h/2f, h);
path.lineTo(w, h/2f);
path.lineTo(w-h/2f, 0);
break;
case ARROW_LEFT:
path.moveTo(h/2f, 0);
path.lineTo(0, h/2f);
path.lineTo(h/2f, h);
path.lineTo(w, h);
path.lineTo(w, 0);
break;
case ARROW_BOTH:
path.moveTo(h/2f, 0);
path.lineTo(0, h/2f);
path.lineTo(h/2f, h);
path.lineTo(w-h/2f, h);
path.lineTo(w, h/2f);
path.lineTo(w-h/2f, 0);
break;
}
path.close();
}
#Override
protected void onDraw(Canvas canvas) {
invalidate();
if (isPressed()) {
canvas.drawPath(path, downPaint);
} else {
canvas.drawPath(path, paint);
}
super.onDraw(canvas);
}
}
activity_main.xml
<LinearLayout 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:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" xmlns:app="http://schemas.android.com/apk/res/com.example.arrowcontainertest">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.example.arrowcontainertest.ArrowContainer
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:arrowSide="right">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play"
android:textSize="50sp"/>
</com.example.arrowcontainertest.ArrowContainer>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.example.arrowcontainertest.ArrowContainer
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:arrowSide="right">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play"
android:textSize="50sp"/>
</com.example.arrowcontainertest.ArrowContainer>
</RelativeLayout>
MainActivity.java
package com.example.arrowcontainertest;
import android.os.Bundle;
import android.app.Activity;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
An update:
I've been unable to solve this problem and this component has been causing problems in other situations. As such I decided to rewrite the component to use a little custom functionality as possible.
My solution has been to create a custom LinearLayout which contains a nested LinearLayout. The outer layout is responsible for drawing the background, and applies sufficient padding to allow space to draw the arrows. All children get passed to the inner layout. This solution isn't perfect as there is often excess padding and so wasted space, but it is sufficient for my purposes.
Code is here:
package com.example.arrowcontainertest;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
public class NewArrowContainer extends LinearLayout {
private static final int ARROW_LEFT = 0;
private static final int ARROW_RIGHT = 1;
private static final int ARROW_BOTH = 2;
private static final int DEFAULT_COLOUR = 0xFFFF0000;
private static final int ARROW_MAX_WIDTH = 150;
private LinearLayout childLayout;
private Path path;
private Paint paint;
private int arrowSide = ARROW_RIGHT;
private int colour = DEFAULT_COLOUR;
private int downColour;
private Paint downPaint;
private Boolean isButton = false;
public NewArrowContainer(Context context) {
super(context);
init();
}
public NewArrowContainer(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArrowContainer, 0, 0);
try {
arrowSide = a.getInteger(R.styleable.ArrowContainer_arrowSide, ARROW_RIGHT);
colour = a.getColor(R.styleable.ArrowContainer_colour, DEFAULT_COLOUR);
isButton = a.getBoolean(R.styleable.ArrowContainer_isButton, false);
} finally {
a.recycle();
}
init();
}
public NewArrowContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArrowContainer, 0, 0);
try {
arrowSide = a.getInteger(R.styleable.ArrowContainer_arrowSide, ARROW_RIGHT);
colour = a.getColor(R.styleable.ArrowContainer_colour, DEFAULT_COLOUR);
isButton = a.getBoolean(R.styleable.ArrowContainer_isButton, false);
} finally {
a.recycle();
}
init();
}
private void init() {
paint = new Paint();
paint.setColor(colour);
paint.setStyle(Style.FILL);
setWillNotDraw(false);
if (isButton) {
setFocusable(true);
setClickable(true);
downColour = 0xFF00FF00;
downPaint = new Paint();
downPaint.setColor(downColour);
downPaint.setStyle(Style.FILL);
}
LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutInflater.inflate(R.layout.arrow_container, this);
childLayout = (LinearLayout) findViewById(R.id.child);
// Pass properties to childLayout
childLayout.setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom());
childLayout.setOrientation(getOrientation());
// Give the padding sufficient for arrows
switch (arrowSide) {
case ARROW_RIGHT:
setPadding(0, 0, ARROW_MAX_WIDTH, 0);
break;
case ARROW_LEFT:
setPadding(ARROW_MAX_WIDTH, 0, 0, 0);
break;
case ARROW_BOTH:
setPadding(ARROW_MAX_WIDTH, 0, ARROW_MAX_WIDTH, 0);
break;
}
}
public void setColour(int colour) {
paint.setColor(colour);
}
#Override
public void onFinishInflate() {
// Pass all children to the childLayout
while (getChildCount() > 1) {
View v = getChildAt(1);
removeViewAt(1);
childLayout.addView(v);
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
path = new Path();
switch (arrowSide) {
case ARROW_RIGHT:
path.lineTo(0, h);
path.lineTo(w-ARROW_MAX_WIDTH, h);
path.lineTo(w-ARROW_MAX_WIDTH+h/2f, h/2f);
path.lineTo(w-ARROW_MAX_WIDTH, 0);
break;
case ARROW_LEFT:
path.moveTo(ARROW_MAX_WIDTH-h/2f, h/2f);
path.lineTo(ARROW_MAX_WIDTH, h);
path.lineTo(w, h);
path.lineTo(w, 0);
path.lineTo(ARROW_MAX_WIDTH, 0);
break;
case ARROW_BOTH:
path.moveTo(ARROW_MAX_WIDTH-h/2f, h/2f);
path.lineTo(ARROW_MAX_WIDTH, h);
path.lineTo(w-ARROW_MAX_WIDTH, h);
path.lineTo(w-ARROW_MAX_WIDTH+h/2f, h/2f);
path.lineTo(w-ARROW_MAX_WIDTH, 0);
path.lineTo(ARROW_MAX_WIDTH, 0);
break;
}
path.close();
}
#Override
protected void onDraw(Canvas canvas) {
invalidate();
if (isPressed()) {
canvas.drawPath(path, downPaint);
} else {
canvas.drawPath(path, paint);
}
super.onDraw(canvas);
}
}
MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec)-HORIZONTAL_PADDING*numArrows, MeasureSpec.AT_MOST);
I am concerned about this line, why you don't check if width, supplied to makeMeasureSpec, isn't negative? This method doesn't perform range check, so it your responsibility. Negative width = invalid measureSpec = undefined behavior.
Also, when i have implemented some custom layouts, i've used super.onMeasure to determine maximum available dimensions and then used them via getMeasuredWidth(), getMeasuredHeight().

onDraw not firing when created

My custom view's onDraw() is not being fired. Why? I am using 2.3.3. Shouldn't the view automatically draw when it gets created? The onMeasure() method is being fired and I get the correct screen width and height.
package com.example.assignment2b;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import android.view.Display;
import android.view.Menu;
import android.view.View;
import android.view.WindowManager;
public class AcesActivity extends Activity {
private static final String TAG = "DEBUG";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
HeartView heartView = new HeartView(this);
setContentView(heartView);
heartView.invalidate();
}
class HeartView extends View {
private int screenWidth;
private int screenHeight;
private Context context;
public HeartView(Context context) {
super(context);
this.context = context;
}
#Override
public void onDraw(Canvas canvas) {
Log.println(Log.DEBUG, TAG, "onDraw fired");
int cardWidth = screenWidth - 10;
int cardHeight = (int) (cardWidth * 1.4);
canvas.drawColor(Color.DKGRAY);
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
canvas.drawRect(5, (screenHeight/2) - (cardHeight/2), screenWidth - 5, (screenHeight/2) + (cardHeight/2), paint);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(5, (screenHeight/2) - (cardHeight/2), screenWidth - 5, (screenHeight/2) + (cardHeight/2), paint);
}
#Override
public void onMeasure(int width, int height) {
Log.println(Log.DEBUG, TAG, "onMeasure fired");
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
screenWidth = display.getWidth();
screenHeight = display.getHeight();
setMeasuredDimension(screenWidth, screenHeight);
Log.println(Log.DEBUG, TAG, "screenWidth = " + screenWidth);
Log.println(Log.DEBUG, TAG, "scrrenHeight = " + screenHeight);
super.onMeasure(screenWidth, screenHeight);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
}
A few different issues going on. First as #dragonwrenn suggested, without a ViewGroup and layout params, your view has no intrinsic bounds. The call to invalidate() is unnecessary, but not problematic.
public class AcesActivity extends Activity {
private static final String TAG = "DEBUG";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RelativeLayout rl = new RelativeLayout(this);
RelativeLayout.LayoutParams params = new
RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
setContentView(rl);
HeartView heartView = new HeartView(this);
heartView.setLayoutParams(params);
rl.addView(heartView);
}
class HeartView extends View {
private int screenWidth;
private int screenHeight;
private Context context;
// Moved a couple of variable declarations out of the onDraw method
Paint paint;
int cardWidth;
int cardHeight;
public HeartView(Context context) {
super(context);
this.context = context;
paint = new Paint();
}
Second, your math for the rects drew them beyond the borders of the screen. I put in some hard numbers just as a test. You will want to rework your numbers to get what you really want. Also, your stroke had no width, so note
#Override
public void onDraw(Canvas canvas) {
Log.println(Log.DEBUG, TAG, "onDraw fired");
canvas.drawColor(Color.DKGRAY);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
canvas.drawRect(10, 10, 50, 50, paint);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
canvas.drawRect(10, 10, 50, 50, paint);
}
Third, I'm not sure what everything in onMeasure was trying to achieve, but it was causing issues for the view. You shouldn't need more than what's below:
#Override
public void onMeasure(int width, int height) {
Log.println(Log.DEBUG, TAG, "onMeasure fired");
screenWidth = width;
screenHeight = height;
Log.println(Log.DEBUG, TAG, "screenWidth = " + screenWidth);
Log.println(Log.DEBUG, TAG, "scrrenHeight = " + screenHeight);
super.onMeasure(screenWidth, screenHeight);
cardWidth = screenWidth - 10;
cardHeight = (int) (cardWidth * 1.4);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
}
Edit: note to original poster, the above is functional code tested in Eclipse. If each section is copied and pasted exactly, it will work.
Do the below changes in your onMeasure() method:
Declare the display metrics in your class.
private DisplayMetrics m_metrics;
#Override
public void onMeasure(int width, int height) {
Log.println(Log.DEBUG, TAG, "onMeasure fired");
Display m_display = getWindowManager().getDefaultDisplay();
m_metrics = context.getResources().getDisplayMetrics();
m_display.getMetrics(m_metrics);
screenWidth = m_display.getWidth();
screenHeight = m_display.getHeight();
super.onMeasure(screenWidth, screenHeight);
}

Android Progressbar with circle radius changes Dynamically

I want to change the circle radius dependent on the slider position
I created both individually.
while apply the circle method to the the Progress bar.
facing error.
Actually I call the Progress-Bar(Seek Bar) with
setContentView(R.layout.main);
and for drawing circle as you know I have to used setContentView(demoview);
Query: I want to merge both layout as display into image.
I didn't have idea will it possible or not?
Any guidance,tutorial appreciable.
Thanks for give your valuable time for my query.
Hope you have solution.
Check this out..
I am not using android seekbar, instead i am drawing a similar seekbar on canvas..
Check if this code can help u..
package com.test;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
public class SampleComp extends View implements OnTouchListener {
private Paint paint = null;
private int width = 0;
private int height = 0;
private int barStartX = 20;
private int barStartY = 20;
private int barEndX;
private int barEndY = 30;
private int radius;
private int heightAvailableForCircle;
private int widthAvailableForCircle;
private int maxRadius;
private int totalSeekBarLength;
private int currentSeekBarLength = 10;
private int whatPercentOfSeekBarIsSelected = 50;
public SampleComp(Context context) {
super(context);
paint = new Paint();
paint.setAntiAlias(true);
setOnTouchListener(this);
}
public SampleComp(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
paint.setAntiAlias(true);
}
public SampleComp(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
paint = new Paint();
paint.setAntiAlias(true);
}
#Override
public void onDraw(Canvas canvas){
barEndX = getWidth() - 20;
paint.setColor(Color.WHITE);
setWidth(canvas.getWidth());
setHeight(canvas.getHeight());
setHeightAvailableForCircle(getViewHeight() - barEndY);
setWidthAvailableForCircle(getViewWidth() - 40);
System.out.println("heightAvailableForCircle: "+getAvailableHeightForCircle());
System.out.println("widthAvailableForCircle: "+getWidthAvailableForCircle());
totalSeekBarLength = barEndX - barStartX;
System.out.println("SEEK LEN: "+totalSeekBarLength);
canvas.drawRect(barStartX, barStartY, barEndX, barEndY, paint);
paint.setColor(Color.BLUE);
setMaxRadius(heightAvailableForCircle, widthAvailableForCircle);
whatPercentOfSeekBarIsSelected = getSelectedSeekBarPercentage(totalSeekBarLength,getCurrentSeekBarLenghtSelected());
System.out.println("whatPercentOfSeekBarIsSelected: "+whatPercentOfSeekBarIsSelected);
System.out.println("!!!!!: "+canvas.getWidth());
System.out.println("####: "+getViewWidth());
System.out.println("^^^^^^^^^************: "+ (whatPercentOfSeekBarIsSelected * (getViewWidth() - 40)) / 100);
canvas.drawRect(barStartX, barStartY, ( (whatPercentOfSeekBarIsSelected * (getViewWidth() - 40)) / 100) + 20,
barEndY, paint);
paint.setColor(Color.GRAY);
setRadius(whatPercentOfSeekBarIsSelected);
canvas.drawCircle( (canvas.getWidth())/2, (canvas.getHeight() - 30)/2, radius, paint);
}
private void setRadius(int per){
this.radius = (getMaxRadius() * per)/100;
}
private int getSelectedSeekBarPercentage(int total, int current){
int per = 0;
per = ( (current * 100) / total);
return per;
}
private void setRadius(int total, int current){
System.out.println("total: "+total);
System.out.println("current: "+current);
this.radius = ( ( (getMaxRadius()/2) * current) / 100);
System.out.println("radius: "+this.radius);
}
private void setMaxRadius(int h, int w){
this.maxRadius = h < w ? h/2 : w/2 ;
}
private int getMaxRadius(){
return this.maxRadius;
}
private void setWidth(int w){
this.width = w;
}
private void setHeight(int h){
this.height = h;
}
private int getViewWidth(){
return this.width;
}
private int getViewHeight() {
return this.height;
}
private void setHeightAvailableForCircle(int availableHeightForCircle){
this.heightAvailableForCircle = availableHeightForCircle;
}
private int getAvailableHeightForCircle(){
return this.heightAvailableForCircle;
}
private void setWidthAvailableForCircle(int wid){
this.widthAvailableForCircle = wid;
}
private int getWidthAvailableForCircle(){
return this.widthAvailableForCircle;
}
private void setCurrentSeekBarLength(int x){
this.currentSeekBarLength = x;
}
private int getCurrentSeekBarLenghtSelected(){
return this.currentSeekBarLength;
}
#Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
System.out.println("x: "+x);
System.out.println("y: "+y);
if(x >= 10 && x<= barEndX && y >= 10 && y <= 30){
System.out.println("TRUE");
setCurrentSeekBarLength(x - 20);
invalidate();
}
return false;
}
}
And this is my Activity class:
package com.test;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.widget.ImageView;
public class SampleActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleComp(this));
}
}
Can u be little more specific regarding what u want to achieve. Ur question isn't that clear. What i can make out is, u want to increase/decrease the radius of the circle based on the progress bar value.
Have a look at the android custom component guide. You basically have to derive your circle component from android.view.View. Then you can add it to your layout like every other component.

custom View not displayed well in android

My custom view does not display entirely. Please see my screenshot:
And the source code
package com.dots;
import android.graphics.Color;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
public class Dots1Activity extends Activity
{
private static final String TAG = "DotsActivity";
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout ll = new LinearLayout(this);
ll.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
CustomDrawableView view1 = new CustomDrawableView(this, 50, 50, Constants.DOTS_RADIUS, Constants.DOTS_COLOR);
CustomDrawableView view2 = new CustomDrawableView(this, 150, 150, Constants.DOTS_RADIUS, Constants.DOTS_COLOR);
CustomDrawableView view3 = new CustomDrawableView(this, 300, 300, Constants.DOTS_RADIUS, Constants.DOTS_COLOR);
ll.addView(view1, layoutParams);
ll.addView(view2, layoutParams);
ll.addView(view3, layoutParams);
setContentView(ll);
}
}
class CustomDrawableView extends View implements View.OnClickListener{
private Context context;
private int x, y, radius, color;
public CustomDrawableView(Context context, int x, int y, int radius, int color) {
super(context);
this.context = context;
this.x = x;
this.y =y;
this.radius = radius;
this.color = color;
setOnClickListener(this);
}
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(color);
canvas.drawCircle(x, y, radius, paint);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(View.MeasureSpec.makeMeasureSpec(Constants.DOTS_RADIUS*2, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(Constants.DOTS_RADIUS*2, View.MeasureSpec.EXACTLY));
}
public void onClick(View v) {
Toast.makeText(this.context,
x+"-"+y+"-"+radius,
Toast.LENGTH_SHORT).show();
}
}
public interface Constants
{
public static final int DOTS_RADIUS = 50;
public static final int DOTS_COLOR = Color.GREEN;
public static final int NUM_DOTS_ROWS = 5;
public static final int NUM_DOTS_COLS = 5;
public static final int WIDTH_BETWEEN_DOTS = 100;
public static final int HEIGHT_BETWEEN_DOTS = 100;
}
Making the assumption that you don't want the clipping you see in your screenshot. Your problem is that the values you return in onMeasure don't account for your x, y offsets:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(View.MeasureSpec.makeMeasureSpec(Constants.DOTS_RADIUS*2 + x, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(Constants.DOTS_RADIUS*2 + y, View.MeasureSpec.EXACTLY));
}
What exactly do you want to Achieve? if you want to be fullscreen then use the FILL_PARENT flag instead of WRAP_CONTENT at least for the width of your view. also for the height there is a weight parameter that might help even the height of your view. but since its a custom drawing i cant help you further if there are any adjustments needed in your view code. you have to figure that out for yourself.
The radius of each of your "dots" is identical, and this directly translates into the answer you return in onMeasure(). You're changing the x and y location of the center, getting further from the actual View canvas.

Categories

Resources