I have made a custom view - it extends View. In the onDraw() method I create a circle with a set radius. At the moment in my xml, I have the layout_width and layout_height set to wrap_content. The circle is the right size, but when I set an onClickListener I don't have to touch the circle for it to register. I can tap anywhere where there is no other view.
I think I need to do something with onMeasure or LayoutParams but I don't know what exactly.
The aim is for the onClickListener to only be called when I click the circle with the layout_width and height still being set to wrap_content.
EDIT:
This creates a square not a circle as I wanted.
Here is my code:
protected void onDraw(Canvas canvas) {
canvas.drawCircle(canvas.getWidth() /2 , canvas.getHeight() /2,
RADIUS, paint);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
widthMeasureSpec = RADIUS;
heightMeasureSpec = RADIUS;
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
Try this:
float mTranslateX;
float mTranslateY;
public void onDraw(Canvas canvas){
super.onDraw(canvas);
canvas.save();
canvas.translate(mTranslateX, mTranslateY);
canvas.drawCircle(0, 0, RADIUS, paint);
canvas.restore();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int dia = RADIUS * 2;
int w = resolveSize(dia, widthMeasureSpec);
int h = resolveSize(dia, heightMeasureSpec);
setMeasuredDimension(w, h);
float radius = Math.min(w, h)/2F;
mTranslateX = radius;
mTranslateY = radius;
}
Related
I want to make a view that it's width and height is always equal.
when user define a specfied height and width in xml, it chooses a smaller one.
for example:
<com.mmmmar.ControlView
android:layout_width="100dp"
android:layout_height="200dp"
android:background="#f00" />
it run well.
However, when I make width lager than height, It runs unexpectedly.
green block is something I draw on this view, red block is the background I set in xml to indicate the area of view.
<com.mmmmar.ControlView
android:layout_width="200dp"
android:layout_height="100dp"
android:background="#f00" />
here is my code
public class ControlView extends View {
private final static int DEFAULT_SIZE = 200;
private int mDrawRadius;
private Paint mPaint;
public ControlView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// configure paint
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
mPaint = paint;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int size = dp2px(DEFAULT_SIZE, getContext());
int width = calculateSpec(widthMeasureSpec, size);
int height = calculateSpec(heightMeasureSpec, size);
int radius = Math.min(width, height);
// width should equal height.
setMeasuredDimension(radius, radius);
mDrawRadius = radius;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = mPaint;
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.FILL);
canvas.drawRect(0, 0, mDrawRadius, mDrawRadius, paint);
}
public int dp2px(float dp, Context context) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
public int calculateSpec(int measureSpec, int defaultSize) {
int mode = View.MeasureSpec.getMode(measureSpec);
int size = View.MeasureSpec.getSize(measureSpec);
int realSize;
if (mode == View.MeasureSpec.EXACTLY) {
realSize = size;
} else {
// For Mode :UNSPECIFIED and AT_MOST
realSize = Math.min(size, defaultSize);
}
return realSize;
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int size = dp2px(DEFAULT_SIZE, getContext());
int width = calculateSpec(widthMeasureSpec, size);
int height = calculateSpec(heightMeasureSpec, size);
int radius = Math.min(width, height);
// width should equal height.
setMeasuredDimension(radius, radius);
mDrawRadius = radius;
}
You do not need to set measured dimension after calculating.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Your logic comes here to pick smallest border
super.onMeasure(smallest_border_that_you_pick,smallest_border_that_you_pick)
}
Would be fix your problem.
This is my code which use another Sensor class and other activity but
i want only compass to rotate in smooth way i use bitmap image on
canvas bitmap not rotate in smooth way
public class CompassView extends View
{
private int mWidth;
private int mHeight;
private float position = 0;
private Bitmap myCompassPointer;
public CompassView(Context context)
{
super(context);
myCompassPointer=BitmapFactory.decodeResource(getResources(),
R.drawable.pin_finder);
}
#Override
protected void onDraw(Canvas canvas)
{
int cx = (mWidth - myCompassPointer.getWidth()) >> 1;
int cy = (mHeight - myCompassPointer.getHeight()) >> 1;
if (position > 0)
{
canvas.rotate(position, mWidth >> 1, mHeight >> 1);
}
//it set the bitmap to cx,cy position
canvas.drawBitmap(myCompassPointer, cx, cy, null);
canvas.restore();
}
public void updateData(float position)
{
this.position = position;
invalidate();
}
protected void onMeasure(int widthMeasureSpec, int
heightMeasureSpec)
{
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = View.MeasureSpec.getSize(widthMeasureSpec);
mHeight = View.MeasureSpec.getSize(heightMeasureSpec);
}
}
You should store the current rotation of the canvas in some field variable and on each update of data use the ObjectAnimator to smoothly update the value of the current rotation of the canvas to the new value. Be sure to invalidate the view on each animation frame. You can read more about animations of properties and ObjectAnimator here: Property Animation
I newbie to android and I have a question(probably dummy question ...)
I am creating a custom view by extending View element.
I start to draw and notice that my drawings is clipped a little bit at the top and left boundaries.
what should I do to solved it out?
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec){
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int chosenDimention = Math.min(widthSize, heightSize);
setMeasuredDimension(chosenDimention, chosenDimention);
Log.v(TAG, "onMeasure: "+chosenDimention);
}
public void onDraw(Canvas canvas){
drawBackground(canvas);
Log.v(TAG, "onDraw");
Canvas c = new Canvas(background);
drawPie(c);
}
private void drawPie(Canvas canvas){
int width = getWidth();
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(width, width);
// drawing stuff
canvas.restore();
}
i'm gonna try to draw a simple arc on my custom view, but it just won't show anything. It has been bothering me for a long time.
It's the main actitvity:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//setContentView(new Ring(this));
}}
And it's the main xml:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ring="http://schemas.android.com/apk/res/com.shockwave.arandomer"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/scrollView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|center_horizontal|center"
android:orientation="vertical" >
<com.shockwave.arandomer.Ring
android:id="#+id/ring"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
ring:ring_color="red"
ring:stroke_width="5.0"/>
</LinearLayout>
</ScrollView>
Then this is the main class handle the drawing:
public class Ring extends ViewGroup{
Paint paint = null;
float stroke_w;
int ring_color;
public Ring(Context context,AttributeSet attrs) {
super(context,attrs);
setFocusable(true);
TypedArray type = context.getTheme().obtainStyledAttributes(attrs,R.styleable.Ring,0,0);
try{
stroke_w = type.getFloat(R.styleable.Ring_stroke_width, (float)3.0);
ring_color = type.getInteger(R.styleable.Ring_ring_color, 0);//default: black
}finally{
type.recycle();
}
switch(ring_color){
case 0:
ring_color = Color.BLACK;
break;
case 1:
ring_color = Color.RED;
break;
case 2:
ring_color = Color.GREEN;
break;
}
Ring_main ring_view = new Ring_main(getContext());
addView(ring_view);
//ring_view.invalidate();
}
#Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
int w = Math.max(minw, MeasureSpec.getSize(widthMeasureSpec));
int minh = getPaddingTop() + getPaddingBottom() + getSuggestedMinimumHeight();
int h = Math.max(MeasureSpec.getSize(heightMeasureSpec), minh);
setMeasuredDimension(w, h);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh){
super.onSizeChanged(w, h, oldw, oldh);
}
private class Ring_main extends View{
public Ring_main(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public Ring_main(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
#Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(ring_color);
paint.setStrokeWidth(stroke_w);
paint.setStyle(Style.STROKE);
RectF oval = new RectF();
oval.set(100, 100, 300, 300);
canvas.drawArc(oval, 90, 180, false, paint);
}
}
#Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
// TODO Auto-generated method stub
}
}
Thanks for helping
Your inner class Ring_main doesn't override onMeasure so it is going to have zero height and width.
Your outer class Ring doesn't do anything in onLayout and tell the children where they should be drawn on the screen, so that will also cause issues. Nine times out of ten, you are going to want to subclass LinearLayout, RelativeLayout, or FrameLayout, rather than ViewGroup, so you don't have to override onMeasure and onLayout.
But is there any reason for you to be using a view group for this when there is only one child view? It seems like you could move the onDraw method into the parent Ring, delete the inner Ring_main class, and change Ring to subclass View, and you'd be mostly done.
Edit:
In response to your code in the comment below:
Your onMeasure implementation is wrong, because you ignore the possible mode attached to the incoming width and height. For example, in your layout xml, the parent has a height of wrap_content so the onMeasure's height parameter is going to to have a mode of UNSPECIFIED and when you call MeasureSpec.getSize(aHeight) it will return a useless number (although it seems to default to zero).
This is a more correct implementation, although ultimately, there are more details you will need to consider, such as whether you want the arc to expand to fill whatever amount of space is available to your view, or to always be the same size and just center itself in the view.
static final float DESIRED_WIDTH_BEFORE_PADDING = 100;
static final float DESIRED_HEIGHT_BEFORE_PADDING = 200;
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int wDesired = getPaddingLeft() + getPaddingRight() +
Math.max(DESIRED_WIDTH_BEFORE_PADDING, getSuggestedMinimumWidth());
int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int w = 0;
switch(wSpecMode){
case MeasureSpec.EXACTLY:
w = MeasureSpec.getSize(widthMeasureSpec);
break;
case MeasureSpec.AT_MOST:
w = Math.min(wDesired, MeasureSpec.getSize(widthMeasureSpec));
break;
case MeasureSpec.UNSPECIFIED:
w = wDesired;
break;
}
int hDesired = getPaddingTop() + getPaddingBottom() +
Math.max(DESIRED_HEIGHT_BEFORE_PADDING, getSuggestedMinimumHeight());
int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int h = 0;
switch(hSpecMode){
case MeasureSpec.EXACTLY:
h = MeasureSpec.getSize(heightMeasureSpec);
break;
case MeasureSpec.AT_MOST:
h = Math.min(hDesired, MeasureSpec.getSize(heightMeasureSpec));
break;
case MeasureSpec.UNSPECIFIED:
h = hDesired;
break;
}
setMeasuredDimension(w, h);
}
If your min SDK is 11 or higher, this can be simplified a lot with the resolveSizeAndState method.
Note the above is simplified to ignore DIP. Those two static floats should be in DIP units, and then multiply them by density when you use them in onMeasure.
It will also be up to you to override onSizeChanged to calculate what actual height and width you will use to draw your arc, as well as any offsets required to possibly center the arc within the view. Then these values would be used in onDraw to ensure the arc draws where you want it to.
public class Player extends ViewGroup {
private RectF rect = new RectF();
private Paint paint;
public Player(Context context,String pname) {
super(context);
setWillNotDraw(false);
paint=new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Style.FILL);
paint.setColor(getResources().getColor(R.color.red));
}
public void onDraw(Canvas canvas) {
canvas.drawRoundRect(rect, 10, 10, paint);
canvas.drawCircle(rect.centerX(), rect.centerY(), 10, paint);
//canvas.drawColor(Color.RED);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wspec = MeasureSpec.makeMeasureSpec(
getMeasuredWidth(), MeasureSpec.EXACTLY);
int hspec = MeasureSpec.makeMeasureSpec(
getMeasuredHeight(), MeasureSpec.EXACTLY);
for(int i=0; i<getChildCount(); i++){
View v = getChildAt(i);
v.measure(wspec, hspec);
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
rect.set(l, t,r, b);
}
}
the third command does draw a red rectangle which bounds are the rect (l,t,r,b) = (412,415,735,754) which is given by the param rect, and for some reason, the two first commands do not do any effect on the canvas!
I have made sure the rect is an actual rectangle, as i mentioned its values were (412,415,735,754) which does make a valid rectangle, and you see how i defined the paint so why the hell wouldnt it draw?
been spending 2 hours trying to figure it out, seriously...
thanks!
BTW, the class extends ViewGroup cause it eventually meant to implement a view container..
Try this for your onLayout routine:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
rect.set(0, 0, r-l, b-t);
}
This way you will create a rect with the width and height of the full layout, but whose top left point (relative to the canvas) is 0, 0.