[英]Android - Movable/Draggable Floating Action Button (FAB)
I am using a FloatingActionButton in my app.我在我的应用程序中使用FloatingActionButton 。 Occasionally, it overlaps essential content, so I would like to make it so the user can drag the FAB out of the way.
有时,它会与基本内容重叠,所以我想这样做,以便用户可以将 FAB 拖到一边。
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).你能建议/提供有关如何执行此操作的代码片段(最好是 XML 格式)。
Based on this answer for another SO question this is the code I have created.基于this answer for another SO question,这是我创建的代码。 It seems to work nicely (with working click functionality) and isn't dependent on the FAB's parent layout or positioning...
它似乎工作得很好(具有有效的点击功能)并且不依赖于 FAB 的父布局或定位......
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...这是 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.基本上,您只需要将
android.support.design.widget.FloatingActionButton
替换为 XML 中的com.example.MovableFloatingActionButton
。
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:和 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.您可以使任何视图可拖动和可点击。
Based on @ban-geoengineering answer I updated as perform ripple effect and left and right gravity like faceebook chat bubble.基于@ban-geoengineering 的答案,我更新为执行涟漪效应和左右重力,如 Facebook 聊天气泡。 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);
}
}
All proposed answers used OnTouch listener, which is not recommended by recent Android API because of the Accessibility implementations.所有建议的答案都使用 OnTouch 侦听器,由于辅助功能实现,最近的 Android API 不推荐使用 OnTouch 侦听器。 Note also that startDrag() method is obsolete.
另请注意, startDrag() 方法已过时。 Developers shoud use startDragAndDrop() instead.
开发人员应该使用 startDragAndDrop() 代替。 My implementation uses OnDragListener() as follows:
我的实现使用 OnDragListener() 如下:
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);将下面的代码片段放在 onCreatView() 方法中,其中root是根视图,由 inflater(或任何其他可以接收 Drop 事件的视图)获取;
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; } });
you can try like below by just impletementing onTouch
on any View
,您可以通过在任何
View
上实现onTouch
来尝试如下所示,
xml 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;
}
}
实际上,您可以使用 android.support.design.widget.CoordinatorLayout 而不是相对布局或任何其他布局,这将起作用(移动 FAB)
This is the listener that worked for me, with a tolerance of 70.这是对我有用的侦听器,容差为 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;
}
}
Floating button from function in android android中function的悬浮按钮
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();
}
});
}
You can try this code XML你可以试试这个代码 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;
}
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);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.