Android - Movable/Draggable Floating Action Button (FAB)
Asked Answered
W

11

34

I am using a FloatingActionButton in my app. Occasionally, it overlaps essential content, so I would like to make it so the user can drag the FAB out of the way.

No drag and drop functionality, per se, is required. It just needs to be movable. The docs do not mention this, but I'm sure I've seen such functionality in other apps.

Can you anyone advise / provide a code snippet on how to do it (preferably in XML).

Weeny answered 22/9, 2017 at 18:10 Comment(0)
W
77

Based on this answer for another SO question this is the code I have created. It seems to work nicely (with working click functionality) and isn't dependent on the FAB's parent layout or positioning...

package com.example;

import android.content.Context;
import android.support.design.widget.FloatingActionButton;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

public class MovableFloatingActionButton extends FloatingActionButton implements View.OnTouchListener {

    private final static float CLICK_DRAG_TOLERANCE = 10; // Often, there will be a slight, unintentional, drag when the user taps the FAB, so we need to account for this.

    private float downRawX, downRawY;
    private float dX, dY;

    public MovableFloatingActionButton(Context context) {
        super(context);
        init();
    }

    public MovableFloatingActionButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MovableFloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setOnTouchListener(this);
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent){

        ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)view.getLayoutParams();

        int action = motionEvent.getAction();
        if (action == MotionEvent.ACTION_DOWN) {

            downRawX = motionEvent.getRawX();
            downRawY = motionEvent.getRawY();
            dX = view.getX() - downRawX;
            dY = view.getY() - downRawY;

            return true; // Consumed

        }
        else if (action == MotionEvent.ACTION_MOVE) {

            int viewWidth = view.getWidth();
            int viewHeight = view.getHeight();

            View viewParent = (View)view.getParent();
            int parentWidth = viewParent.getWidth();
            int parentHeight = viewParent.getHeight();

            float newX = motionEvent.getRawX() + dX;
            newX = Math.max(layoutParams.leftMargin, newX); // Don't allow the FAB past the left hand side of the parent
            newX = Math.min(parentWidth - viewWidth - layoutParams.rightMargin, newX); // Don't allow the FAB past the right hand side of the parent

            float newY = motionEvent.getRawY() + dY;
            newY = Math.max(layoutParams.topMargin, newY); // Don't allow the FAB past the top of the parent
            newY = Math.min(parentHeight - viewHeight - layoutParams.bottomMargin, newY); // Don't allow the FAB past the bottom of the parent

            view.animate()
                    .x(newX)
                    .y(newY)
                    .setDuration(0)
                    .start();

            return true; // Consumed

        }
        else if (action == MotionEvent.ACTION_UP) {

            float upRawX = motionEvent.getRawX();
            float upRawY = motionEvent.getRawY();

            float upDX = upRawX - downRawX;
            float upDY = upRawY - downRawY;

            if (Math.abs(upDX) < CLICK_DRAG_TOLERANCE && Math.abs(upDY) < CLICK_DRAG_TOLERANCE) { // A click
                return performClick();
            }
            else { // A drag
                return true; // Consumed
            }

        }
        else {
            return super.onTouchEvent(motionEvent);
        }

    }

}

And here is the XML...

    <com.example.MovableFloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@drawable/ic_navigate_next_white_24dp"/>

Basically, you just need to replace android.support.design.widget.FloatingActionButton with com.example.MovableFloatingActionButton in your XML.

Weeny answered 22/9, 2017 at 22:15 Comment(12)
Did anything else add in activity, with above nothing happen to fab while trying to drag across the screenAnticipation
Are you using MovableFloatingActionButton in a CoordinatorLayout?Weeny
yes, its just move around the size of fab, not to entire pageAnticipation
Can you post your problem and code in a new question, then post a link to it here.Weeny
While I try this, I only got a movable fab inside the red box space. red box is imaginary i.sstatic.net/a5o9j.pngAnticipation
Hmmm, are you sure you have put your MovableFloatingActionButton in the correct container? From your screenshot, it looks like it may be in the same container as the blue button. If not, can you post your screenshot and layout XML in a new question (linking it to this question) and I'll take a proper look.Weeny
Also, does the green circle still get clipped if you replace MovableFloatingActionButton with a standard FloatingActionButton?Weeny
This code is really helped for me. I need the movement more smoothly, how can I do that pls help.Daughterly
@AfinasEM Maybe you are getting too many callbacks to the onTouch() method when dragging the FAB. If so, I'd suggest modifying your code so that the view.animate()... line is only called when the FAB has been moved by more than a specific amount since view.animate()... was last called. Alternatively, you may want to only call view.animate()... if a specific amount of time has elapsed since view.animate()... was last called. If you do it based on time, you'd probably want to use System.nanoTime(). (You could just use instance variables to save and check the previous values.)Weeny
this is not working for me. Where's the missing code where the code above is working?Patsy
this is working perfectly for me.. thank you for saving the dayInflight
It's work for movable on screen area, but block touch screen background (on FAB area movable). is it possible to make FAB movable and keep the background view area touchable?Spermatophore
J
6

Try this:

public class MainActivity extends AppCompatActivity implements View.OnTouchListener {
  float dX;
  float dY;
  int lastAction;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final View dragView = findViewById(R.id.draggable_view);
    dragView.setOnTouchListener(this);
  }

  @Override
  public boolean onTouch(View view, MotionEvent event) {
    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:
        if (lastAction == MotionEvent.ACTION_DOWN)
          Toast.makeText(DraggableView.this, "Clicked!", Toast.LENGTH_SHORT).show();
        break;

      default:
        return false;
    }
    return true;
  }
}

And the XML:

<ImageButton
        android:id="@+id/draggable_view"
        android:background="@mipmap/ic_launcher"
        android:layout_gravity="bottom|right"
        android:layout_marginBottom="20dp"
        android:layout_marginEnd="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

You can make any View Draggable and Clickable.

Jasik answered 10/1, 2019 at 8:14 Comment(0)
P
5

Based on @ban-geoengineering answer I updated as perform ripple effect and left and right gravity like faceebook chat bubble. I created custom click listener cuz if consume touch event inside this code block, ripple effect doesnt work clearly.

    <com.sample.DraggableFloatingActionButton
    android:id="@+id/connect_to_support_fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:layout_marginLeft="@dimen/spacing_10pt"
    android:layout_marginRight="@dimen/spacing_10pt"
    android:layout_marginBottom="@dimen/spacing_16pt"
    android:clickable="true"
    android:focusable="true"
    app:backgroundTint="@color/colorGreen"
    app:fabSize="normal"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:rippleColor="@color/colorWhite"
    app:srcCompat="@drawable/ic_live_support"
    app:tint="@color/colorWhite" />
package com.sample;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.OvershootInterpolator;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

public class DraggableFloatingActionButton extends FloatingActionButton implements View.OnTouchListener {
    CustomClickListener customClickListener;

    private final static float CLICK_DRAG_TOLERANCE = 10; // Often, there will be a slight, unintentional, drag when the user taps the FAB, so we need to account for this.

    private float downRawX, downRawY;
    private float dX, dY;

    int viewWidth;
    int viewHeight;

    int parentWidth;
    int parentHeight;

    float newX;
    float newY;

    public DraggableFloatingActionButton(Context context) {
        super(context);
        init();
    }

    public DraggableFloatingActionButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public DraggableFloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setOnTouchListener(this);
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {

        ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();

        int action = motionEvent.getAction();
        if (action == MotionEvent.ACTION_DOWN) {

            downRawX = motionEvent.getRawX();
            downRawY = motionEvent.getRawY();
            dX = view.getX() - downRawX;
            dY = view.getY() - downRawY;

            return false; // not Consumed for ripple effect

        } else if (action == MotionEvent.ACTION_MOVE) {

            viewWidth = view.getWidth();
            viewHeight = view.getHeight();

            View viewParent = (View) view.getParent();
            parentWidth = viewParent.getWidth();
            parentHeight = viewParent.getHeight();

            newX = motionEvent.getRawX() + dX;
            newX = Math.max(layoutParams.leftMargin, newX); // Don't allow the FAB past the left hand side of the parent
            newX = Math.min(parentWidth - viewWidth - layoutParams.rightMargin, newX); // Don't allow the FAB past the right hand side of the parent

            newY = motionEvent.getRawY() + dY;
            newY = Math.max(layoutParams.topMargin, newY); // Don't allow the FAB past the top of the parent
            newY = Math.min(parentHeight - viewHeight - layoutParams.bottomMargin, newY); // Don't allow the FAB past the bottom of the parent

            view.animate()
                    .x(newX)
                    .y(newY)
                    .setDuration(0)
                    .start();

            return true; // Consumed

        } else if (action == MotionEvent.ACTION_UP) {

            float upRawX = motionEvent.getRawX();
            float upRawY = motionEvent.getRawY();

            float upDX = upRawX - downRawX;
            float upDY = upRawY - downRawY;

            if (newX > ((parentWidth - viewWidth - layoutParams.rightMargin) / 2)) {
                newX = parentWidth - viewWidth - layoutParams.rightMargin;
            } else {
                newX = layoutParams.leftMargin;
            }

            view.animate()
                    .x(newX)
                    .y(newY)
                    .setInterpolator(new OvershootInterpolator())
                    .setDuration(300)
                    .start();

            if (Math.abs(upDX) < CLICK_DRAG_TOLERANCE && Math.abs(upDY) < CLICK_DRAG_TOLERANCE) { // A click
                if (customClickListener != null) {
                    customClickListener.onClick(view);
                }
                return false;// not Consumed for ripple effect
            } else { // A drag
                return false; // not Consumed for ripple effect
            }

        } else {
            return super.onTouchEvent(motionEvent);
        }

    }

    public void setCustomClickListener(CustomClickListener customClickListener) {
        this.customClickListener = customClickListener;
    }

    public interface CustomClickListener {
        void onClick(View view);
    }

}
Phelgen answered 30/5, 2019 at 11:20 Comment(3)
i tried its work. how to move a layout that contain fab icon.Davies
Working Solution! @Weeny Solution also worked but Fab not automatically Dragged out left/Right place.Halifax
can you make it the area movable (transparant background) to touchable? the area moveable prevent receive touch to other appSpermatophore
A
4

All proposed answers used OnTouch listener, which is not recommended by recent Android API because of the Accessibility implementations. Note also that startDrag() method is obsolete. Developers shoud use startDragAndDrop() instead. My implementation uses OnDragListener() as follows:

  1. Define two global float variables dX and dY;
  2. Put the below snippet inside onCreatView() method, where root is root view, taken by the inflater (or any other view, which can receive Drop events);

    final FloatingActionButton fab = root.findViewById(R.id.my_fab);
    
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            // Do whatever this button will do on click event
        }
    });
    
    root.setOnDragListener(new View.OnDragListener() {
        @Override
        public boolean onDrag(View v, DragEvent event) {
            switch (event.getAction()) {
                case DragEvent.ACTION_DRAG_LOCATION:
                    dX = event.getX();
                    dY = event.getY();
                    break;
                case DragEvent.ACTION_DRAG_ENDED:
                    fab.setX(dX-fab.getWidth()/2);
                    fab.setY(dY-fab.getHeight()/2);
                    break;
            }
            return true;
        }
    });
    
    
    fab.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            View.DragShadowBuilder myShadow = new View.DragShadowBuilder(fab);
            v.startDragAndDrop(null, myShadow, null, View.DRAG_FLAG_GLOBAL);
            return true;
        }
    });
    
Amyamyas answered 16/12, 2019 at 11:45 Comment(1)
Great solution. Worked instantly, without debugging. The only thing I had to change is, instead of putting it in OnCreateView, I had to put it in OnCreate, in my case.Bootee
L
0

you can try like below by just impletementing onTouch on any View,

xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:id="@+id/rootlayout"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</FrameLayout>

java

public class dragativity extends AppCompatActivity implements View.OnTouchListener{

    FloatingActionButton fab;

    FrameLayout rootlayout;

     int _xDelta;
     int _yDelta;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.drag);

        rootlayout = (FrameLayout) findViewById(R.id.rootlayout);

        fab = (FloatingActionButton) findViewById(R.id.fab);

        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(150, 150);
        fab.setLayoutParams(layoutParams);
        fab.setOnTouchListener(dragativity.this);
    }

    public boolean onTouch(View view, MotionEvent event) {
        final int X = (int) event.getRawX();
        final int Y = (int) event.getRawY();
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                FrameLayout.LayoutParams lParams = (FrameLayout.LayoutParams) view.getLayoutParams();
                _xDelta = X - lParams.leftMargin;
                _yDelta = Y - lParams.topMargin;
                break;
            case MotionEvent.ACTION_UP:
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                break;
            case MotionEvent.ACTION_POINTER_UP:
                break;
            case MotionEvent.ACTION_MOVE:
                FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) view
                        .getLayoutParams();
                layoutParams.leftMargin = X - _xDelta;
                layoutParams.topMargin = Y - _yDelta;
                layoutParams.rightMargin = -250;
                layoutParams.bottomMargin = -250;
                view.setLayoutParams(layoutParams);
                break;
        }
        rootlayout.invalidate();
        return true;
    }


}
Laina answered 22/9, 2017 at 19:7 Comment(3)
Thanks Omar. I've been trying this approach, but had no luck with it. I've got some code working, though, so will post it in a sec. Thanks anyway.Weeny
While I try this, I only got a movable fab inside the red box space. red box is imaginary i.sstatic.net/a5o9j.pngAnticipation
@SubinBabu I've just noticed you've posted that same comment beneath two different answers. Can you confirm which answer(s) it applies to. If it applies to both, then please see the comment I just left on my answer, above. Also, can you make sure you are not using Omar's Java code with my solution as maybe that could break things. Cheers.Weeny
R
0

actually you can just use android.support.design.widget.CoordinatorLayout instead of Relative layout or any other layout and this will work(moving FAB)

Rebane answered 11/4, 2019 at 5:18 Comment(1)
Hiya Rohit, Did you intend to add that as a comment to an existing answer rather than as a new answer? (In this case, the former is probably more appropriate.)Weeny
P
0

This is the listener that worked for me, with a tolerance of 70.

private class FloatingOnTouchListener implements View.OnTouchListener {
        private float x;
        private float y;
        private float nowX;
        private float nowY;
        private float downX;
        private float downY;
        private final int tolerance = 70;

        @Override
        public boolean onTouch(View view, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                x = (int) event.getRawX();
                y = (int) event.getRawY();
                downX = x;
                downY = y;
            } else
            if (event.getAction() == MotionEvent.ACTION_MOVE) {
                nowX = event.getRawX();
                nowY = event.getRawY();
                float movedX = nowX - x;
                float movedY = nowY - y;
                x = nowX;
                y = nowY;
                iconViewLayoutParams.x = iconViewLayoutParams.x + (int) movedX;
                iconViewLayoutParams.y = iconViewLayoutParams.y + (int) movedY;
                windowManager.updateViewLayout(view, iconViewLayoutParams);
            } else
            if (event.getAction() == MotionEvent.ACTION_UP) {
                float dx = Math.abs(nowX - downX);
                float dy = Math.abs(nowY - downY);
                if (dx < tolerance && dy < tolerance) {
                    Log.d(TAG, "clicou");
                    Log.d(TAG, "dx " + dx);
                    Log.d(TAG, "dy " + dy);
                    windowManager.removeViewImmediate(iconView);
                    windowManager.addView(displayView, layoutParams);
                } else {
                    Log.d(TAG, "dx " + dx);
                    Log.d(TAG, "dy " + dy);
                    return true;
                }
            }
            return true;
        }
    }
Payton answered 20/3, 2020 at 17:34 Comment(0)
T
0

Floating button from function in android

private static float dX,dY;
    private static float downRawX, downRawY;
    private final static float CLICK_DRAG_TOLERANCE = 10;

 public static void setfab(Activity activity){
            FloatingActionButton fab = new FlyyFloatingButton(activity);
            LinearLayout layout = new LinearLayout(activity);
            LinearLayout.LayoutParams parm = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            fab.setImageResource(R.drawable.ic_rewards);
            fab.setBackgroundTintList(ColorStateList.valueOf(Color.BLUE));
            fab.setFocusable(true);
            fab.setSize(FloatingActionButton.SIZE_AUTO);
            layout.addView(fab);
            View viewParent = (View) fab.getParent();
            activity.addContentView(layout,parm);
            fab.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View view, MotionEvent motionEvent){
                    int action = motionEvent.getAction();
                    if (action == MotionEvent.ACTION_DOWN) {
                        downRawX = motionEvent.getRawX();
                        downRawY = motionEvent.getRawY();
                        dX = view.getX() - downRawX;
                        dY = view.getY() - downRawY;
                        return true; // Consumed
                    }
                    else if (action == MotionEvent.ACTION_MOVE) {
                        int viewWidth = view.getWidth();
                        int viewHeight = view.getHeight();
    
                        View viewParent = (View)view.getParent();
                        int parentWidth = viewParent.getWidth();
                        int parentHeight = viewParent.getHeight();
    
                        float newX = motionEvent.getRawX() + dX;
                        newX = Math.max(0, newX); // Don't allow the FAB past the left hand side of the parent
                        newX = Math.min(parentWidth - viewWidth, newX); // Don't allow the FAB past the right hand side of the parent
    
                        float newY = motionEvent.getRawY() + dY;
                        newY = Math.max(0, newY); // Don't allow the FAB past the top of the parent
                        newY = Math.min(parentHeight - viewHeight, newY); // Don't allow the FAB past the bottom of the parent
    
                        view.animate()
                                .x(newX)
                                .y(newY)
                                .setDuration(0)
                                .start();
    
                        return true; // Consumed
    
                    }
                    else if (action == MotionEvent.ACTION_UP) {
    
                        float upRawX = motionEvent.getRawX();
                        float upRawY = motionEvent.getRawY();
    
                        float upDX = upRawX - downRawX;
                        float upDY = upRawY - downRawY;
    
                        if (Math.abs(upDX) < CLICK_DRAG_TOLERANCE && Math.abs(upDY) < CLICK_DRAG_TOLERANCE) { // A click
                            return view.performClick();
                        }
                        else { // A drag
                            return true; // Consumed
                        }
    
                    }
                    else {
                        return view.onTouchEvent(motionEvent);
                    }
    
                }
            });
            fab.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(context, "Hello Floating Action Button", Toast.LENGTH_SHORT).show();
                }
            });
        }
Tarkany answered 2/1, 2023 at 11:37 Comment(0)
V
0

Floating Action button Movable everywhere in java code

flBtnCallify.setOnTouchListener(new View.OnTouchListener() {
        float dX;
        float dY;
        float startX;
        float startY;
        int lastAction;
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                    dX = v.getX() - event.getRawX();
                    dY = v.getY() - event.getRawY();
                    startX = event.getRawX();
                    startY = event.getRawY();
                    lastAction = MotionEvent.ACTION_DOWN;
                    break;
                case MotionEvent.ACTION_MOVE:
                    v.setY(event.getRawY() + dY);
                    v.setX(event.getRawX() + dX);
                    lastAction = MotionEvent.ACTION_MOVE;
                    break;
                case MotionEvent.ACTION_UP:
                    if (Math.abs(startX - event.getRawX()) < 10 && Math.abs(startY - event.getRawY()) < 10){
                        Toast.makeText(v.getContext(), "Clicked!", Toast.LENGTH_SHORT).show();

                    }
                    break;
                default:
                    return false;
            }
            return false;
        }
    });
Vietcong answered 3/5, 2023 at 6:46 Comment(0)
B
-1

You can try this code XML

 <com.google.android.material.floatingactionbutton.FloatingActionButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginEnd="8dp"
    android:layout_marginRight="8dp"
    android:id="@+id/dashboardShowActionsFab"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent" />

JAVA

fab.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {


            ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();

            int action = motionEvent.getAction();
            if (action == MotionEvent.ACTION_DOWN) {

                downRawX = motionEvent.getRawX();
                downRawY = motionEvent.getRawY();
                dX = view.getX() - downRawX;
                dY = view.getY() - downRawY;

                return true; // Consumed

            } else if (action == MotionEvent.ACTION_MOVE) {

                int viewWidth = view.getWidth();
                int viewHeight = view.getHeight();

                View viewParent = (View) view.getParent();
                int parentWidth = viewParent.getWidth();
                int parentHeight = viewParent.getHeight();

                float newX = motionEvent.getRawX() + dX;
                newX = Math.max(layoutParams.leftMargin, newX); // Don't allow the FAB past the left hand side of the parent
                newX = Math.min(parentWidth - viewWidth - layoutParams.rightMargin, newX); // Don't allow the FAB past the right hand side of the parent

                float newY = motionEvent.getRawY() + dY;
                newY = Math.max(layoutParams.topMargin, newY); // Don't allow the FAB past the top of the parent
                newY = Math.min(parentHeight - viewHeight - layoutParams.bottomMargin, newY); // Don't allow the FAB past the bottom of the parent

                view.animate()
                        .x(newX)
                        .y(newY)
                        .setDuration(0)
                        .start();

                return true; // Consumed

            } else if (action == MotionEvent.ACTION_UP) {

                float upRawX = motionEvent.getRawX();
                float upRawY = motionEvent.getRawY();

                float upDX = upRawX - downRawX;
                float upDY = upRawY - downRawY;

                if (Math.abs(upDX) < CLICK_DRAG_TOLERANCE && Math.abs(upDY) < CLICK_DRAG_TOLERANCE) { // A click
                    // return performClick();
                    Toast.makeText(MainActivity.this, "clicked", Toast.LENGTH_SHORT).show();

                } else { // A drag
                    return true; // Consumed
                }

            } else {
                //return super.onTouchEvent(motionEvent);
            }

            return true;
        }
Bordeaux answered 9/1, 2020 at 9:9 Comment(1)
the fab will move with in the screenBordeaux
M
-1

Here is a slightly updated version. It handles the ripple effect correctly, at least it did the trick for me.

public MovableFloatingActionButton(Context context) {
    super(context);
    init();
}

public MovableFloatingActionButton(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

public MovableFloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

private void init() {
    setOnTouchListener(this);
}

@Override
public boolean onTouch(View view, MotionEvent motionEvent){
    ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)view.getLayoutParams();

    switch (motionEvent.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
            downRawX = motionEvent.getRawX();
            downRawY = motionEvent.getRawY();
            dX = view.getX() - downRawX;
            dY = view.getY() - downRawY;
            return super.onTouchEvent(motionEvent);

        case MotionEvent.ACTION_MOVE:
            int viewWidth = view.getWidth();
            int viewHeight = view.getHeight();

            View viewParent = (View)view.getParent();
            int parentWidth = viewParent.getWidth();
            int parentHeight = viewParent.getHeight();

            float newX = motionEvent.getRawX() + dX;
            newX = Math.max(layoutParams.leftMargin, newX);
            newX = Math.min(parentWidth - viewWidth - layoutParams.rightMargin, newX);

            float newY = motionEvent.getRawY() + dY;
            newY = Math.max(layoutParams.topMargin, newY);
            newY = Math.min(parentHeight - viewHeight - layoutParams.bottomMargin, newY);

            view.animate().x(newX).y(newY).setDuration(0).start();
            setPressed(false);
            return true;

        case MotionEvent.ACTION_UP:
            final float upRawX = motionEvent.getRawX();
            final float upRawY = motionEvent.getRawY();

            final float upDX = upRawX - downRawX;
            final float upDY = upRawY - downRawY;

            final boolean isDrag = Math.abs(upDX) >= CLICK_DRAG_TOLERANCE || Math.abs(upDY) >= CLICK_DRAG_TOLERANCE;
            return isDrag || performClick();

        default:
            return super.onTouchEvent(motionEvent);

    }
}
Mindymine answered 1/4, 2020 at 14:7 Comment(13)
Hiya Samuel, Can you add an explanation to highlight the changes you made in order to handle the ripple effect?Weeny
Hey, I actually only do return super.onTouchEvent(motionEvent) for MotionEvent.ACTION_DOWN (to initiate ripple effect), and call setPressed(false) on MotionEvent.ACTION_MOVE to unpress the button when moving it. I also think that my version is a bit more readable as I use a switch, however this is quite subjective. Have fun programming !Mindymine
I also wonder if you had the same problem with ripple effect? Maybe it depends on Android version?Mindymine
I added your two changes to my code, but the MFAB would no longer move. Also, I tested the original com.google.android.material.floatingactionbutton.FloatingActionButton and it doesn't appear to have any ripple effect either.Weeny
I forgot to mention that I use android:background="?android:attr/selectableItemBackground" in my xml.Mindymine
When I add that to my MFAB in xml, I get this error: AAPT: error: 'android:attr/selectableItemBackground' is incompatible with attribute background (attr) reference|color.Weeny
? character went missing in my post... try again with ?android:attr/selectableItemBackgroundMindymine
Thanks. It compiles now, but still no ripple effect - either with FAB or MFAB.Weeny
I honestly don't know why it does not work for you. Try setting ripple color in xml manually. I use this code and I guarantee it is working...Mindymine
Also, my minSdkVersion is 23, maybe it can explain why you can't make it workMindymine
Yes, maybe. Mine is 15. To be honest, I don't think the ripple effect for a FAB is such a big deal as I presume you don't really get to see it anyway?!Weeny
It behaves like a button so you get to see the ripple effect. Have you tried with minSdkVersion 23 ?Mindymine
No, I'm not that bothered, really. My point was that you won't really see the ripple effect as your thumb will obscure most/all the FAB so, in my opinion, it's not really worth the faff.Weeny

© 2022 - 2024 — McMap. All rights reserved.