I have a SurfaceView that takes up part of the screen, and some buttons along the bottom. When a button is pressed and the user drags, I want to be able to drag a picture (based on the button) onto the SurfaceView and have it drawn there.
I want to be able to use clickListeners and the like, and not just have a giant SurfaceView with me writing code to detect where the user pressed and if it's a button, etc.
I have somewhat of a solution, but it seems a bit of a hack to me. What is the best way to accomplish this using the framework intelligently?
Part of my XML:
<RelativeLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/background">
<!-- Place buttons along the bottom -->
<RelativeLayout android:id="#+id/bottom_bar"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="40dip"
android:layout_alignParentBottom="true"
android:background="#null">
<ImageButton android:id="#+id/btn_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:background="#null"
android:src="#drawable/btn_1">
</ImageButton>
<!-- More buttons here... -->
</RelativeLayout>
<!-- Place the SurfaceView in a frame so we can stack on top of it -->
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="0px"
android:layout_weight="1"
android:layout_above="#id/bottom_bar">
<com.project.question.MySurfaceView
android:id="#+id/my_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</FrameLayout>
And the relevant Java code in MySurfaceView, which extends SurfaceView. mTouchX and Y are used in the onDraw method to draw the image:
#Override
public boolean onTouchEvent(MotionEvent event){
mTouchX = (int) event.getX();
mTouchY = (int) event.getY();
return true;
}
public void onButtonTouchEvent(MotionEvent event){
event.setLocation(event.getX(), event.getY() + mScreenHeight);
onTouchEvent(event);
}
Finally, the activity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.my_surface);
mView = (MySurfaceView) findViewById(R.id.my_view);
mSurfaceHeight = mView.getHeight();
mBtn = (ImageButton) findViewById(R.id.btn_1);
mBtn.setOnTouchListener(mTouchListener);
}
OnTouchListener mTouchListener = new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
int [] location = new int[2];
v.getLocationOnScreen(location);
event.setLocation(event.getX() + location[0], event.getY());
mView.onButtonTouchEvent(event);
return true;
}
};
Strangely, one has to add to the x-coordinate in the activity, then add to the y coordinate in the View. Otherwise, it doesn't show up in the correct position. If you add nothing, something drawn using mTouchX and mTouchY will show up in the upper left corner of the SurfaceView.
Any direction would be greatly appreciated. If I'm going about this completely the wrong way, that would be good information too.
I'm not sure if I completely understand what you're trying to do, but I'll try to give you some useful information anyway:
Is your main problem with the coordinates?
You could have an invisible view on top of everything (which is an ugly solution), or you could use some math. You get the coordinates of the view, which, together with information about the display size and total view size can give you all the information you need
As previously mentioned, I am not entirely sure where you run into problems, but in my opinion your approach seems fine.
Oh and one more thing:
You should try to make use of the MotionEvent called ACTION_MOVE with
float x = event.getX();
float y = event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
//DO SOMETHING
case MotionEvent.ACTION_MOVE:
//DO SOMETHING using x and y.
}
within your onTouch.
Related
I have used 4 different colored custom shape buttons. I am trying to implement an ontouch listener by getting the pixel color as shown below
#Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
return isPixelTransparent(v, x, y) || v.onTouchEvent(event);
}
private boolean isPixelTransparent(View v, int x, int y) {
Bitmap bmp = Bitmap.createBitmap(v.getDrawingCache());
int color = Color.TRANSPARENT;
try {
color = bmp.getPixel(x, y);
} catch (IllegalArgumentException e) {
// x or y exceed the bitmap's bounds.
// Reverts the View's internal state from a previously set "pressed" state.
v.setPressed(false);
}
return color == Color.TRANSPARENT;
}
However the rectangular area of the buttons overlap so the buttons underneath dont get pressed if pixel is transparent. Also please note all the buttons have large white text. I have done lots of searching but cannot figure out how to go about it, any help would be much appreciated please.
XML
<za.co.####.####.DiamondShapeButton
android:id="#+id/button_resources"
style="#style/NavigationButton"
android:layout_width="150dp"
android:layout_height="150dp"
android:text="#string/resources"
custom:diamondColor="#color/red"/>
<za.co.####.####.DiamondShapeButton
android:id="#+id/button_practise"
style="#style/NavigationButton"
android:layout_width="150dp"
android:layout_height="150dp"
android:text="#string/practise"
custom:diamondColor="#color/blue"/>
<za.co.####.####.DiamondShapeButton
android:id="#+id/button_tests_exams"
style="#style/NavigationButton"
android:layout_width="150dp"
android:layout_height="150dp"
android:text="#string/my_tests_exams"
custom:diamondColor="#color/orange_light"/>
<za.co.####.####.DiamondShapeButton
android:id="#+id/button_track_studies"
style="#style/NavigationButton"
android:layout_width="150dp"
android:layout_height="150dp"
android:text="#string/track_studies"
custom:diamondColor="#color/black"/>
</za.co.####.####.DiamondShapeLayout>
A View only knows about its own rectangular space, not that of the views that might be behind it. So if a View has a transparent pixel, it will only know that its own pixel is transparent, not anything about the views that may be behind it.
If you want to have a view bypass some event handling, it will need to know about the views behind it and if they wish to handle the event instead. You can't do this with pixel color of a single view - you will have to code this logic yourself based on the overall layout of the views.
I have scoured the web in search of an answer to a question that has been bugging me for days. I am making a widget that allows a user to pan the entire widget by dragging (around the Y axis only) within the viewing area. I then have a smaller view (like a button, listens to onTouchEvent) inside here that the user can also drag. The idea is that the user will drag this view up or down and if they want to go higher or lower they can pan the entire widget and then continue dragging the view higher or lower. Now, I'm having two issues with this. First off, after dragging the main view (whole widget) the view that I would like to then move still resides in the area it once was before translating the entire canvas.
So for example, if I move the entire canvas by 50 in the Y position, I then want to move the smaller view but have to touch in the old position rather than the offsetted one (by 50). So it looks like I don't click the smaller view where it is now, but where it used to be (I hope this makes sense).
Next issue, after I move the smaller view. I am not able to trigger anymore touch events. I'm assuming that's because it was moved outside of its parent (which is still viewable since I set clipChildren to false) and its "z-order" is lower than the other views in the layout. Is there a way to always position this on top? I am using Android API 17. Even if I set this smaller view (that I am moving when touched and ACTION_MOVE event is triggered) as the last child in the main layout, I need to be able to drag it out of here (which I can) and continue to drag it on a new onTouchEvent. ANY help is greatly appreciated. I added some layout xml and code snippets below to help.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
android:id="#+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:clipChildren="false"
android:clipToPadding="false">
<ImageView
android:id="#+id/ref_image"
android:layout_width="wrap_content"
android:layout_height="216dp"
android:src="#drawable/my_ref_image"
android:layout_centerInParent="true"
android:clipChildren="false"/>
<RelativeLayout
android:id="#+id/selection_view"
style="#style/MyStyle"
android:layout_alignTop="#id/ref_image"
android:layout_marginTop="116dp"
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
android:id="#+id/selection_area"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:clipChildren="false"
android:clipToPadding="false">
<ImageView
android:id="#+id/underlay_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/translucent_image"
android:layout_centerInParent="true"/>
<ImageView
android:id="#+id/point_area"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/point_image"
android:layout_centerInParent="true"/>
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
So the snippets of code that is of interest:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (DEBUG) {
Log.d(TAG, TAG + "::onDraw: ");
}
canvas.translate(0, backgroundPositionY);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
Log.d(TAG, TAG + "::onTouchEvent: parent touch event");
switch (action) {
case MotionEvent.ACTION_DOWN: {
final float y = ev.getY();
// Remember where we started
lastTouchY = y;
if (DEBUG) {
Log.d(TAG, TAG + "::onTouchEvent: parent ACTION_DOWN");
}
return true;
}
case MotionEvent.ACTION_MOVE: {
final float y = ev.getY();
// Calculate the distance moved
final float dy = y - lastTouchY;
backgroundPositionY += dy
// Remember this touch position for the next move event
lastTouchY = y;
// Invalidate to request a redraw
invalidate();
break;
}
case MotionEvent.ACTION_UP: {
lastTouchY = ev.getY();
}
}
return false;
}
// called upon initialization (selectionArea refs view id = selection_area)
private void initPointListener() {
this.setClipChildren(false);
this.setClipToPadding(false);
if (selectionArea != null) {
selectionArea .setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
RelativeLayout.LayoutParams pointParams = (RelativeLayout.LayoutParams) selectionArea.getLayoutParams();
final int action = motionEvent.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: {
pointLastTouchY = motionEvent.getY();
}
break;
}
case MotionEvent.ACTION_MOVE: {
float moveY = motionEvent.getY();
final float dy = moveY - pointLastTouchY;
pointPosY += dy;
selectionArea.setY(pointPosY);
break;
}
}
return true;
}
});
}
}
Note that I have tried setting the padding and I have referenced the following on SO with no good results:
How to translate the canvas and still get the touch events on the correct places
Android : click / touch event not working after canvas translate
Alright, well... I found a work around. Basically, my parent view (the widget itself) could always see touch events. But the children could not given the circumstances I mentioned above. So, what I did was keep track of the x and y positions of the child view I was dragging relative to the viewing area. Then whenever I received a touch event I would check to see if I touched the child area or not. If I happened to trigger the onTouchEvent for the child when it was in its old location, I would ignore it if it didn't pass this check. Its sloppy, but its the only way I could get it to work. Now I have a pannable view and a draggable object
I'm not sure did I understand everything on your problem right but maybe this could help you
View with horizontal and vertical pan/drag and pinch-zoom You should try the code from Alex's answer. The code is doing some extra things but you could just take scalelistener (for zoom) off and ofcourse the panning for X-axis. That code is also moving childviews "inside" the canvas so then you shouldn't have to translate click positions after onTouch called.
The better solution is the leave your view in place. Then apply a matrix transformation to your view and the inverted matrix to MotionEvents. This way you can always get your view properly and klugelessly delegated your events, and all the panning takes place in the drawing of the scene. Moving the view around will also hit a basically unpassable problem that you cannot get the MotionEvents that start outside of the bounds of your real view. So if you zoom out, and press something outside the the area where the screen would be it ignores that and doesn't dispatch the touch events there.
Also, it goes insane on some implementations, I think galaxy tab 10, it was just horribly fubar and impossible wrong. It does the invalidation of the view a bit differently than other tablets and refreshed the wrong parts of the view.
in my application i have one bag-round image with 5 icons how can i put on-click actions for individual icons in the android in my case 5 icons is place in the one image and that image used as bag-round image in my app.
Please help me thanks in Advance......
You can use ImageButton and listen its on click method:
imgB =(ImageButton)findViewById(R.id.Image_Button_ID);
imgB.setOnClickListener(new OnClickListener() {
// write your code here
});
Or if you want to specify the method in the xml:
<ImageButton android:id="#+id/Image_Button_ID"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/blah"
android:onClick="cutsom_onclick_method" />
Hopefully I am not misunderstanding, it is not very clear to me what you need.
OnTouchListener will give you the location of the click which you can use to determine where you have clicked.
imageView.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_DOWN){
int x = (int) e.getX();
int y = (int) e.getY();
}
// then do some calculation with x and y and where they are
return true;
}
});
The other way is to use five ImageButtons and five different images
I need to have some text with a drawable on the left and I want to execute some code when the user clicks/touches the image (only the image, not the text), so I used a LinearLayout with a TextView and an ImageView which is clickable and launches an onClick event. The XML parser suggests me to replace this with a TextView with a compound drawable, which would draw the same thing with far less lines of XML.. My question is "can I specify I want to handle an onClick event only on the drawable of the TextView and not on the TextView itself? I've seen some solutions which involves writing your own extension of TextView, but I'm only interested in being able to do it within the layout resource, if possible, otherwise I'll keep the following XML code:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="bottom"
android:paddingTop="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:text="#string/home_feedback_title"
android:textColor="#android:color/primary_text_dark"
android:textStyle="bold"
android:paddingBottom="4dp"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/action_feedback"
android:clickable="true"
android:onClick="onClickFeedback"
android:contentDescription="#string/action_feedback_description"/>
</LinearLayout>
Its very simple. Lets say you have a drawable on left side of your TextView 'txtview'. Following will do the trick.
TextView txtview = (TextView) findViewById(R.id.txtview);
txtview.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP) {
if(event.getRawX() <= txtview.getTotalPaddingLeft()) {
// your action for drawable click event
return true;
}
}
return true;
}
});
If you want for right drawable change the if statement to:
if(event.getRawX() >= txtview.getRight() - txtview.getTotalPaddingRight())
Similarly, you can do it for all compound drawables.
txtview.getTotalPaddingTop();
txtview.getTotalPaddingBottom();
This method call returns all the padding on that side including any drawables. You can use this even for TextView, Button etc.
Click here for reference from android developer site.
You can go either way. Using the compound drawable is faster though because it was intended to be an optimization. It uses less ram because you reduce 3 views into 1 and it's faster layout because you lose 1 depth.
If I were you I'd consider stepping back to see if both the text and the image intercepting the touch to do whatever action is possibly a good thing. In general having a larger touch region makes it easier to press. Some users may actually be inclined to touch the text instead of the image.
Lastly if you go that route of merging the 2 you might want to consider using a Button instead of a TextView. You can style the button to not have the rectangle around it. They call it a borderless button. It's nice because you get visual feedback that you clicked on a actionable item where as an ImageView or TextView normally aren't actionable.
How to Create Borderless Buttons in Android
#Vishnuvathsan's answer is almost perfect, but getRaw() returns an absolute x position of the touch point. If the textview is located not on the left edge of the view, you should compare with the absolute position of the textview by using getLocationOnScreen. Code below is an example to check both left drawable tap and right drawable tap.
textView.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
int[] textLocation = new int[2];
textView.getLocationOnScreen(textLocation);
if (event.getRawX() <= textLocation[0] + textView.getTotalPaddingLeft()) {
// Left drawable was tapped
return true;
}
if (event.getRawX() >= textLocation[0] + textView.getWidth() - textView.getTotalPaddingRight()){
// Right drawable was tapped
return true;
}
}
return true;
}
});
final int DRAWABLE_LEFT = 0;
final int DRAWABLE_TOP = 1;
final int DRAWABLE_RIGHT = 2;
final int DRAWABLE_DOWN = 3;
This click listener is getting in on touch listener
if (event.getAction() == MotionEvent.ACTION_DOWN)
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (event.getX() >= (tvFollow.getTop() - tvFollow.getCompoundDrawables()[DRAWABLE_TOP].getBounds().width())) {
// your action here
return true; } }
Since getRaw() and getRight() both returns in regards with the entire screen coordinates, this will not work if your views are not on the left edge of the screen. The below solution can be used anywhere regardless of layout position. This is for right side drawable touch.
textView.setOnTouchListener(OnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_UP)
{
if (event.x >= textView.width - textView.totalPaddingEnd)
{
<!---DO YOUR ACTIONS HERE--->
return#OnTouchListener true
}
}
true
})
PS: Kotlin code
I'm writing a SMIL composer for a class, and I was planning on making the canvas support drap and drop, so you can place pictures and text however you want. I've looked through examples and did a few on my own, but when I go to implement the drag and drop in my project, it doesn't work. Here is the main code that matters:
public class ComposerActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.composer);
Button add = (Button)findViewById(R.id.addBtn);
...
add.setOnClickListener(mClick);
...
}
OnClickListener mClick = new OnClickListener() {
#Override
public void onClick(View v){
if(v.getId() == R.id.addBtn)
{
FrameLayout fl = (FrameLayout)findViewById(R.id.Canvas);
LayoutInflater inflater = (LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
View itemView = inflater.inflate(R.layout.image_add, null);
View image = (View)itemView.findViewById(R.id.image);
image.setBackgroundResource(R.drawable.icon);
fl.addView(itemView, new FrameLayout.LayoutParams(40, 40));
image.setOnTouchListener(drag);
}
...
OnTouchListener drag = new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event){
FrameLayout.LayoutParams par = (LayoutParams) v.getLayoutParams();
switch(v.getId()){//What is being touched
case R.id.image:{//Which action is being taken
switch(event.getAction()){
case MotionEvent.ACTION_MOVE:{
par.topMargin = (int)event.getRawY() - (v.getHeight());
par.leftMargin = (int)event.getRawX() - (v.getWidth()/2);
v.setLayoutParams(par);
break;
}//inner case MOVE
case MotionEvent.ACTION_UP:{
par.height = 40;
par.width = 40;
par.topMargin = (int)event.getRawY() - (v.getHeight());
par.leftMargin = (int)event.getRawX() - (v.getWidth()/2);
v.setLayoutParams(par);
break;
}//inner case UP
case MotionEvent.ACTION_DOWN:{
par.height = 60;
par.width = 60;
v.setLayoutParams(par);
break;
}//inner case UP
}//inner switch
break;
}//case image
}//switch
return true;
}//onTouch
};//drag
}
Here is the composer.xml:
...
<FrameLayout android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="#+id/TopBar"
android:layout_above="#+id/addBtn"
android:id="#+id/Canvas"
android:layout_gravity="top">
</FrameLayout>
...
and the image_add.xml:
<View android:id="#+id/image"
android:layout_gravity="top"
xmlns:android="http://schemas.android.com/apk/res/android"/>
When I click the add button on my composer, it successfully adds the image to the canvas, and when I touch the image, it responds by getting bigger, from 40x40 to 60x60 like it should. But it doesn't follow my finger across the screen, and thats where I'm stuck.
Any input is appreciated.
I studied the Android Launcher code to see how they did drag and drop. I really like their object model. What they did, and it might be good for you to consider, is move the responsibility for handling drag and drop out to the ViewGroup in which all the draggable views are inside of. Touch events get handled there rather than in your views.
Two of the key classes:
DragLayer – implements a custom ViewGroup, which coordinates movement of views on the screen. The Launcher DragLayer is a subclass of FrameLayout.
DragController – This object is the controller that does most of the work to support dragging and dropping.
The other thing they do is not move the actual view until you drop the object you are dragging. It does not look that way though because on the screen you see a bitmap constructed from the view moving as your finger (or pointer) moves.
I built a simple example and wrote it up. See http://blahti.wordpress.com/2011/01/17/moving-views-part-2/
Source code of the example is there too.
If you're looking for a pre-made solution, there's this:
https://github.com/thquinn/DraggableGridView
It's a custom ViewGroup I put together that seems like it might be suited to your project. Good luck!