I'm working on a game and ran into a bit of trouble, I might not be doing this correctly as I'm new to the graphic side of Android.
I have a SurfaceView and an ArrayList of my own Card object which extends View. I override the onDraw method for the Card object, and then in the SurfaceView's onDraw I draw everything. The drawing part works as it should.
I now try to detect when an individual card is touched using the onTouchListener, I set the listener for each card, but it detects the touch as if the view that being touched is the SurfaceView. It's possible that my whole way of thinking of this is wrong, so I'm asking for your advice.
Some code:
public GameSurfaceView(Context context) {
super(context);
GAME_STATE = GameState.LOADING;
this.context = context;
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Canvas c = holder.lockCanvas(null);
onDraw(c);
holder.unlockCanvasAndPost(c);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
});
}
SurfaceView onDraw():
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRGB(30, 180, 30);
for (Card card : user.getTableCards()) {
card.draw(canvas);
}
}
Card's onDraw():
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(getBitmap(), x, y, null);
}
and onTouch
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TOUCH", "Class: " + v.getClass().toString());
if (v.getClass() == Card.class) {
Log.d("CARD", "Touched: " + ((Card) v).getValue());
}
return true;
}
I'm always getting the SurfaceView class being logged.
Is this even the right way of doing something like that? I need to detect touch on individual objects of the same class..
I currently do this by adding a member of type Rect to each card and then looping over and checking if the x and y of the touch are contained in that rect, but it seems like an expensive way, iterating over each card (even with max of 52) and checking it..
Thanks
You're mixing up two different concepts, I'll explain both in A) and B).
You have two options:
A) If you want to continue using SurfaceView (which might be the right choice for game, since it gives you more control over how things are drawn), your Card object should extend Drawable, not View. Drawable is a dumb class that you just draw to a canvas or pass to widgets as their background etc. Android doesn't need to know much more about it. In this case you will need to check what card was hit yourself, like you described. Android can not handle this for you. You're doing this with a View instead of Drawable, this is wrong! Or at least a huge waste. See below for how to do it properly with views.
B) Use views, which will handle touch events and many other things for you.
View is a complex class that is supposed to exist in android's view hierarchy. The way you're using it you're throwing away most of it's functionality - when you draw a view straight to a canvas android isn't really aware of the view at all, so it can't handle touch events.
A proper view-based implementation of your cards could look like this: Use a viewgroup as your base, this is what you set in setContentView(). This ViewGroup could just be a LinearLayout, or if you want the ability to set an arbitrary position and size on your cards of your cards, you could use AbsoluteView, although that's deprecated.
Your cards are then added to the ViewGroup using addView(). The positioning of your Card view within this is governed by a LayoutParams object that you pass to addView(). This way android is aware of it's existence, and will handle touch events like this:
The touch event first goes to your ViewGroup
The ViewGroup onTouchEvent will send the event down to down to it's children's onTouchEvent (which have been added via addView()). So you just override this method in your Card class. You inherently know which Card's onTouchEvent was called since it gets called on the object itself.
In your Card's onTouchEvent, you also let the parent know that you've handled the touch event by returning true. If you don't do this, the parent can handle the event instead.
To be perfectly clear: You were NOT adding the views to SurfaceView, in fact, you can't since it is a not a ViewGroup. It's just a complex View object that allows you to do arbitrary drawing operations within it. But it is always a leaf of the View hierarchy, and can only handle it's own onTouchEvents, it will not send it down to children because it can't have any.
your are drawing cards on a SurfaceView, it's like drawing image on a wall when you come to touch the image you're basically touching the wall, the image is now a part of the wall you can't separate them.
Here's an idea that may help:
you have a Card which extends View, why don't you just Use a RelativeLayout instead of your SurfaceView and ADD your cards to it (side by side maybe). This way you have a concrete view for each card that you can register an OnClickListener to each and handle your touch event...
Related
I am writing an application in Java for android that draws filled circle shapes on canvas.
I have written a draw circles class to handle events and the actual drawing of the circles:
public class drawCircles extends android.support.v7.widget.AppCompatImageView {
float x, y;
Paint paint = new Paint();
PointF pointf = new PointF();
ArrayList<PointF> locations = new ArrayList<PointF>();
public drawCircles(Context context) {
super(context);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
x = event.getX();
y = event.getY();
pointf = new PointF(x, y);
locations.add(pointf);
invalidate();
return true;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Random rand = new Random();
int n = rand.nextInt(4) + 1;
switch(n)
{
case 1:
paint.setColor(GREEN);
break;
case 2:
paint.setColor(RED);
break;
case 3:
paint.setColor(YELLOW);
break;
case 4:
paint.setColor(BLUE);
break;
}
int i=0;
while(i < locations.size())
{
canvas.drawCircle(locations.get(i).x,locations.get(i).y,50, paint);
i = i + 1;
}
}
}
public class MainActivity extends AppCompatActivity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
drawCircles draw = new drawCircles(this);
setContentView(draw);
}
}
My application works just fine as long as the setContentView method in the onCreate() method for the main activity is set to an object of the drawCircles class
My question, is when you point setContentView at an instance of a class like this, is it possible to customise the layout? I want to add widgets for paint brush size etc.
My first thought was to set the content view to a customised xml file then call methods from the drawCircle class where appropriate however if I am understanding the reading I have done correctly, it is not possible to call the onDraw() method from a class that doesn't extend a view, so I can't call them from my main class which extends an activity class for backwards compatibility.
It has occured to me that it may be possible to assign the drawCircles class a particular layout.xml file but I haven't been able to find any cases where this has been done.
Any advise is greatly appreciated
You can do whatever you want to a view either way. The two functions run the same code- the one that takes a layout identifier just inflates the layout into a View, then calls setContentView on the result.
Its also quite possible to put a custom view in xml, in which case its onDraw will be called. I think you don't quite understand how xml inflation works, which makes it difficult to figure out how to help you.
Also, you would never call onDraw directly. Its called by the framework when a view has an invalid area.
Yes you can, let see some custom libraries like custom dialog, calendar,... they define many case for properties.
In xml, just choose property value then the custom view class will do every things.
The first thing you need is learn more about custom view, collect attribute, set conditions...
Let see some samples:
http://www.vogella.com/tutorials/AndroidCustomViews/article.html
http://trickyandroid.com/protip-inflating-layout-for-your-custom-view/
I know how to do this within the activity class, however this doesn't fit my need. Within my class that extends View, once a method is called i would like the screen orientation to be locked. And then once another method is called i would like it to be unlocked.
Is there a way i can do this within my class that extends View?
Thanks.
#Override
public void onConfigurationChanged(Configuration newConfig) {
// TODO Auto-generated method stub
super.onConfigurationChanged(newConfig);
setContentView(this.gameView);
if (this.gameView.isOrientationChange() == false) {
// Stop the screen orientation changing during an event
switch (this.getResources().getConfiguration().orientation) {
case Configuration.ORIENTATION_PORTRAIT:
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
break;
case Configuration.ORIENTATION_LANDSCAPE:
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
break;
}
}
else {
// allow screen rotations
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
}
In the class extending View, there is an orientationChange boolean field. this is changed by method indicating whether the devices orientation can be changed. This worked but the screen size also changes when the orientation is changed. So if there is a lot of code dependent on the size and is timed, then this may cause more problems than solve.
i think what you are looking for, is configChanges see doc -> http://developer.android.com/reference/android/R.attr.html#configChanges
Maybe I've got a method, just write down that:
set layoutParams.screenOrientation value into: ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED or ActivityInfo.SCREEN_ORIENTATION_LOCKED
Then, call addView or updateViewLayout to update this LayoutParams.
I want to make an app that can create notification on the screen on top of anything that is currently being displayed. Something like the Go SMS message popup or something like the ChatHead in the following picture:
It would be even better if it is possible to draw it dynamically including touch events.What is the conventional or standard way to do this?
Example:
Like an Icon that can be clicked or dragged no matter whether you are on home screen or app drawer or other apps.Pay attention to the circular icons near the edges of the screen in the picture posted. You can drag them anywhere in any app.
What you are looking for is System Alert Window.
There's a library called StandOut! which will assist you in creating such apps.
Here is how things like Toast and dialog windows work:
In the case where just adding or bringing to front does not work, say when you are having a service add its own view to another client activity or application (FaceUnlock does this), or you cannot depend on hierarchies, you need to use the window manager and a window token to do the trick. You can then create layouts and take advantage of animations and hardware acceleration as before.
WindowManager windowManager = (WindowManager) getBaseContext().getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.FIRST_SUB_WINDOW);
layoutParams.width = 300;
layoutParams.height = 300;
layoutParams.format = PixelFormat.RGBA_8888;
layoutParams.flags =
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
layoutParams.token = getWindow().getDecorView().getRootView().getWindowToken();
//Feel free to inflate here
mTestView = new View(this);
mTestView.setBackgroundColor(Color.RED);
//Must wire up back button, otherwise it's not sent to our activity
mTestView.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackPressed();
}
return true;
}
});
windowManager.addView(mTestView, layoutParams);
Then be sure to remove the view onDestroy (or onPause) or you will crash
if (mTestView != null) {
WindowManager windowManager = (WindowManager) getBaseContext().getSystemService(Context.WINDOW_SERVICE);
if (mTestView.isShown()) {
windowManager.removeViewImmediate(mTestView);
}
}
You don't need a new activity to do this. All you need to do is to add another view into your existing activity and bring it to the front, and draw/write the things that you want into that view.
If you want to do special things with this extra view, you could create your own view class
class DrawOnTop extends View {
public DrawOnTop(Context activity) {
super(activity);
}
#Override
protected void onDraw(Canvas canvas) {
// put your drawing commands here
}
}
and then you do something like
DrawOnTop mDraw = new DrawOnTop(this);
addContentView(mDraw, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
mDraw.bringToFront();
Then to force it to draw, you need to use mDraw.invalidate();
You could have the parent of your whole layout as RelativeLayout. The first child being the "root" of your main layout. Anything after that can be considered an overlay which is placeable to your whims.
Example:
<RelativeLayout>
<LinearLayout>
... Main Layout here ...
</LinearLayout>
<TextView left="20dip" top="20dip" text="Overlay" alpha="0.7" />
</RelativeLayout>
The best way is to start a service with your application.
Create an ImageView.
Set the LayoutParams of the Image View.
Add the view along with the params to the window manager when the service is created.
ALL SET
Your Image sticks to your window (At any screen over all apps), till you application is closed.
You can even add onclicklisteners and ontouchlisteners to the imageview.
Eg. OnClick listeners to perform some actions and Ontouchlisteners move the image along the screen.
I have multiple HorizontalScrollViews inside a ScrollView. Horizontal scroll isn't smooth at all. I have to scroll almost perfectly horizontally for scrolling to work. Is there a simple fix to tweak this ??? Thanks!
You can use Recycler view with Staggered layout manager
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.HORIZONTAL);
RecyclerViewAdapter recyclerViewAdapter = newRecyclerViewAdapter(this);
recyclerView.setAdapter(recyclerViewAdapter); //Don't miss to initialize your adapter
This class creates a ScrollView containing a HorizontalScrollView combined into one class. You can put stuff inside it using the AddChild() method. The dispatchTouchEvent overide keeps the scrolling smooth so you can pan around with a single slide of the finger.
(I recently used this to wrap a programmatically created TextView)
class MultiScrollView extends ScrollView
{
public HorizontalScrollView hscroll;
public MultiScrollView ( Context context )
{
super( context );
}
public void AddChild( View child )
{
hscroll.addView( child );
}
#Override
public boolean dispatchTouchEvent( MotionEvent event )
{
hscroll.dispatchTouchEvent(event);
onTouchEvent(event);
return true;
}
}
If you are using the horizontal scroll view solution from (http://www.dev-smart.com/archives/34) the solution for the cross focus problem between the scroll view and the list view is blocking the focus to the scroll view once you have focus on the list view.
From a technical point of view you should add the following line to the onScroll function inside the HorizontalListView class.
getParent().requestDisallowInterceptTouchEvent(true);
Hope this helps.
I've found the solution and still can't believe that this is what you have to do to make this work normal! Just added blank onClickListener to the each item in the HorizontalScrollView:
item.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
After this slide is really smooth, both upwards and downwards.
In general, you shouldn't be using nested ScrollViews in Android at all, the behaviour of scrolling in this way is unnatural too.
You may want to rethink your layout design, is it anything that couldn't be achieved with an expandable list?
While David's answer works, it has a downside. It passes ScrollView's MotionEvent object directly to HorizontalScrollView.onTouchEvent(), so if HorizontalScrollView or its children try to get the event coordinates, they will get the wrong coordinates which based on ScrollView.
My solution:
public class CustomScrollView extends ScrollView{
/*************skip initialization*************/
#Override
public boolean onInterceptTouchEvent(MotionEvent e){
//returning false means ScrollView is not interested at any events,
//so ScrollView's onTouchEvent() won't be called,
//and all of the events will be passed to ScrollView's child
return false;
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//manually call ScrollView's onTouchEvent(),
//the vertical scrolling happens there.
onTouchEvent(ev);
//dispatch the event,
//ScrollView's child will have every event.
return super.dispatchTouchEvent(ev);
}
}
Just wrap this CustomScrollView around the HorizontalScrollView in your layout file.
I have a LinearLayout, and this LinearLayout will hold dynamically placed views. I need to find out what the width of the children of LinearLayout, however this has to be done in onCreate method. From researching I've found out that you can't use getWidth from this method. So instead I'm using onWindowFocusChanged, which works for the parent LinearLayout (returning me the actual size), but it doesn't work with its children.
Another thing I noticed is that when the screen is fading away and the screen is locked, I can see at the logs the actual width of the children being returned (I think the activity is being paused).
I'm really stuck and this is needed because I need to dynamically place those views depending on the children width.
You might be able to get with the below. But as others pointed out, this probably isn't a great idea.
LinearLayout.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
LinearLayout.getMeasuredWidth();
inside the onCreate , views still can't know the state of the nearby views and the children ,etc... so only after all is prepared and the layout process is done , you can get the size of views .
here's a quick code for getting the size of the view just before it's being drawn:
private static void runJustBeforeBeingDrawn(final View view, final Runnable runnable)
{
final ViewTreeObserver vto = view.getViewTreeObserver();
final OnPreDrawListener preDrawListener = new OnPreDrawListener()
{
#Override
public boolean onPreDraw()
{
Log.d(App.APPLICATION_TAG, CLASS_TAG + "onpredraw");
runnable.run();
final ViewTreeObserver vto = view.getViewTreeObserver();
vto.removeOnPreDrawListener(this);
return true;
}
};
vto.addOnPreDrawListener(preDrawListener);
}
alternatively , you can use addOnGlobalLayoutListener instead of addOnPreDrawListener if you wish.
example of usage :
runJustBeforeBeingDrawn(view,new Runnable()
{
#Override
public void run()
{
int width=view.getWidth();
int height=view.getHeight();
}
});
another approach is to use onWindowFocusChanged (and check that hasFocus==true) , but that's not always the best way ( only use for simple views-creation, not for dynamic creations)
EDIT: Alternative to runJustBeforeBeingDrawn: https://stackoverflow.com/a/28136027/878126
https://stackoverflow.com/a/3594216/1397218
So you should somehow change your logic.