Why onDraw is not called after invalidate()? - android

I have found many posts on stackoverflow but I still cannot solve my problem. Here is my code piece:
public class MyView extends RelativeLayout {
Button b1;
Button b2;
Context sContext;
public static int i = 0;
private int w = 400;
private int h = 400;
private int w2 = 100;
private int h2 = 100;
public MyView(Context context) {
super(context);
sContext = context;
init();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
sContext = context;
init();
}
private void init() {
b1 = new Button(sContext);
addView(b1);
b1.setBackgroundColor(Color.YELLOW);
b1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (w >= 600) {
MyView.this.setBackgroundColor(Color.GREEN);
//b1.setBackgroundColor(Color.RED);
} else {
MyView.this.setX(100);
}
MyView.this.invalidate();
w += 100;
w2 += 20;
}
});
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//b1.setBackgroundColor(Color.RED);
Toast.makeText(sContext, ""+i, Toast.LENGTH_SHORT).show();
++i;
}
}
Would you please explain why onDraw is not called the first three times I press b1? Because I called invalidate everytime I press b1. Thank you very much!

By default all ViewGroup sub-classes do not call their onDraw method, you should enable it by calling setWillNotDraw(false) link

Borrowing from the link #
Android Custom Layout - onDraw() never gets called
Try the below it works
1.If you're extending a ViewGroup (in your case a RelativeLayout) you should override dispatchDraw() instead of onDraw().
Discussion on the topic #
https://groups.google.com/forum/?fromgroups=#!topic/android-developers/oLccWfszuUo
protected void dispatchDraw (Canvas canvas)
Called by draw to draw the child views. This may be overridden by derived classes to gain control just before its children are drawn (but after its own view has been drawn).
Parameters
canvas the canvas on which to draw the view
Example
public class Hello extends Activity {
private MyView myView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
Log.e("hello", "hello");
this.myView = new MyView(this);
setContentView(this.myView);
}
public class MyView extends RelativeLayout
{
private Paint myPaint = new Paint();
private int[] numbers;
public MyView(Context paramContext)
{
super(paramContext);
Log.e("MyView", "MyView");
setFocusable(true);
setBackgroundResource(R.drawable.ic_launcher);
}
#Override
protected void dispatchDraw(Canvas canvas){
super.dispatchDraw(canvas);
Log.i("...............","drawing");
}
}
}
2.If you Override onDraw in the constructor call setWillNotDraw(false) then it should work.
http://developer.android.com/reference/android/view/View.html#setWillNotDraw(boolean)
public MyView(Context context) {
super(context);
sContext = context;
init();
setWillNotDraw(false);
}

You need to call setWillNotDraw(false); in order for your onDraw method to be called:
private void init() {
setWillNotDraw(false);
...
}

I was having OnDraw not called in C# using 4.1 Jelly Bean API 16. I switched from compiling with API 16 SDK to compiling with 7.1 Nougat SDK. It solved the problem completely. I still have in manifest min sdk of API 16 and it runs fine in my Jelly Beam avd.

In my case onDraw was not called because I returned 0 as width or height of view.

Related

Drawing on Canvas, Adding Stickers, Custom Bitmaps - Android

I am looking to implement a feature that allows me to take predefined borders/images and place them over top a picture that a user has taken.
This app would be very similar to the functionality of Aviary, or Photo Editing Apps.
I cannot seem to figure out how to draw many images (say mustaches, noses, etc..) onto a canvas or view.
Is there a specific method to accomplishing this stacked bitmap/filter editing? I tried creating a custom View, but I can only set 1 setContentView();
My code is relatively simple right now, but I just wanted to know if there was a method of stacking views, or bitmaps individually.
public class CustomView extends View {
public CustomView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public CustomView(Context context, AttributeSet attrs) {
super(context);
}
#Override
public void onDraw (Canvas canvas) {
//Draw stuff in here
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.basketball);
canvas.drawBitmap(bitmap, 0, 0, null);
}
}
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
CustomView m_View = new CustomView(this);
setContentView(m_View);
}
}
This logic can be implemented many ways. It is based on your requirements and details of the application. Basically you can do something similar to this:
#SuppressLint("WrongCall")
#Override
protected void onDraw(Canvas canvas) {
// use for-int to avoid object (iterator) allocations
int count = mItems.size();
for (int i = 0; i < count; i++) {
mItems.get(i).onDraw(canvas);
}
}
ArrayList<Item> mItems;
static class Item {
Context mContext;
Drawable mDrawable;
Rect mRect;
int mResourceId;
// resource ID to point to drawable resources
public Item(Context context, int resourceId) {
mContext = context;
mResourceId = resourceId;
mRect = new Rect();
}
public void onDraw(Canvas canvas) {
// allocate image only when it actually required
if (mDrawable == null) {
mDrawable = mContext.getResources().getDrawable(mResourceId);
}
// manage mRect to place sticker and etc.
if (mDrawable != null) {
mDrawable.setBounds(mRect);
mDrawable.draw(canvas);
}
}
}

Gesture automatically draws itself

I've seen it on Dolphin Browser. There're some gestures that're already created by default. They will redraw themselves so that users know where to begin drawing. I've noticed that in Gesture object, there's a method named toPath(). But I have no clue how to use it and I'm not sure if I'm on the right track. Can somebody tell me how to do it? Thanks. You can take a look at the picture below.
First of all I would suggest to take a look at GestureBuilder app from SDK samples. It has exactly that is shown in your question (small gesture thumbnails).
I've slightly extended that example to make usage of Gesture APIs more clear:
Add the following code into GestureBuilderActivity from GeatureBuilder sample:
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
final Intent intent = new Intent(getApplicationContext(), ShowGestureActivity.class);
intent.putExtra(ShowGestureActivity.GESTURE_NAME_EXTRA, ((NamedGesture)v.getTag()).name);
startActivity(intent);
}
It will launch new test activity ShowGestureActivity:
public class ShowGestureActivity extends Activity {
public static final String GESTURE_NAME_EXTRA = "gesture_extra";
private Gesture mGesture = null;
private FrameLayout mContainer;
private MyPathView mMyPathView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.show_gesture);
final ArrayList<Gesture> gestures = GestureBuilderActivity.getStore()
.getGestures(getIntent().getStringExtra(GESTURE_NAME_EXTRA));
if (gestures.size() == 1) {
mGesture = gestures.get(0);
} else {
Toast.makeText(getApplicationContext(), "No gesture available!", Toast.LENGTH_LONG).show();
}
mContainer = (FrameLayout) findViewById(R.id.container);
mMyPathView = (MyPathView) findViewById(R.id.myPathView);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.show_gesture_menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
final int id = item.getItemId();
if (mGesture == null) {
return false;
}
switch (id) {
case R.id.action_show_gesture_bmp:
final Bitmap gestureBmp = mGesture.toBitmap(mContainer.getWidth(), mContainer.getHeight(),
getResources().getDimensionPixelSize(R.dimen.gesture_thumbnail_inset), Color.YELLOW);
mMyPathView.setVisibility(View.GONE);
mContainer.setBackground(new BitmapDrawable(getResources(), gestureBmp));
return true;
case R.id.action_show_gesture_path:
mMyPathView.setPath(mGesture.toPath(mContainer.getWidth(), mContainer.getHeight(),
getResources().getDimensionPixelSize(R.dimen.gesture_thumbnail_inset), 10));
mMyPathView.setVisibility(View.VISIBLE);
mContainer.setBackground(null);
return true;
}
return super.onOptionsItemSelected(item);
}
}
In onOptionsItemSelected you can see usage of both Gesture methods available for drawing. Seems toBitmap is quite clear (GesturesBuilder app itself uses that method for gestures thumbnails display in the list).
Regarding toPath: it provides you with the path which corresponds to the Gesture. After that you can use that path for drawing as you want. MyPathView from test activity above provides easiest way of doing so:
public class MyPathView extends View {
private Paint mPaint;
private Path mPath = null;
public MyPathView(Context context) {
super(context);
init(null, 0);
}
public MyPathView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public MyPathView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
private void init(AttributeSet attrs, int defStyle) {
mPaint = new Paint();
mPaint.setColor(Color.YELLOW);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen.paint_width));
}
public void setPath(final Path path) {
mPath = path;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mPath != null) {
canvas.drawPath(mPath, mPaint);
}
}
}
And xml is (just to make example easy to compile):
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/container">
<com.sandrstar.testapp.test.MyPathView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/myPathView"
android:visibility="gone"/>
</FrameLayout>
If you want to apply some kind of animation to gesture drawing, you need to get path, create custom view as described above and apply some animation method, e.g. like one described here Draw path on canvas with animation

Canvas Clip function bug on Galaxy Nexus

I'm making a application it's picture cropping.
But Galaxy nexus has some problem.
Region.Op.DIFFERENCE is not works.
Desire(2.3.3) and GalaxyNexus(4.1) Emulator works well.
But not works only on GalaxyNexus Real Phone
Please see the code...
It's a onDraw overrided method it's extended imageview
#override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//all rectangle
getDrawingRect(viewRect);
//small rectangle
getDrawingRect(smallRect);
smallRect.left += 100;
smallRect.right -= 100;
smallRect.bottom -= 200;
smallRect.top += 200;
// paint color setting to transparency black
mNoFocusPaint.setARGB(150, 50, 50, 50);
// All Rectangle clipping
canvas.clipRect(viewRect);
// Small Rectangle clipping
canvas.clipRect(smallRect, Region.Op.DIFFERENCE);
// Draw All Rectangle transparency black color it's except small rectangle
canvas.drawRect(viewRect, mNoFocusPaint);
}
solved!
add this code in manifest
android:hardwareAccelerated="false"
: )
JuHyun's answer is great! In my case however I didn't want to remove hardware acceleration for my entire app across all SDK versions. The issue with hardware-accelerated canvas clipping seems to be confined to 4.1.1, so I took the route of disabling hardware acceleration for the specific view in which I was performing the clipping operation.
Custom view class (in this case, a RecyclerView):
public class ClippableRecyclerView extends RecyclerView {
private final CanvasClipper clipper = new CanvasClipper();
public ClippableRecyclerView(Context context) {
super(context);
configureHardwareAcceleration();
}
public ClippableRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
configureHardwareAcceleration();
}
public ClippableRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
configureHardwareAcceleration();
}
public void configureHardwareAcceleration() {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
}
/**
* Remove the region from the current clip using a difference operation
* #param rect
*/
public void removeFromClipBounds(Rect rect) {
clipper.removeFromClipBounds(rect);
invalidate();
}
public void resetClipBounds() {
clipper.resetClipBounds();
invalidate();
}
#Override
public void onDraw(Canvas c) {
super.onDraw(c);
clipper.clipCanvas(c);
}
}
Canvas clipper class:
public class CanvasClipper {
private final ArrayList<Rect> clipRegions = new ArrayList<>();
/**
* Remove the region from the current clip using a difference operation
* #param rect
*/
public void removeFromClipBounds(Rect rect) {
clipRegions.add(rect);
}
public void resetClipBounds() {
clipRegions.clear();
}
public void clipCanvas(Canvas c) {
if (!clipRegions.isEmpty()) {
for (Rect clipRegion : clipRegions) {
c.clipRect(clipRegion, Region.Op.DIFFERENCE);
}
}
}
}

onDraw not being called in custom view even though setWillNotDraw(false) is called

So i have a custom view called Square which I want to add in a table.
When I replace my squares with text views it works perfectly.
I searched quite a bit online and found that I should call setWillNotDraw(false) in the constructor. I did this to no result.
Here is my class Square:
public class Square extends View
{
private String courseName;
private String className;
private String place;
private Weekdays weekday;
private int hour;
private Paint paint;
public Square(Context context, AttributeSet attrs){
super(context,attrs);
setWillNotDraw(false);
}
public Square(Context context, String courseName, String className, String place, Weekdays weekday, int hour)
{
super(context);
this.courseName = courseName;
this.className = className;
this.place = place;
this.weekday = weekday;
this.hour = hour;
setWillNotDraw(false);
paint = new Paint();
}
public Square(Context context,Weekdays weekdays, int i) {
super(context);
this.weekday = weekdays;
this.hour = i;
setWillNotDraw(false);
paint = new Paint();
paint.setColor(Color.BLACK);
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.drawText("Hello I'm a test", 1, 30, paint);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
I have tried setting the onMeasure() method to use 10,10 or whatever, but nothing shows up. I don't know if this is relevant to my problem.
The table is made dynamically like this:
public class MainActivity extends Activity {
private Controller controller;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
controller = new Controller(this);
createTable();
}
private void createTable() {
// get a reference for the TableLayout
TableLayout table = (TableLayout) findViewById(R.id.tableLayout1);
/**Create 8 rows*/
for(int i=0;i<Controller.TABLE_ROW_COUNT;i++){
// create a new TableRow
TableRow row = new TableRow(this);
/** create 5 columns*/
for(int j=0;j<Controller.TABLE_COLUMN_COUNT;j++){
Square sq = controller.tableModel.getSquare(Weekdays.values()[j], i);
row.addView(sq, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
// add the TableRow to the TableLayout
table.addView(row,new TableLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
}
Thanks!
What's your target SDK version? If it's >= 11, hardware acceleration is making GPU cache your view. Try calling setLayerType(View.LAYER_TYPE_SOFTWARE, null) in your constructor.
Replace your onMeasure with the following:
public void onMeasure(int wms, int hms){
setMeasuredDimension(MeasureSpec.getSize(wms), MeasureSpec.getSize(hms));
}
The default (super) implementation in View sets its size to 0x0.
You may need to adjust the values to get a nice size depending on the parent.
If Hardware Acceleration is TRUE for your Activity.
Then try disabling the Hardware acceleration for that ImageView.
imageview.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
Worked for me.

How to implement the parent activity code when custom child view is touched?

I have a custom ImageView('CustomImageView') and an Edit-text in a linear layout of an activity 'ImageViewActivity'. The Edit-text is initially set invisible. When the customimageview is touched, and onDraw() is called, I want the visibility of the Edit-text to be set visible. Where should I put the code for this?
Code for ImageViewActivity:
public class ImageViewActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.custom_imageview);
}
}
And the code for CustomImageView is:
public class CustomImageView extends ImageView {
Paint paint = new Paint();
float xp = -1, yp = -1;
private Options opt;
public CustomImageView(Context context) {
super(context);
init();
}
public CustomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void init() {
opt = new BitmapFactory.Options();
opt.inJustDecodeBounds = true;
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
xp = event.getX();
yp = event.getY();
invalidate();
}
return true;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//some code
setMeasuredDimension(width, height);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (xp >= 0 && yp > 0) {
canvas.drawCircle(xp, yp, 20, paint);
}
}
}
Implement touch listener on your customImageview class and then call the edittext to hide it or show is as you need, for results of touch on custom view you have to define touchlistener on your customview not of activity and you can manage views of parent activity in touchevent of customview OR other method is implement touch listener on you activity and find the view which is touched if touched view is your custom view then make visible your Edittext. Then you will not need to do anything in onDraw() as regarding the edittext.
I'd recommend setting the view to visible once you have detected a touch motion on your particular view. Specifically it doesn't matter how the user touches it, since once they touch it you want to show your view (at least that is what you said you wanted).

Categories

Resources