I have an Android app where i need to take a photo, display it and then be able to put a mark icon it.
I succeeded in taking the picture and displaying it on an ImageView, but now i try to put the mark on it, so i searched and found that a good solution was to subclass ImageView, so that's what i did, but when i draw the canvas on my custom Imageview it's blank...
This is how i do, first my custom ImageView :
public class CustomImageView extends AppCompatImageView {
private Point mTouch;
private Bitmap mMarker;
public CustomImageView(Context context) {
super(context);
init();
}
public CustomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mTouch = new Point();
mMarker = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.ic_mark);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP) {
mTouch = new Point(Math.round(event.getX()), Math.round(event.getY()));
}
return super.onTouchEvent(event);
}
#Override
protected void onDraw(Canvas c) {
super.onDraw(c);
Paint paint = new Paint();
c.drawBitmap(mMarker, mTouch.x, mTouch.y, paint);
}
}
And Where i use it :
....
mCustomImageView = (CustomImageView) view.findViewById(R.id.mypic);
....
File imgFile = new File(getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES),"mypic.jpg");
Bitmap myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath());
Bitmap myMutableBitmap = myBitmap.copy(Bitmap.Config.RGB_565,true);
mCanvas = new Canvas(myMutableBitmap);
mCustomImageView.draw(mCanvas);
Then nothing shows... seems that mCustomImageView is empty, and of course onDraw is never fired as i can't draw on it.
But if i use this :
mCustomImageView.setImageBitmap(myMutableBitmap);
Then the image is showing, and touch events are sent. But i think this way i don't draw on the canvas, and onDraw is not fired as well...
Any hint ?
onDraw is not called after touch event.Adding invalidate() to onTouchEvent to force refresh may resolve your problem.
Related
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);
}
}
}
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
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.
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);
}
}
}
}
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).