I have written a custom view to display images dynamically inside a circle.
I have overwritten Viewgroup's dispatchDraw method to draw circle. After this the child ImageViews are not displaying on screen, if I do no override the method, then they are displaying on screen.
Here is my class:
public class CustomView extends RelativeLayout {
private Paint paint;
private View mView;
private Context context;
private void init(Context context) {
LinearLayout layout = new LinearLayout(context);
layout.setGravity(Gravity.CENTER);
layout.setOrientation(LinearLayout.VERTICAL);
// Set generic layout parameters
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
Button button = new Button(context);
button.setText("Button!");
layout.addView(button, params); // Modify this
ImageView imageView = new ImageView(context);
imageView.setImageResource(R.drawable.coffe_selected);
layout.addView(imageView);
this.addView(layout);
}
public CustomView(Context mContext) {
super(mContext);
context = mContext;
// create the Paint and set its color
paint = new Paint();
paint.setColor(0xFF1f5b83);
init(context);
}
#Override
protected void dispatchDraw(Canvas canvas) {
int width = this.getWidth();
int height = this.getHeight();
canvas.drawCircle(width / 2, height / 2-64, 200, paint);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Take a look at the source code of ViewGroup and what is happening in dispatchDraw.
Just one line out of it:
more |= drawChild(canvas, transientChild, drawingTime);
as you can see, the childs are drawn there.
So if you don't call the super method of dispatchDraw, it is possible that the childs are not drawn.
Simply call:
super.dispatchDraw(canvas);
Related
I have a custom view class that draws a rectangle and I have a custom view group where I am trying to add these custom views but somehow the views are not getting drawn properly(at times only one of the view gets drawn).
Not able to locate the problem!
MainActivity.java
public class MainActivity extends Activity {
private CustomTaskView customTaskView;
private FrameLayout frameLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
customTaskView = new CustomTaskView(MainActivity.this);
frameLayout = new FrameLayout(MainActivity.this);
frameLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
Paint blackPaint = new Paint();
blackPaint.setColor(Color.BLACK);
blackPaint.setStyle(Paint.Style.STROKE);
CustomView customView1 = new CustomView(MainActivity.this, 100, 50, 100, 300, blackPaint);
customView1.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT));
frameLayout.addView(customView1);
Paint redPaint = new Paint();
redPaint.setColor(Color.RED);
redPaint.setStyle(Paint.Style.STROKE);
CustomView customView2 = new CustomView(MainActivity.this, 200, 50, 300, 400, redPaint);
customView2.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT));
frameLayout.addView(customView2);
setContentView(frameLayout);
}
}
CustomView.java
public class CustomView extends View {
private Paint paint;
private float l, t, r, b;
public CustomView(Context context, float l, float t, float r, float b, Paint paint) {
super(context);
this.b = b;
this.l = l;
this.r = r;
this.t = t;
this.paint = paint;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(l, t, r, b, paint);
}
}
CustomTaskView.java(the custom viewgroup file)
public class CustomTaskView extends ViewGroup implements LongPressGestureListener.HandleClicks {
int width, height;
private GestureDetectorCompat mGestureDetector;
private LongPressGestureListener longPressGestureListener;
private Map<Integer, List<Point>> map;
public CustomTaskView(Context context) {
super(context);
longPressGestureListener = new LongPressGestureListener(context, CustomTaskView.this);
mGestureDetector = new GestureDetectorCompat(context, longPressGestureListener);
map = new HashMap<>();
}
public CustomTaskView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = getMeasuredWidth();
height = getMeasuredHeight();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
// Handle any other event here, if not long press.
return true;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
#Override
public void handleLongClick(MotionEvent event) {
Log.e("ganesh", "handling long clicks!");
}
#Override
public void handleSingleClick(MotionEvent event) {
float xCoordinate = event.getX();
float yCoordinate = event.getY();
Point tempPoint = new Point(xCoordinate, yCoordinate);
int count = 0;
for (Map.Entry<Integer, List<Point>> entry : map.entrySet()) {
if (isPointInside(entry.getValue(), tempPoint))
count++;
}
Log.e("ganesh", "handling single clicks!" + xCoordinate + " " + yCoordinate + " count: " + count);
}
public boolean isPointInside(List<Point> pointList, Point targetPoint) {
return targetPoint.getxCoordinate() >= pointList.get(0).getxCoordinate() && targetPoint.getxCoordinate() <= pointList.get(1).getxCoordinate() && targetPoint.getyCoordinate() >= pointList.get(0).getyCoordinate() && targetPoint.getyCoordinate() <= pointList.get(2).getyCoordinate();
}
}
Whenever you make any change to a custom view, you need to rebuild your project for the XML preview parser to draw. Try Build->Rebuild Project once.
You have to provide the correct implementation of onLayout and onMeasure method in your custom view if you extend ViewGroup class directly. You can find out more about these two methods here Google Developers Guide Custom ViewGroup
Well here is the edit to your current CustomTaskView.java to make your code work.
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
CustomView customView = (CustomView) this.getChildAt(i);
customView.layout(l, t, r, b);
}
}
In Custom View onLayout method is responsible for Position all children within the layout. You need to call layout method on all the children and provide calculated left, top, right, bottom attribute to them.
I have a view and I want to draw a shape on it (circle for example) after click.
I've tried to do this but there are two problems -
onDraw is never called.
Not sure the setLayoutParams(v.getLayoutParams) will give me the result I want.
#Override
public void onClick(View v) {
CircleView circle = new CircleView(GameXoActivity.this, v.getWidth(), v.getHeight());
circle.setLayoutParams(v.getLayoutParams());
circle.startDrawing();
}
CircleView:
public CircleView(Context context, int width, int height) {
super(context);
this.width = width;
this.height = height;
}
protected void startDrawing() {
this.postInvalidate();
}
#Override
protected void onDraw(Canvas canvas) {
Log.d("TAG", "onDraw");
// draw circle
}
}
}
UPDATE:
The shape is not an image and I want to draw it with animation (I didn't write the entire code).
Also, the shape is not always a circle, so using a drawable-state is not an option.
Because there is not just one view, but 9, I don't think the making 9 more on top of them would be right.
As I'm sure you'll need to customize this quite a bit, I've left things rather generic. The following example will animate a blue circle being drawn clockwise, starting from the east (0 degrees), on top of the View's content when the View is clicked.
public class CircleView extends View
{
private static final int MARGIN = 50;
Handler handler = new Handler();
Paint paint = new Paint();
RectF rect = new RectF();
boolean drawing = false;
float sweep = 0;
public CircleView(Context context)
{
this(context, null);
}
public CircleView(Context context, AttributeSet attrs)
{
super(context, attrs);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(15);
paint.setColor(Color.BLUE);
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.drawArc(rect, 0, sweep, false, paint);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
rect.set(MARGIN, MARGIN, w - MARGIN, h - MARGIN);
}
public void startAnimation()
{
drawing = true;
handler.post(runnable);
}
Runnable runnable = new Runnable()
{
#Override
public void run()
{
sweep += 10;
if (!(sweep > 360))
{
invalidate();
handler.postDelayed(this, 20);
}
else
{
drawing = false;
sweep = 0;
}
}
};
}
In this Activity example, I used an image that most developers would already have in their project, but it can obviously be changed to your custom image. Also, for the sake of simplicity and brevity, the CircleView is set as the entire content of the Activity, but it can easily be listed in an xml layout, as well.
public class MainActivity extends Activity
{
CircleView circle;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
circle = new CircleView(this);
circle.setBackgroundResource(R.drawable.ic_launcher);
circle.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
circle.startAnimation();
}
}
);
setContentView(circle);
}
}
I suggest, create two imageView with same dimensions, set the image you want to display on image view and then make the second image invisible .
For example :
circleimage.setVisibility(View.INVISIBLE);//now its hidden(do it OnCreate)
and then show 2ndimage when 1stimage is clicked(do it in onclick of 1st image)
circleimage.setVisibility(View.VISIBLE);
If you want to mess with drawing of the object you should overridepublic void draw(Canvas canvas) and not protected void onDraw(Canvas canvas)
EDIT:Please read comments, this first statement of my answer is probably wrong
but I would use a FrameLayout or a RelativeLayout and put the images one on top of another.
Then you can play with the visibility of the overlaying image in order to hide/show it.
EDIT:
In case your circle is not an image and needs to be drawn, make your own circle class extending View and use it as a component in the FrameLayout or RelativeLayout as you would do if it were an image
I'm using RelativeLayout to absolutely position some standard views (like TextView).
What I'd like to do is to draw a custom line on this RelativeLayout's Canvas using Canvas.drawLine that is drawn behind all its other subviews.
These other subviews are added with explicitely defining RelativeLayout.LayoutParams, but I'd like to leave the decision of where to paint itself to my custom line.
I tried wrapping this line in a CustomView with overloaded View.onDraw(Canvas canvas) method and simply adding the view without specifying any LayoutParams, so:
public class CustomView extends View {
public CustomView(Context context, int x0, int y0, int x1, int y1) {
super(context);
setClickable(false);
setBackgroundColor(Color.TRANSPARENT);
}
public void onDraw(Canvas canvas) {
Log.i("myapp", "i'm not called! :(")
Paint p = new Paint();
p.setColor(Color.BLACK);
canvas.drawLine(x0, y0, x1, y1, p);
}
}
And usage:
CustomView v = new CustomView(MyActivity.this, 0, 0, 100, 100);
relativeLayout.addView(v);
... but this onDraw method is never called.
Is there a way to make this work?
Edit: works if I substitute:
relativeLayout.addView(v)
with
relativeLayout.addView(v,
new RelativeLayout.LayoutParams(SOME_WIDTH, SOME_HEIGHT));
The point is, I know neither SOME_WIDTH, nor SOME-HEIGHT at that point.
try this custom RelativeLayout:
class RL extends RelativeLayout {
private Paint mPaint;
public RL(Context context) {
super(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStrokeWidth(5);
mPaint.setColor(0xffffffff);
}
#Override
protected void dispatchDraw(Canvas canvas) {
int cnt = getChildCount();
for (int i = 0; i < cnt; i++) {
View child = getChildAt(i);
int l = child.getLeft();
int t = child.getTop();
int r = child.getRight();
int b = child.getBottom();
if (i % 2 == 0) {
canvas.drawLine(l, t, r, b, mPaint);
} else {
canvas.drawLine(l, b, r, t, mPaint);
}
}
super.dispatchDraw(canvas);
}
}
and test it ba adding the following in onCreate() method:
RelativeLayout rl = new RL(this);
TextView tv;
List<String> list = Arrays.asList("one", " two ", "three", " four ", "fife");
int i = 0;
for (String string : list) {
int id = 1000 + i;
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
if (i != 0) {
params.addRule(RL.BELOW, id - 1);
}
tv = new TextView(this);
tv.setTextSize(48);
tv.setTextColor(0xffff0000);
tv.setText(string);
rl.addView(tv, params);
tv.setId(id);
i++;
}
setContentView(rl);
So.
I ended up creating a CustomController which has some methods to calculate position/size and using this controller when creating RelativeLayout.LayoutParams for each CustomView(context, controller).
I guess you cannot have a subview in a RelativeLayout without specifying its RelativeLayout.LayoutParams.
Easiest way is to call the super.draw(Canvas) method after you finished your background in the onDraw() method.
That will cause it to draw the children last.
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.
Edit: This problem was down to me passing the wrong view to the Touch Delegate...problem is resolved now....
My RelativeLayout extension enlarges the clickable area of an ImageView. The RelativeLayout is the root View of the ListView row. The problem is when tapping areas that should be delegated to the ImageView, the ListView's onItemClicked is triggered.
My code is at the end and below is an image of what the extended bounds of the ImageView should be. If I tap on blue areas that are not the image, the click is received by the ListView.
public class DelegatingRelativeLayout extends RelativeLayout {
//relevant code moved to top
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
final float density = getContext().getResources().getDisplayMetrics().density;
int extra = (int) (density * 4 + 0.5f);
Rect rect = new Rect(0, 0, mEnlargedView.getWidth(), getHeight());
rect.right += extra; //extend bounds by 4 pixels
setTouchDelegate(new TouchDelegate(rect, mEnlargedView));
mExtendedBounds = rect;
}
public void setEnlargedView(View v) {
mEnlargedView = v;
}
private static final boolean DEBUG_DRAW = true;
private View mEnlargedView;
private Rect mExtendedBounds;
//constructors removed
#Override
protected void dispatchDraw(Canvas canvas) {
if (DEBUG_DRAW) {
Paint p = new Paint();
p.setColor(Color.BLUE);
canvas.drawRect(mExtendedBounds, p);
}
super.dispatchDraw(canvas);
}
}
Ugh, I've just realized I've been passing the wrong view to the touch delegate >.< It's been working fine now.... >.<