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
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);
}
}
}
SOLVED: Solution below as answer.
I have a custom view with a TransitionDrawable and when I draw it in the onDraw() method it scales automatically to fill the whole parent layout, even when it's set in the xml to wrap_content. The picture is in mdpi and hdpi and my testing device (samsung galaxy s) I think it's no more than hdpi.
package com.adyrsoft.pronunciationtrainer;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.TransitionDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
public class RecordButton extends View {
private static final String TAG = "RecordButton";
private TransitionDrawable mDrawable;
private boolean mActivated;
private OnClickListener mOnClickListenerInternal = new OnClickListener() {
#Override
public void onClick(View v) {
toggleState();
if(mOnClickListener != null) {
mOnClickListener.onClick(v);
}
}
};
private OnClickListener mOnClickListener = null;
public RecordButton(Context context) {
super(context);
init();
}
public RecordButton(Context context, AttributeSet attrib) {
super(context, attrib);
init();
}
public RecordButton(Context context, AttributeSet attrib, int defStyle) {
super(context, attrib, defStyle);
init();
}
public void setState(boolean activated) {
mActivated = activated;
if(mActivated){
mDrawable.startTransition(300);
}
else {
mDrawable.reverseTransition(300);
}
}
public void toggleState() {
if(mActivated) {
setState(false);
}
else {
setState(true);
}
}
#SuppressWarnings("deprecation")
private void init() {
mActivated = false;
mDrawable = (TransitionDrawable) getResources().getDrawable(R.drawable.btnrecord);
Log.d(TAG, "Drawable intrinsic width and height are: " +
Integer.toString(mDrawable.getIntrinsicWidth()) + " " +
Integer.toString(mDrawable.getIntrinsicHeight()));
mDrawable.setBounds(0,0,mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
Log.d(TAG, "The bounds for the button are: "+mDrawable.getBounds().flattenToString());
super.setBackgroundDrawable(mDrawable);
setClickable(true);
super.setOnClickListener(mOnClickListenerInternal);
invalidate();
}
public void setOnClickListener(View.OnClickListener listener) {
mOnClickListener = listener;
}
protected void onDraw (Canvas canvas) {
super.onDraw(canvas);
}
}
What am I doing wrong?
Thanks.
After hours trying to understand how I should use the drawables in a custom view in order to be displayed in its original size, I've figured out how to do it.
First a few things that I didn't know but are a must is:
The background drawable should be left to the parent class to be
drawn when using View as the parent. If not, the TransitionDrawable can't be seen fading between pictures.
Only if I am going to draw on the background drawable I should override onDraw() and do the drawing there.
And the last but not less important is that I should override onMeasure() to specify the size of the view. If I don't do it, it will fill all the free space in the parent layout, as it was happening to me.
I've passed the TransitionDrawable to the parent class with setBackgroundDrawable() and since I wasn't drawing in the background drawable, I've removed the onDraw() method. Also I've implemented onMeasure() with a quick and dirty solution specifying the size of the picture I am drawing.
This is the final result:
public class RecordButton extends View {
private static final String TAG = "RecordButton";
private static final int DESIRED_WIDTH = 180;
private static final int DESIRED_HEIGHT = 66;
private TransitionDrawable mDrawable;
private Rect mViewRect;
private boolean mActivated;
private OnClickListener mOnClickListenerInternal = new OnClickListener() {
#Override
public void onClick(View v) {
toggleState();
if(mOnClickListener != null) {
mOnClickListener.onClick(v);
}
}
};
private OnClickListener mOnClickListener = null;
public RecordButton(Context context) {
this(context, null, 0);
}
public RecordButton(Context context, AttributeSet attrib) {
this(context, attrib, 0);
}
public RecordButton(Context context, AttributeSet attrib, int defStyle) {
super(context, attrib, defStyle);
init();
}
public void setState(boolean activated) {
mActivated = activated;
if(mActivated){
mDrawable.startTransition(300);
}
else {
mDrawable.reverseTransition(300);
}
}
public void toggleState() {
if(mActivated) {
setState(false);
}
else {
setState(true);
}
}
#SuppressWarnings("deprecation")
private void init() {
mActivated = false;
mDrawable = (TransitionDrawable) getResources().getDrawable(R.drawable.btnrecord);
setBackgroundDrawable(mDrawable);
setClickable(true);
super.setOnClickListener(mOnClickListenerInternal);
invalidate();
}
public void setOnClickListener(View.OnClickListener listener) {
mOnClickListener = listener;
}
#Override
protected void onMeasure(int m, int n) {
setMeasuredDimension(DESIRED_WIDTH, DESIRED_HEIGHT);
}
}
So basically I have some image views drawn over my canvas, but when I try to set up onclick listeners and try to handle events, it doesn't work.
public class DrawView extends View implements OnClickListener{
Paint paint = new Paint();
RectF rf = new RectF(30, 30, 80, 80);
BallView ball = new BallView(getContext());
Bitmap bmp = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.ic_launcher);
public DrawView(Context context) {
super(context);
}
#Override
public void onDraw(Canvas canvas) {
ball.setOnClickListener(this);
ball.draw(canvas);
canvas.drawBitmap(bmp, 110, 10, paint);
}
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
Toast.makeText(getContext(), "Mock", Toast.LENGTH_SHORT).show();
}
}
BallView.java
public class BallView extends ImageView {
Paint b = new Paint();
public BallView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public void onDraw(Canvas c){
b.setColor(Color.BLUE);
c.drawCircle(50, 50, 40, b);
}
}
Basically the answer is: Your view is not receiving touch events, because it's not in views hierarchy, so click callback is also not called. Actually, view should be in View hierarchy (in other words, added to some upper level view and finally, to the window) in order to participate is user interactions.
So, if You want to implement somthing covering Your view, then it should be ViewGroup or shouldn't be related to view at all and be some abstract container.
E.g. using the way below click works without any issues (but You will need second view or modify BallView to draw bmp):
DrawView:
public class DrawView extends FrameLayout implements View.OnClickListener {
BallView ball = new BallView(getContext());
public DrawView(Context context) {
this(context, null, 0);
}
public DrawView(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public DrawView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
addView(ball);
ball.setOnClickListener(this);
}
#Override
public void onClick(View v) {
Toast.makeText(getContext(), "Mock", Toast.LENGTH_SHORT).show();
}
}
BallView class stays the same.
Layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.ruinalst.performance.tests.views.DrawView
android:id="#+id/oscillator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" />
</RelativeLayout>
I have around twenty custom buttons in my app, with a different image for each button.
I know that to create a click effect I have to create an XML resource like this, I have to created 20 different XML resources for my buttons.
Is there a better way to get the same result without creating separate XML resources for each button?
Update:
Can we make the button translucent when it is clicked.
Found the solution Here.
public class SAutoBgButton extends Button {
public SAutoBgButton(Context context) {
super(context);
}
public SAutoBgButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SAutoBgButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void setBackgroundDrawable(Drawable d) {
// Replace the original background drawable (e.g. image) with a LayerDrawable that
// contains the original drawable.
SAutoBgButtonBackgroundDrawable layer = new SAutoBgButtonBackgroundDrawable(d);
super.setBackgroundDrawable(layer);
}
/**
* The stateful LayerDrawable used by this button.
*/
protected class SAutoBgButtonBackgroundDrawable extends LayerDrawable {
// The color filter to apply when the button is pressed
protected ColorFilter _pressedFilter = new LightingColorFilter(Color.LTGRAY, 1);
// Alpha value when the button is disabled
protected int _disabledAlpha = 100;
public SAutoBgButtonBackgroundDrawable(Drawable d) {
super(new Drawable[] { d });
}
#Override
protected boolean onStateChange(int[] states) {
boolean enabled = false;
boolean pressed = false;
for (int state : states) {
if (state == android.R.attr.state_enabled)
enabled = true;
else if (state == android.R.attr.state_pressed)
pressed = true;
}
mutate();
if (enabled && pressed) {
setColorFilter(_pressedFilter);
} else if (!enabled) {
setColorFilter(null);
setAlpha(_disabledAlpha);
} else {
setColorFilter(null);
}
invalidateSelf();
return super.onStateChange(states);
}
#Override
public boolean isStateful() {
return true;
}
}
}
Use this in xml:
<net.shikii.widgets.SAutoBgButton
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:background="#drawable/button_blue_bg"
android:text="Button with background image" />
I'm creating an Android app that searches for items based on the visible area of the MapView. Is there a way to set up a listener on my MapView to detect when a map has been panned or zoomed?
try mapview-overlay-manager, it is a extension for overlayer for android maps,
it has some simplified OnGestureListener, few example:
onSingleTap(MotionEvent, ManagedOverlay, GeoPoint, OverlayItem)
onDoubleTap(MotionEvent, ManagedOverlay, GeoPoint, OverlayItem)
onLongPress(MotionEvent, ManagedOverlay, GeoPoint, OverlayItem)
onZoom(ZoomEvent, ManagedOverlay)
onScrolled(...)
link:http://code.google.com/p/mapview-overlay-manager/ hope it helps
You can create a SimpleMapView that extends MapView.
public class SimpleMapView extends MapView {
private int currentZoomLevel = -1;
private GeoPoint currentCenter;
private List<ZoomChangeListener> zoomEvents = new ArrayList<ZoomChangeListener>();
private List<PanChangeListener> panEvents = new ArrayList<PanChangeListener>();
public SimpleMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public SimpleMapView(Context context, String apiKey) {
super(context, apiKey);
}
public SimpleMapView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
*
* #return
*/
public int[][] getBounds() {
GeoPoint center = getMapCenter();
int latitudeSpan = getLatitudeSpan();
int longtitudeSpan = getLongitudeSpan();
int[][] bounds = new int[2][2];
bounds[0][0] = center.getLatitudeE6() - (latitudeSpan / 2);
bounds[0][1] = center.getLongitudeE6() - (longtitudeSpan / 2);
bounds[1][0] = center.getLatitudeE6() + (latitudeSpan / 2);
bounds[1][1] = center.getLongitudeE6() + (longtitudeSpan / 2);
return bounds;
}
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
GeoPoint centerGeoPoint = this.getMapCenter();
if (currentCenter == null ||
(currentCenter.getLatitudeE6() != centerGeoPoint.getLatitudeE6()) ||
(currentCenter.getLongitudeE6() != centerGeoPoint.getLongitudeE6()) ) {
firePanEvent(currentCenter, this.getMapCenter());
}
currentCenter = this.getMapCenter();
}
return super.onTouchEvent(ev);
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if(getZoomLevel() != currentZoomLevel){
fireZoomLevel(currentZoomLevel, getZoomLevel());
currentZoomLevel = getZoomLevel();
}
}
#Override
public void setSatellite(boolean on){
super.setSatellite(on);
}
#Override
public MapController getController(){
return super.getController();
}
private void fireZoomLevel(int old, int current){
for(ZoomChangeListener event : zoomEvents){
event.onZoom(old, current);
}
}
private void firePanEvent(GeoPoint old, GeoPoint current){
for(PanChangeListener event : panEvents){
event.onPan(old, current);
}
}
public void addZoomChangeListener(ZoomChangeListener listener){
this.zoomEvents.add(listener);
}
public void addPanChangeListener(PanChangeListener listener){
this.panEvents.add(listener);
}
}
You have the Listeners you can put the code for pan or zoom.
Then in your xml:
<com.androidnatic.maps.SimpleMapView android:clickable="true"
android:layout_height="match_parent" android:id="#+id/mapView"
android:layout_width="match_parent"
android:apiKey="xxx">
</com.androidnatic.maps.SimpleMapView>
And then in your code you can specify the Pan Listener:
mapView.addPanChangeListener(new PanChangeListener() {
#Override
public void onPan(GeoPoint old, GeoPoint current) {
//TODO
}
});
Sadly, there is no built-in functionality to do this in the MapView tools (a strange oversight since this functionality is in the JavaScript SDK, as well as the iOS SDK).
You can deal with this easily enough though by using a Runnable and just polling the MapView. I do this by keeping track of the "last" state of:
getLatitudeSpan();
getLongitudeSpan();
getCenter();
getZoomLevel();
And then comparing them to the current values. If the values of changed, you know the map view has moved. If not, you can do nothing.
Either way, reschedule the runnable for another run after 500ms or so and repeat the process. You can use onResume() and onPause() to remove the callback for the Runnable and restart it as necessary.
The MapView class can track changes using the onLayout method.
i.e.
class CustomMapView extends MapView {
protected void onLayout(boolean changed,int left, int right, int top, int bottom){
super.onLayout(changed,left, right, top, bottom);
if(changed){
// do something special
}
}
}
The only way that I can think of is to extends the MapView and override the OnTouchEvent and watch for the Up action. This will tell you that the user has finished moving and you can get the lat/lon span to determine the region you should check out.
Old question, but I wrote a class a while back called ExMapView which fires an event when the map region starts changing (onRegionBeginChange) and when it stops changing (onRegionEndChange). This class is for use with the old MapView, not V2.0. Hope it helps someone.
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
public class ExMapView extends MapView {
private static final String TAG = ExMapView.class.getSimpleName();
private static final int DURATION_DEFAULT = 700;
private OnRegionChangedListener onRegionChangedListener;
private GeoPoint previousMapCenter;
private int previousZoomLevel;
private int changeDuration; // This is the duration between when the user stops moving the map around and when the onRegionEndChange event fires.
private boolean isTouched = false;
private boolean regionChanging = false;
private Runnable onRegionEndChangeTask = new Runnable() {
public void run() {
regionChanging = false;
previousMapCenter = getMapCenter();
previousZoomLevel = getZoomLevel();
if (onRegionChangedListener != null) {
onRegionChangedListener.onRegionEndChange(ExMapView.this, previousMapCenter, previousZoomLevel);
}
}
};
public ExMapView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ExMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public ExMapView(Context context, String apiKey) {
super(context, apiKey);
init();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
isTouched = event.getAction() != MotionEvent.ACTION_UP;
return super.onTouchEvent(event);
}
#Override
public void computeScroll() {
super.computeScroll();
// If the map region is still changing (user is still scrolling or zooming), reset timer for onRegionEndChange.
if ((!isTouched && !getMapCenter().equals(previousMapCenter)) || (previousZoomLevel != getZoomLevel())) {
// If the region has just begun changing, fire off onRegionBeginChange event.
if (!regionChanging) {
regionChanging = true;
if (onRegionChangedListener != null) {
onRegionChangedListener.onRegionBeginChange(this, previousMapCenter, previousZoomLevel);
}
}
// Reset timer for onRegionEndChange.
removeCallbacks(onRegionEndChangeTask);
postDelayed(onRegionEndChangeTask, changeDuration);
}
}
private void init() {
changeDuration = DURATION_DEFAULT;
previousMapCenter = getMapCenter();
previousZoomLevel = getZoomLevel();
}
public void setOnRegionChangedListener(OnRegionChangedListener listener) {
onRegionChangedListener = listener;
}
public void setChangeDuration(int duration) {
changeDuration = duration;
}
public interface OnRegionChangedListener {
public abstract void onRegionBeginChange(ExMapView exMapView, GeoPoint geoPoint, int zoomLevel);
public abstract void onRegionEndChange(ExMapView exMapView, GeoPoint geoPoint, int zoomLevel);
}
}