26 void onToggleTouchPointer();
27 void onToggleSysKeyboard();
28 void onToggleExtKeyboard();
39 private final LinearLayout container;
40 private final LinearLayout buttons;
41 private boolean expanded =
false;
42 private int insetLeft = 0, insetTop = 0, insetRight = 0, insetBottom = 0;
43 private Edge snappedEdge = Edge.LEFT;
44 private float snappedFraction = 0.4f;
46 public void setInsets(
int left,
int top,
int right,
int bottom)
54 public FloatingToolbar(Activity activity, Listener listener)
56 container = activity.findViewById(R.id.floating_toolbar_container);
57 buttons = activity.findViewById(R.id.floating_toolbar_buttons);
58 View handle = activity.findViewById(R.id.floating_toolbar_handle);
61 GestureDetector gestureDetector =
62 new GestureDetector(activity,
new GestureDetector.SimpleOnGestureListener() {
63 @Override public boolean onSingleTapConfirmed(MotionEvent e)
70 View.OnTouchListener dragListener = buildDragListener(activity, gestureDetector, handle);
71 container.setOnTouchListener(dragListener);
72 handle.setOnTouchListener(dragListener);
73 for (
int i = 0; i < buttons.getChildCount(); i++)
74 buttons.getChildAt(i).setOnTouchListener(dragListener);
76 bindButton(activity, R.id.floating_toolbar_touch_pointer, listener::onToggleTouchPointer);
77 bindButton(activity, R.id.floating_toolbar_sys_keyboard, listener::onToggleSysKeyboard);
78 bindButton(activity, R.id.floating_toolbar_ext_keyboard, listener::onToggleExtKeyboard);
80 ViewTreeObserver vto = container.getViewTreeObserver();
81 vto.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
82 @Override public void onGlobalLayout()
84 container.getViewTreeObserver().removeOnGlobalLayoutListener(this);
85 View parent = (View)container.getParent();
88 positionInitial(parent);
89 parent.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop,
90 oldRight, oldBottom) -> {
91 if (right - left != oldRight - oldLeft || bottom - top != oldBottom - oldTop)
98 private void positionInitial(View parent)
100 container.setOrientation(LinearLayout.VERTICAL);
101 buttons.setOrientation(LinearLayout.VERTICAL);
102 snappedEdge = Edge.LEFT;
103 snappedFraction = 0.4f;
104 float ph = parent.getHeight();
105 float ch = container.getHeight();
106 container.setX(insetLeft);
108 Math.max(insetTop, insetTop + snappedFraction * (ph - insetTop - insetBottom - ch)));
111 private void resnapToSameEdge()
113 View parent = (View)container.getParent();
117 int orientation = (snappedEdge == Edge.LEFT || snappedEdge == Edge.RIGHT)
118 ? LinearLayout.VERTICAL
119 : LinearLayout.HORIZONTAL;
120 container.setOrientation(orientation);
121 buttons.setOrientation(orientation);
123 container.post(() -> {
124 float pw = parent.getWidth(), ph = parent.getHeight();
125 float w = container.getWidth(), h = container.getHeight();
131 ty = insetTop + snappedFraction * (ph - insetTop - insetBottom - h);
134 tx = pw - w - insetRight;
135 ty = insetTop + snappedFraction * (ph - insetTop - insetBottom - h);
138 tx = insetLeft + snappedFraction * (pw - insetLeft - insetRight - w);
142 tx = insetLeft + snappedFraction * (pw - insetLeft - insetRight - w);
143 ty = ph - h - insetBottom;
146 tx = Math.max(insetLeft, Math.min(tx, pw - w - insetRight));
147 ty = Math.max(insetTop, Math.min(ty, ph - h - insetBottom));
148 container.animate().x(tx).y(ty).setDuration(250).start();
152 private void toggle()
154 expanded = !expanded;
155 buttons.setVisibility(expanded ? View.VISIBLE : View.GONE);
159 private void bindButton(Activity activity,
int id, Runnable action)
161 View v = activity.findViewById(
id);
165 v.setOnClickListener(ignored -> action.run());
169 private static void setTooltip(View v)
171 v.setTooltipText(v.getContentDescription());
174 private View.OnTouchListener buildDragListener(Activity activity,
175 GestureDetector gestureDetector, View handle)
177 int touchSlop = android.view.ViewConfiguration.get(activity).getScaledTouchSlop();
178 return new View.OnTouchListener() {
179 private float startX, startY, offsetX, offsetY;
180 private boolean dragging;
182 @Override
public boolean onTouch(View v, MotionEvent e)
184 if (v == handle || v == container)
185 gestureDetector.onTouchEvent(e);
187 switch (e.getActionMasked())
189 case MotionEvent.ACTION_DOWN:
190 startX = e.getRawX();
191 startY = e.getRawY();
192 offsetX = container.getX() - startX;
193 offsetY = container.getY() - startY;
197 case MotionEvent.ACTION_MOVE:
198 if (!dragging && (Math.abs(e.getRawX() - startX) > touchSlop ||
199 Math.abs(e.getRawY() - startY) > touchSlop))
202 MotionEvent cancel = MotionEvent.obtain(e);
203 cancel.setAction(MotionEvent.ACTION_CANCEL);
204 v.onTouchEvent(cancel);
209 .x(e.getRawX() + offsetX)
210 .y(e.getRawY() + offsetY)
216 case MotionEvent.ACTION_UP:
217 case MotionEvent.ACTION_CANCEL:
230 View parent = (View)container.getParent();
234 float px = container.getX(), py = container.getY();
235 float pw = parent.getWidth(), ph = parent.getHeight();
236 float cw = container.getWidth(), ch = container.getHeight();
238 float dLeft = px - insetLeft;
239 float dRight = Math.max(0, pw - px - cw - insetRight);
240 float dTop = py - insetTop;
241 float dBottom = Math.max(0, ph - py - ch - insetBottom);
242 float min = Math.min(Math.min(dLeft, dRight), Math.min(dTop, dBottom));
245 snappedEdge = Edge.LEFT;
246 else if (min == dRight)
247 snappedEdge = Edge.RIGHT;
248 else if (min == dTop)
249 snappedEdge = Edge.TOP;
251 snappedEdge = Edge.BOTTOM;
253 int orientation = (snappedEdge == Edge.LEFT || snappedEdge == Edge.RIGHT)
254 ? LinearLayout.VERTICAL
255 : LinearLayout.HORIZONTAL;
256 container.setOrientation(orientation);
257 buttons.setOrientation(orientation);
259 container.post(() -> {
260 float w = container.getWidth(), h = container.getHeight();
261 float tx = container.getX(), ty = container.getY();
268 tx = pw - w - insetRight;
274 ty = ph - h - insetBottom;
277 tx = Math.max(insetLeft, Math.min(tx, pw - w - insetRight));
278 ty = Math.max(insetTop, Math.min(ty, ph - h - insetBottom));
279 if (snappedEdge == Edge.LEFT || snappedEdge == Edge.RIGHT)
281 float available = ph - insetTop - insetBottom - h;
283 available > 0 ? Math.max(0f, Math.min(1f, (ty - insetTop) / available)) : 0.5f;
287 float available = pw - insetLeft - insetRight - w;
289 available > 0 ? Math.max(0f, Math.min(1f, (tx - insetLeft) / available)) : 0.5f;
291 container.animate().x(tx).y(ty).setDuration(250).start();