26 void onToggleTouchPointer();
27 void onToggleKeyboard();
38 private final LinearLayout container;
39 private final LinearLayout buttons;
40 private boolean expanded =
false;
41 private int insetLeft = 0, insetTop = 0, insetRight = 0, insetBottom = 0;
42 private Edge snappedEdge = Edge.LEFT;
43 private float snappedFraction = 0.4f;
45 public void setInsets(
int left,
int top,
int right,
int bottom)
53 public FloatingToolbar(Activity activity, Listener listener)
55 container = activity.findViewById(R.id.floating_toolbar_container);
56 buttons = activity.findViewById(R.id.floating_toolbar_buttons);
57 View handle = activity.findViewById(R.id.floating_toolbar_handle);
60 GestureDetector gestureDetector =
61 new GestureDetector(activity,
new GestureDetector.SimpleOnGestureListener() {
62 @Override public boolean onSingleTapConfirmed(MotionEvent e)
69 View.OnTouchListener dragListener = buildDragListener(activity, gestureDetector, handle);
70 container.setOnTouchListener(dragListener);
71 handle.setOnTouchListener(dragListener);
72 for (
int i = 0; i < buttons.getChildCount(); i++)
73 buttons.getChildAt(i).setOnTouchListener(dragListener);
75 bindButton(activity, R.id.floating_toolbar_touch_pointer, listener::onToggleTouchPointer);
76 bindButton(activity, R.id.floating_toolbar_sys_keyboard, listener::onToggleKeyboard);
78 ViewTreeObserver vto = container.getViewTreeObserver();
79 vto.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
80 @Override public void onGlobalLayout()
82 container.getViewTreeObserver().removeOnGlobalLayoutListener(this);
83 View parent = (View)container.getParent();
86 positionInitial(parent);
87 parent.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop,
88 oldRight, oldBottom) -> {
89 if (right - left != oldRight - oldLeft || bottom - top != oldBottom - oldTop)
96 private void positionInitial(View parent)
98 container.setOrientation(LinearLayout.VERTICAL);
99 buttons.setOrientation(LinearLayout.VERTICAL);
100 snappedEdge = Edge.LEFT;
101 snappedFraction = 0.4f;
102 float ph = parent.getHeight();
103 float ch = container.getHeight();
104 container.setX(insetLeft);
106 Math.max(insetTop, insetTop + snappedFraction * (ph - insetTop - insetBottom - ch)));
109 private void resnapToSameEdge()
111 View parent = (View)container.getParent();
115 int orientation = (snappedEdge == Edge.LEFT || snappedEdge == Edge.RIGHT)
116 ? LinearLayout.VERTICAL
117 : LinearLayout.HORIZONTAL;
118 container.setOrientation(orientation);
119 buttons.setOrientation(orientation);
121 container.post(() -> {
122 float pw = parent.getWidth(), ph = parent.getHeight();
123 float w = container.getWidth(), h = container.getHeight();
129 ty = insetTop + snappedFraction * (ph - insetTop - insetBottom - h);
132 tx = pw - w - insetRight;
133 ty = insetTop + snappedFraction * (ph - insetTop - insetBottom - h);
136 tx = insetLeft + snappedFraction * (pw - insetLeft - insetRight - w);
140 tx = insetLeft + snappedFraction * (pw - insetLeft - insetRight - w);
141 ty = ph - h - insetBottom;
144 tx = Math.max(insetLeft, Math.min(tx, pw - w - insetRight));
145 ty = Math.max(insetTop, Math.min(ty, ph - h - insetBottom));
146 container.animate().x(tx).y(ty).setDuration(250).start();
150 private void toggle()
152 expanded = !expanded;
153 buttons.setVisibility(expanded ? View.VISIBLE : View.GONE);
157 private void bindButton(Activity activity,
int id, Runnable action)
159 View v = activity.findViewById(
id);
163 v.setOnClickListener(ignored -> action.run());
167 private static void setTooltip(View v)
169 v.setTooltipText(v.getContentDescription());
172 private View.OnTouchListener buildDragListener(Activity activity,
173 GestureDetector gestureDetector, View handle)
175 int touchSlop = android.view.ViewConfiguration.get(activity).getScaledTouchSlop();
176 return new View.OnTouchListener() {
177 private float startX, startY, offsetX, offsetY;
178 private boolean dragging;
180 @Override
public boolean onTouch(View v, MotionEvent e)
182 if (v == handle || v == container)
183 gestureDetector.onTouchEvent(e);
185 switch (e.getActionMasked())
187 case MotionEvent.ACTION_DOWN:
188 startX = e.getRawX();
189 startY = e.getRawY();
190 offsetX = container.getX() - startX;
191 offsetY = container.getY() - startY;
195 case MotionEvent.ACTION_MOVE:
196 if (!dragging && (Math.abs(e.getRawX() - startX) > touchSlop ||
197 Math.abs(e.getRawY() - startY) > touchSlop))
200 MotionEvent cancel = MotionEvent.obtain(e);
201 cancel.setAction(MotionEvent.ACTION_CANCEL);
202 v.onTouchEvent(cancel);
207 .x(e.getRawX() + offsetX)
208 .y(e.getRawY() + offsetY)
214 case MotionEvent.ACTION_UP:
215 case MotionEvent.ACTION_CANCEL:
228 View parent = (View)container.getParent();
232 float px = container.getX(), py = container.getY();
233 float pw = parent.getWidth(), ph = parent.getHeight();
234 float cw = container.getWidth(), ch = container.getHeight();
236 float dLeft = px - insetLeft;
237 float dRight = Math.max(0, pw - px - cw - insetRight);
238 float dTop = py - insetTop;
239 float dBottom = Math.max(0, ph - py - ch - insetBottom);
240 float min = Math.min(Math.min(dLeft, dRight), Math.min(dTop, dBottom));
243 snappedEdge = Edge.LEFT;
244 else if (min == dRight)
245 snappedEdge = Edge.RIGHT;
246 else if (min == dTop)
247 snappedEdge = Edge.TOP;
249 snappedEdge = Edge.BOTTOM;
251 int orientation = (snappedEdge == Edge.LEFT || snappedEdge == Edge.RIGHT)
252 ? LinearLayout.VERTICAL
253 : LinearLayout.HORIZONTAL;
254 container.setOrientation(orientation);
255 buttons.setOrientation(orientation);
257 container.post(() -> {
258 float w = container.getWidth(), h = container.getHeight();
259 float tx = container.getX(), ty = container.getY();
266 tx = pw - w - insetRight;
272 ty = ph - h - insetBottom;
275 tx = Math.max(insetLeft, Math.min(tx, pw - w - insetRight));
276 ty = Math.max(insetTop, Math.min(ty, ph - h - insetBottom));
277 if (snappedEdge == Edge.LEFT || snappedEdge == Edge.RIGHT)
279 float available = ph - insetTop - insetBottom - h;
281 available > 0 ? Math.max(0f, Math.min(1f, (ty - insetTop) / available)) : 0.5f;
285 float available = pw - insetLeft - insetRight - w;
287 available > 0 ? Math.max(0f, Math.min(1f, (tx - insetLeft) / available)) : 0.5f;
289 container.animate().x(tx).y(ty).setDuration(250).start();