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!
Related
I know the question seems a bit vague, but I am creating an "alchemy" game whereby users drag different earthly elements onto one another to see what the combination produces.
At the moment, I can drag views around the screen, but I want them to be able to interact with each other. For example, when the user drags one ImageView onto another it will produce a new one, either programmatically or in some snazzy way...
I am just wondering what the best approach to this may be?
Here's my code so far, I've removed all the crap I had to show the barebones:
public class MainActivity extends AppCompatActivity {
private ImageView earthImg;
private ImageView waterImg;
private ViewGroup rootLayout;
private int _xDelta;
private int _yDelta;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rootLayout = findViewById(R.id.root_view);
earthImg = rootLayout.findViewById(R.id.earthView);
waterImg = rootLayout.findViewById(R.id.waterView);
earthImg.setOnTouchListener(new UserTouchListener());
waterImg.setOnTouchListener(new UserTouchListener());
}
private final class UserTouchListener implements View.OnTouchListener {
float dX, dY;
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
dX = view.getX() - event.getRawX();
dY = view.getY() - event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
view.animate()
.x(event.getRawX() + dX)
.y(event.getRawY() + dY)
.setDuration(0)
.start();
break;
default:
return false;
}
rootLayout.invalidate();
return true;
}
}
<?xml version="1.0" encoding="utf-8"?>
<android.widget.RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.android.alchgame.MainActivity">
<ImageView
android:id="#+id/earthView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="59dp"
android:layout_marginStart="59dp"
android:layout_marginTop="70dp"
app:srcCompat="#drawable/earth_element"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<ImageView
android:id="#+id/waterView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/earthView"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="150dp"
android:layout_toEndOf="#+id/earthView"
android:layout_toRightOf="#+id/earthView"
app:srcCompat="#drawable/water_element" />
</android.widget.RelativeLayout>
I added the view above for reference
You could add MotionEvent.ACTION_UP to your switch statement, then calculate the bounds of the view you dragged based on the x and y co-ordinates (which you can get based on your dragging code) and the size of the view. Check if these numbers match the numbers of another view. If they do, you can then do whatever you need, like make a new image, delete the old ones, animate the dragged one back to where it started etc.
Additionally, views have getTop(), getLeft()... etc methods that will return that edge based on the parent.
So you could do something like:
case MotionEvent.ACTION_UP:
//view = view being dragged
String dTag = view.getTag();
for (View mView: myImageViews){
if (!mView.getTag().equals(dTag){
if (view.getTop() <= mView.getBottom() && view.getBottom() >= mView.getBottom()){
if (view.getLeft() <= mView.getRight() && view.getRight() >= mView.getRight()){
//match
}
else if (view.getRight() >= mView.getLeft() && view.getLeft() <= mView.getLeft()){
//match
}
//No match
}
else if (view.getBottom() >= mView.getTop() && view.getTop() <= mView.getTop()){
if (view.getLeft() <= mView.getRight() && view.getRight() >= mView.getRight()){
//match
}
else if (view.getRight() >= mView.getLeft() && view.getLeft() <= mView.getLeft()){
//match
}
//No match
}
}
}
break;
I'm not sure if there is some 'snazzy' way to do this in vanilla Android, but I'm sure there's probably some sort of library for exactly this, or you could do it manually as mentioned above.
Hopefully this has helped.
EDIT:
I'm assuming here that you will make an arraylist of your image views or similar. I would also like to point out that you will need to check if the current view in the loop is the one you are dragging, if it is, you should just move on to the next one. You could achieve this by adding a tag to each view and then checking if the tag matches the tag of the dragged view. I'll update the code above.
Extend View.OnDragListener and set it to a container of each "element"
class MyDragListener implements View.OnDragListener {
Drawable enterShape = getResources().getDrawable(...);
Drawable normalShape = getResources().getDrawable(...);
#Override
public boolean onDrag(View v, DragEvent event) {
View element = (View) event.getLocalState();
View elementContainer = (View) element.getParent();
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
element.setVisibility(View.INVISIBLE);
break;
case DragEvent.ACTION_DRAG_ENTERED:
v.setBackground(enterShape);
break;
case DragEvent.ACTION_DRAG_EXITED:
v.setBackground(normalShape);
break;
case DragEvent.ACTION_DROP:
// Dropped, reassign Entry to Day
View newContainer = v;
doSnazzyStuff(element, newContainer);
break;
case DragEvent.ACTION_DRAG_ENDED:
v.setBackground(normalShape);
element.setVisibility(View.VISIBLE);
default:
break;
}
return true;
}
private void doSnazzyStuff(View element, View newContainer) {
// do stuff
}
}
and set something like this on each "element" to affect how it looks when it's dragged.
private final class ElementClickListener implements OnLongClickListener {
#Override
public boolean onLongClick(View view) {
ClipData data = ClipData.newPlainText("", "");
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(
view);
view.startDrag(data, shadowBuilder, view, 0);
return true;
}
}
If you create/extend custom views for your "element" and containers then you can do some pretty powerful stuff.
I want to animate a ShadowView to some coordinates (to destination view).
I'm using D&D and if user drops (DragEvent.ACTION_DROP) then the view in some area I want to animate the view (from the drop location) to some destination view.
I don't want to animate view from the source location but want to do from the DROP location.
I tried a lot of things but nothing works. How can I get access to the ShadowView? This also not working:
EventDragShadowBuilder.getView()
I think TranslateAnimation should work for this but I need access to the "shadow" view during the D&D.
Image:
First implement onTouchlistener on the view
llDragable.setOnTouchListener(this);
Make a view draggable
#Override
public boolean onTouch(View view, MotionEvent event) {
float dX = 0;
float dY = 0;
switch (view.getId()){
case R.id.dragableLayout :{
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
dX = view.getX() - event.getRawX();
dY = view.getY() - event.getRawY();
lastAction = MotionEvent.ACTION_DOWN;
break;
case MotionEvent.ACTION_MOVE:
view.setY(event.getRawY() + dY);
view.setX(event.getRawX() + dX);
lastAction = MotionEvent.ACTION_MOVE;
break;
case MotionEvent.ACTION_UP:
//Animate
animate();
if (lastAction == MotionEvent.ACTION_DOWN)
//Toast.makeText(DraggableView.this, "Clicked!", Toast.LENGTH_SHORT).show();
break;
default:
return false;
}
return true;
}
}
return false;
}
Then you can use object animator at case MotionEvent.ACTION_UP using object animation . You need to have the position of the destination.
private void animate() {
Path path = new Path();
path.moveTo(destinationX, destinationY);
path.lineTo(destinationX, destinationY);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mButton, View.X, View.Y, path);
objectAnimator.setDuration(duration);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();
}
One possible thing could be
once the drag event has finished, you can move the "view" to the location of the drop and then you can start an animation to move the view from the dropped location to the destination.
Keep in mind here the drag shadow is not being animated but the view itself is being animated.
As a workaround, you can try to add a selector with transparent and black-gradient drawables, depended on appropriate state. In this case you will decide, when your "shadow" should be shown and when it should be gone.
Here is a question about "shadow" bordered layouts:
Android LinearLayout : Add border with shadow around a linearLayout
Don't sure, is it a good way... But it might work.
Good luck!
Create a duplicate view of the one you are dragging. Once drag ends, you get the location or drop off co-ordinates of the view. Once this happens, set the visibility of your "real" view to View.INVISIBLE. Then set the x and y of the temp invisible view to that of the drop-off point and make it visible. After that, create a translate animation that animates the temp view to the desired position, and don't forget to set the setFillEnabled and setFillAfter properties of the translate animation to true.
I created a class that should be used to allow user to horizontally relocate a view by sliding his finger on a bar, the code is this
public class Dragger implements View.OnTouchListener {
private View toDrag;
private Point startDragPoint;
private int offset;
public Dragger (View bar, View toDragg){
toDrag = toDragg;
dragging = false;
bar.setOnTouchListener(this);
}
public boolean onTouch(View v, MotionEvent evt) {
switch (evt.getAction()) {
case MotionEvent.ACTION_DOWN:
startDragPoint = new Point((int) evt.getX(), (int) evt.getY());
offset = startDragPoint.x - (int) toDrag.getX();
break;
case MotionEvent.ACTION_MOVE:
int currentX = (int)evt.getX();
Log.d("LOG","currentX=" + currentX);
toDrag.setX(currentX-offset);
break;
case MotionEvent.ACTION_UP:
int difference = (startDragPoint.x-offset)-(int)toDrag.getX();
if(difference<-125){
((ViewManager) toDrag.getParent()).removeView(toDrag);
}else{
toDrag.setX(startDragPoint.x-offset);
}
dragging = false;
}
return true;
}
}
In the oncreate of the main activity i just use it by calling his constructor like this:
new Dragger(barItem,itemToDrag);
It apparently work, but it seems to tremble a lot while dragging... I tried to understand why does it work so strangely, so i added a Log instruction in the ACTION_MOVE, and the result is that... even if i only move to right pixel by pixel sometimes the currentX instead of increasing by 1 it decrease of a variable number, sometimes 10, sometimes 18 and when i go to the next pixel it turn back to normal by increasing of 1...
That really doesen't make sense...
Some ideas on how to fix it?
First guess: There are other fingers on the device which causes move to work erratically
Second guess: Right now you seem to be using the setX method more like a moveX and specifying the increment by which the function moves. This might not be what it was designed for
Third Point:
Sometimes the move function acts finicky. It is always run (as in whenever there is a pointer on the screen) and depends on the position of the finger. I would recommend changing the function to make it like a step
Suggestion
Make the action move like a step function that takes one value
case MotionEvent.ACTION_MOVE:
int currentX = (int)evt.getX();
if(abs(currentX)>10)currentX=(int) currentX/5;
toDrag.setX(currentX-offset);
I am newbie in Android, in my app I required draw textview/editview on finger touch where user is able to type in that.I searched a lot but find nothing relevant.
I found links to draw editview/text but not on fingertouch.
You can accomplish this by registering touch events on a view. And when the touch event fires, you can then create a EditText/TextView based on the touch event coordinates.
class YourMainClass extends Activity{
public void onCreate(Bundle bundle)
{
//Do your normal UI initialization here
your_layout.setOnTouchListener(new TouchListener()); //where your_layout is the layout/view of your Activity that should register the touch events.
}
class TouchListener implements OnTouchListener
{
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP) {
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,RelativeLayout.LayoutParams.WRAP_CONTENT); //See note below
params.leftMargin = (int)event.getX() - v.getLeft();
params.topMargin = (int)event.getY() - v.getTop();
EditText edit = new EditText(this);
edit.setLayoutParams(params);
your_layout.addView(edit);
return true;
}
}
}
}
Note: Be sure to change the LayoutParams type to the type of layout you use (for example: LinearLayout.LayoutParams if you're using a LinearLayout to place the EditText in).
Also if you use padding, the coordinates of the actual EditText might be off (since the padding area is still counted as the View when using v.getLeft() and v.getTop(), but not when adding the EditText to your layout).
I'm new to android, and I've been trying for a while to find out how can I retrieve the coordinates of a continuous touch on the screen. for example have 2 vars (x,y) that update in real time as finger moves around. I got it how to find it when the touch is made, but I really don;t get it how to make it return the result after that , when the finger is moving.
I've been trying switch statements, while/for loops in different combination with ACTION_MOVE./ UP/ DOWN .. .still nothing.
I've found like the same question on the website, but the answers only fit for the first step(showing the coordination from the touch only)
I'd really appreciate a solution to this! Thanks!
Without seeing your code I'm just guessing, but essentially if you don't return true to the first call to onTouchEvent, you won't see any of the subsequent events in the gesture (MOVE, UP, etc).
Maybe that is your problem? Otherwise please put code sample up.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final TextView xCoord = (TextView) findViewById(R.id.textView1);
final TextView yCoord = (TextView) findViewById(R.id.textView2);
final View touchView = findViewById(R.id.textView3);
touchView.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
xCoord.setText(String.valueOf((int) event.getX()));
yCoord.setText(String.valueOf((int) event.getY()));
break;
}
case MotionEvent.ACTION_MOVE:{
xCoord.setText(String.valueOf((int) event.getX()));
yCoord.setText(String.valueOf((int) event.getY()));
break;
}
}
return true;
}
});
}
You need to implement an OnTouchListener for whatever view you want to recognize the drag.
Then in the OnTouchListener you need to display the X and Y coordinates. I believe you can get those via MotionEvent.getRawX() and MotionEvent.getRawY()
You can use the MotionEvent.getAction() method to find out when a drag is occurring. I believe the constant is MotionEvent.ACTION_MOVE. Here is some psuedo-code:
Add OnTouchListener interface
public class XYZ extends Activity implements OnTouchListener
Register the listener in the onCreate method
public void onCreate(Bundle savedInstanceState)
{
//other code
View onTouchView = findViewById(R.id.whatever_id);
onTouchView.setOnTouchListener(this);
}
Implement the onTouch method
public boolean onTouch(View view, MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_MOVE)
{
float x = event.getRawX();
float y = event.getRawY();
// Code to display x and y go here
// you can print the x and y coordinate in a textView for exemple
}
}