I have a layout with 2 LinearLayout. The first one is used as a container to contain a graph, and the second one contains few buttons.
When the app starts, at first instance the LinearLayout1 which contains the graph is hidden View.GONE.
Then when I touch a button from LinearLayout2, this layout goes back to its original place using a translate animation.
Finally, I should have the capability to hide again the LinearLayout1. I would like to achieve this by draggin up the LinearLayout2, so when the user has moved a bit upwards the LinearLayout2, the LinearLayouy1 would become again hidden by View.GONE.
This final part is the one with I need some help. I have tried something using an OnTochListener() but I haven't worked too much with this and i'm not sure about how to do it. This is code snnipet where I do this:
/*Layout views*/
private View graphContainer; //This is the LinearLayout1
private View valuesContainer; //This is the LinearLayout2
private float oldY;
private float newY;
...
valuesContainer.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
float y = event.getY();
oldY = y;
break;
case MotionEvent.ACTION_MOVE:
float y2 = event.getRawY();
newY = y2;
if (oldY < newY) {
graphContainer.setVisibility(View.GONE);
}
break;
}
return true;
}
});
Depending on where I touch to do the movement I get to set the visibility to GONE, but the movement is not as I would like, I don't get to move the LinearLayout2.
What you did above is hiding layout2 when the user moves it's finger up.
You say that "you don't get to move the LinearLayout2" -> in order to move a view you need to update it's LayoutParams. You can see an example to this here : How to move a view in Android?
This way you can "push" layout2 up and at some point hide layout1 (using animation, or pushing layout1 as well). Hope this helps.
Edit (a requested code sample) :
BTW - animations transitions is the better way to go when view's params needs changing. It's not the answer to your question since you want a real "drag" feel (when going up).
Also - android L has some beautiful animations (I haven't used yet) that we should keep an eye on.
So... using a layout as follows :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
tools:context=".MyActivity">
<LinearLayout
android:id="#+id/first_layout"
android:background="#android:color/darker_gray"
android:layout_width="match_parent"
android:layout_height="100dp" >
</LinearLayout>
<LinearLayout
android:id="#+id/second_layout"
android:background="#android:color/holo_green_dark"
android:layout_width="match_parent"
android:layout_height="100dp" >
</LinearLayout>
</LinearLayout>
and a corresponding activity as follows :
public class MyActivity extends Activity {
int yDown = 0;
int initialTopMargin = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
LinearLayout layout2 = (LinearLayout)findViewById(R.id.second_layout);
layout2.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event)
{
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams();
switch (event.getAction())
{
case MotionEvent.ACTION_MOVE:
params.topMargin = initialTopMargin - (yDown - (int)event.getRawY());
view.setLayoutParams(params);
break;
case MotionEvent.ACTION_DOWN:
yDown = (int)event.getRawY();
initialTopMargin = params.topMargin;
break;
}
return true;
}
});
}
}
how about using onDragListener ?
valuesContainer.setOnDragListener(new OnDragListener() {
#Override
public boolean onDrag(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
float y = event.getY();
oldY = y;
break;
case MotionEvent.ACTION_MOVE:
float y2 = event.getRawY();
newY = y2;
if (oldY < newY) {
graphContainer.setVisibility(View.GONE);
}
break;
}
return true;
}
});
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 have added drag functinality to my custom editext by overriding the touchevent()
Now the problem is after the edittext is dragged and dropped in a particular position and i want to input text into the edittext by clicking on it, it still getting dragged maybe because the touch event has been overriden and keyboard does not appear to input text
The workaround maybe triggering the dragfunctionality on long press but now the default longpress functionality of the edittext may change
I dont want this to happen
What to do.
mainactivity.java
public class MainActivity extends Activity
{
RelativeLayout dropLayout;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
dropLayout = (RelativeLayout) findViewById(R.id.ondraglayout);
dropLayout.setOnDragListener(new MyDragListener());
EditText et = (EditText) findViewById(R.id.mainEditText1);
}
}
my customedittext.java
public class CustomEdittext extends EditText
{
public CustomEdittext(Context context){
super(context);
}
public CustomEdittext(Context context, AttributeSet attr){
super(context, attr);
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
switch(event.getAction()){
case MotionEvent.ACTION_DOWN :
break;
case MotionEvent.ACTION_MOVE :
ClipData dragdata = ClipData.newPlainText("","");
View.DragShadowBuilder shdwbldr = new View.DragShadowBuilder(this);
this.startDrag(dragdata, shdwbldr, this, 0);
this.setVisibility(View.INVISIBLE);
break;
}
return true;
}
}
mydraglistener.java
public class MyDragListener implements OnDragListener
{
private RelativeLayout.LayoutParams params;
#Override
public boolean onDrag(View v, DragEvent event)
{
View view = (View) event.getLocalState();
switch(event.getAction())
{
case DragEvent.ACTION_DRAG_STARTED:
params = (RelativeLayout.LayoutParams) view.getLayoutParams();
break;
case DragEvent.ACTION_DRAG_ENTERED:
int x = (int) event.getX();
int y = (int) event.getY();
break;
case DragEvent.ACTION_DRAG_EXITED :
break;
case DragEvent.ACTION_DRAG_LOCATION :
x= (int) event.getX();
y = (int) event.getY();
break;
case DragEvent.ACTION_DRAG_ENDED :
break;
case DragEvent.ACTION_DROP:
x = (int) event.getX();
y = (int) event.getY();
params.leftMargin = x;
params.topMargin = y;
view.setLayoutParams(params);
view.setVisibility(View.VISIBLE);
break;
default: break;
}
return true;
}
}
my main.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#CDC2C0"
android:id="#+id/ondraglayout">
<com.mycompany.myapp.CustomEdittext
android:layout_height="wrap_content"
android:ems="10"
android:layout_width="wrap_content"
android:id="#+id/mainEditText1"/>
Your whole code not making sense at all: First let discuss what happening inside your onTouchEvent:
#Override
public boolean onTouchEvent(MotionEvent event)
{
switch(event.getAction()){
case MotionEvent.ACTION_DOWN :
break;
case MotionEvent.ACTION_MOVE :
ClipData dragdata = ClipData.newPlainText("","");
View.DragShadowBuilder shdwbldr = new View.DragShadowBuilder(this);
this.startDrag(dragdata, shdwbldr, this, 0);
this.setVisibility(View.INVISIBLE);
break;
}
return true;
When you put your finger in the screen, the system first trigger MotionEvent.ACTION_DOWN, and afterwards, she will deliver events of MotionEvent.ACTION_MOVE as long as nothing else in the system will consume this events. The meaning of this is your call for startDrag() will call on each movement of the finger, not really make sense right? So first I suggest you to move the code from MotionEvent.ACTION_MOVE into MotionEvent.ACTION_DOWN.
Now to the more important part. When you assign a DragListener to View, the meaning is that this View will receive all the DragEvent that the system will deliver as long as the Listener returns true for the DragEvent.ACTION_DRAG_STARTED.
Now, you assigned your DragListener, to the RelativeLayout. So lets look into your code:
case DragEvent.ACTION_DROP:
x = (int) event.getX();
y = (int) event.getY();
params.leftMargin = x;
params.topMargin = y;
view.setLayoutParams(params);
view.setVisibility(View.VISIBLE);
break;
You set new position for the LayoutParams, but who is the View that will receive this params? The RelativeLayout, not your EditText. Actually your EditText is now INVISBLE as you set it visibilty to INVISIBLE inside your onTouchEvent and never changed it back. Your code "view.setVisibility(View.VISIBLE);" inside the ACTION_DROP is not referring to the EditText but to the RelativeLayout. Unless there is other code you not shown here, this is the case.
Anyway, in your case I would recommend you to move your startDrag() call to a LongCLickListener. I have no clue which functionality of the EditText you think that may change ,as, at least as far as I know, there is no functionality for LongClick in EditText. If you want to avoid it you can also add a flag to your code and turn it on after the ACTION_DROP, and then make the code inside the ACTION_DOWN to run just if this flag is set to false.
I'm trying to add swipe gesture on a basic application which just show a google map. It works fine without map. Here is my solution without the map :
private float x1,x2;
static final int MIN_DISTANCE = 150;
private Point pt;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pt = new Point();
getWindowManager().getDefaultDisplay().getSize(pt);
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
if(event.getX()>pt.x-5){
x1 = event.getX();
Log.d("DEBUUUUG", ""+event.getX());
}else{
x1=0;
}
break;
case MotionEvent.ACTION_UP:
if(x1>=pt.x-5){
x2 = event.getX();
Log.d("DEBUUUUG", "X1="+x1+" X2="+x2);
float deltaX = x2 - x1;
if (Math.abs(deltaX) > MIN_DISTANCE)
{
Toast.makeText(this, "swipe", Toast.LENGTH_SHORT).show ();
}
}
break;
}
return super.onTouchEvent(event);
}
and here is my xml layout with the map (the layout without just has a simple LinearLayout who's empty) :
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapSee"
android:name="com.google.android.gms.maps.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
My goal is to add a right-to-left slider, like on the GoogleMaps app.
Is it possible ?
Thanks.
EDIT
Done, I've found a solution. I don't think it's the best way to do this, but it works.
I've added an invisible button :
<Button
android:id="#+id/btn_slideMenuCM"
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:background="#android:color/transparent"/>
</RelativeLayout>
I've implemented a TouchListener on this button :
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//The action to do
break;
}
return false;
}
It's not a real "swipe gesture", but it looks like.
I have xml in which I have 2 relative layouts, the first one is map(using map fragments) and the second one is ViewPager layout. I added button to map to hide map when clicked, now I want a method to get back the map layout by sweep down the screen.
I tried setting onTouchListener to relative layout but it is not working, also tried implementing OnTouchListener
public class MainActivity extends FragmentActivity implements OnTouchListener
it is not working! how to achieve this?
#Override
public boolean onTouch(View v, MotionEvent event) {
x= event.getX();
y=event.getY();
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
sX = event.getX();
sY = event.getY();
break;
case MotionEvent.ACTION_UP:
fX = event.getX();
fY = event.getY();
if(fX-sX == 0 || fX-sX > 0 || fX-sX <0)
if(fY-sY < 0)
{
if(mapview.getVisibility()==View.GONE)
{
mapview.setVisibility(View.VISIBLE);
}
}
break;
}
return true;
}
Post your code, btw in simple way, ACTION_DOWN is when you touch the screen, ACTION_UP is when you finger release the screen. Look here
I have a ScrollView on top of another view(with Buttons). The ScrollView is taking the whole screen and is obscuring the view that is beneath it.
At some point in my app I need the ScrollView to be disabled (but still visible) and transfer all the touch events to the Buttons that are beneath the ScrollView. How can I do that? Some views like Buttons are automatically doing that when disabled but a ScrollView is not doing that.
Try to implement your own ScrollView which has a flag to indicate the status(disabled/enabled) and also overrides the onTouchEvent and dispatchTouchEvent to let the touch events get pass the ScrollView. Here is an example:
public class DisabledScrollView extends ScrollView {
private boolean mIsDisable = false;
// if status is true, disable the ScrollView
public void setDisableStatus(boolean status) {
mIsDisable = status;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
// no more tocuh events for this ScrollView
if (mIsDisable) {
return false;
}
return super.onTouchEvent(ev);
}
public boolean dispatchTouchEvent(MotionEvent ev) {
// although the ScrollView doesn't get touch events , its children will get them so intercept them.
if (mIsDisable) {
return false;
}
return super.dispatchTouchEvent(ev);
}
}
Then all you have to do is change the value of that flag. See if it works.
In my case, I just needed to handle the touch event in View A, which was overlaping View B and then send the event to View B. Both views were child of the same RelativeLayout, but there was no parent-child relation between views A and B. This worked for me:
viewA.setOnTouchListener( new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
// do my stuff here
viewB.dispatchTouchEvent( event );
}
}
In this case I have a recyclerview under a scrollview. The top of scrollview is in vertical scroll, and the recyclerview is in horizontal scroll. The scrollview have top padding, making the recyclerview is visible through the transparency in the scrollview padding. I have to make it this way because when the scrollview is scrolled the recyclerview will scroll vertically to like parallax effect (this effect is in another code). This code below is working for my case, might help
scrollView.setOnTouchListener(new View.OnTouchListener() {
float mDownX,mDownY;
boolean mIsSwiping,isDown;
#Override
public boolean onTouch(View v, MotionEvent event) {
if(mIsSwiping){
recyclerView.dispatchTouchEvent(event);
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mIsSwiping = false;
isDown = true;
mDownX = event.getX();
mDownY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
if(isDown){
float deltaX = Math.abs(event.getX() - mDownX);
float deltaY = Math.abs(event.getY() - mDownY);
mDownX = event.getX();
mDownY = event.getY();
if(deltaX!=deltaY){
isDown = false;
if(deltaX>deltaY){
mIsSwiping = true;
}
}
}
}
return mIsSwiping;
}
});
This is the layout
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/gray0"
android:clipToPadding="false"
android:clipChildren="false"
android:paddingBottom="70dp">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="320dp"
android:orientation="horizontal"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"/>
<ScrollView
android:id="#+id/scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="301.75dp"
android:paddingBottom="23.5dp"
android:clipChildren="false"
android:clipToPadding="false">
.
.
.