39 private static final float SCROLL_DELTA = 10.0f;
40 private static final int LONG_PRESS_MS = 500;
43 private ImageView cursor;
44 private ImageButton scrollButton;
48 private float density;
49 private int touchSlop;
50 private boolean placed =
false;
53 private float downRawX, downRawY, startTransX, startTransY;
54 private boolean dragging =
false;
55 private boolean holdDragging =
false;
58 private float scrollLastRawY;
59 private float scrollBaseHeight;
60 private ValueAnimator pillAnimator;
62 private int cursorTint;
64 private final Handler uiHandler =
new Handler(Looper.getMainLooper());
65 private final Runnable longPress = () ->
67 if (!dragging && !holdDragging)
81 this(context, attrs, 0);
86 super(context, attrs, defStyle);
87 initTouchPointer(context);
90 private void initTouchPointer(Context context)
92 density = getResources().getDisplayMetrics().density;
93 touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
94 cursorTint = ContextCompat.getColor(context, R.color.tp_icon);
95 setClipChildren(
false);
97 LayoutInflater.from(context).inflate(R.layout.touch_pointer,
this,
true);
98 cluster = findViewById(R.id.tp_cluster);
99 cursor = findViewById(R.id.tp_cursor);
100 scrollButton = findViewById(R.id.tp_scroll);
102 findViewById(R.id.tp_puck).setOnTouchListener((v, e) -> onPuckTouch(e));
103 scrollButton.setOnTouchListener((v, e) -> onScrollTouch(e));
105 findViewById(R.id.tp_close).setOnClickListener(v -> {
106 if (listener !=
null)
107 listener.onTouchPointerClose();
109 findViewById(R.id.tp_rclick).setOnClickListener(v -> {
111 if (listener !=
null)
113 listener.onTouchPointerRightClick(h[0], h[1],
true);
114 listener.onTouchPointerRightClick(h[0], h[1],
false);
117 findViewById(R.id.tp_reset).setOnClickListener(v -> {
118 if (listener !=
null)
119 listener.onTouchPointerResetScrollZoom();
121 findViewById(R.id.tp_keyboard).setOnClickListener(v -> {
122 if (listener !=
null)
123 listener.onTouchPointerToggleKeyboard();
125 findViewById(R.id.tp_ext_keyboard).setOnClickListener(v -> {
126 if (listener !=
null)
127 listener.onTouchPointerToggleExtKeyboard();
133 this.listener = listener;
136 public int getPointerWidth()
138 return cluster.getWidth() > 0
140 : getResources().getDimensionPixelSize(R.dimen.tp_cluster_size);
143 public int getPointerHeight()
145 return cluster.getHeight() > 0
146 ? cluster.getHeight()
147 : getResources().getDimensionPixelSize(R.dimen.tp_cluster_size);
150 public float[] getPointerPosition()
152 return new float[] { cluster.getX(), cluster.getY() };
156 private int[] hotspot()
158 return new int[] { (int)cluster.getX(), (int)cluster.getY() };
161 private void sendLeft(
boolean down)
164 if (listener !=
null)
165 listener.onTouchPointerLeftClick(h[0], h[1], down);
168 private void sendMove()
171 if (listener !=
null)
172 listener.onTouchPointerMove(h[0], h[1]);
175 private void setClusterTranslation(
float tx,
float ty)
177 float maxX = getWidth() - cluster.getWidth();
178 float maxY = getHeight() - cluster.getHeight();
183 if (maxX > 0 && tx > maxX)
185 if (maxY > 0 && ty > maxY)
187 cluster.setTranslationX(tx);
188 cluster.setTranslationY(ty);
191 @Override
protected void onLayout(
boolean changed,
int l,
int t,
int r,
int b)
193 super.onLayout(changed, l, t, r, b);
194 if (!placed && getWidth() > 0 && cluster.getWidth() > 0)
197 setClusterTranslation((getWidth() - cluster.getWidth()) / 2.0f,
198 (getHeight() - cluster.getHeight()) / 2.0f);
202 setClusterTranslation(cluster.getTranslationX(), cluster.getTranslationY());
206 private boolean onPuckTouch(MotionEvent e)
208 switch (e.getActionMasked())
210 case MotionEvent.ACTION_DOWN:
211 downRawX = e.getRawX();
212 downRawY = e.getRawY();
213 startTransX = cluster.getTranslationX();
214 startTransY = cluster.getTranslationY();
216 holdDragging =
false;
217 uiHandler.postDelayed(longPress, LONG_PRESS_MS);
219 case MotionEvent.ACTION_MOVE:
221 float dx = e.getRawX() - downRawX;
222 float dy = e.getRawY() - downRawY;
223 if (!dragging && Math.hypot(dx, dy) > touchSlop)
227 uiHandler.removeCallbacks(longPress);
229 if (dragging || holdDragging)
231 setClusterTranslation(startTransX + dx, startTransY + dy);
236 case MotionEvent.ACTION_UP:
237 uiHandler.removeCallbacks(longPress);
241 holdDragging =
false;
249 if (listener !=
null)
250 listener.onTouchPointerMoveEnd();
252 case MotionEvent.ACTION_CANCEL:
253 uiHandler.removeCallbacks(longPress);
257 holdDragging =
false;
259 if (listener !=
null)
260 listener.onTouchPointerMoveEnd();
266 private boolean onScrollTouch(MotionEvent e)
268 switch (e.getActionMasked())
270 case MotionEvent.ACTION_DOWN:
271 scrollLastRawY = e.getRawY();
272 scrollButton.setActivated(
true);
273 scrollButton.bringToFront();
276 case MotionEvent.ACTION_MOVE:
278 float dy = e.getRawY() - scrollLastRawY;
279 if (dy > SCROLL_DELTA)
281 if (listener !=
null)
282 listener.onTouchPointerScroll(
true);
283 scrollLastRawY = e.getRawY();
285 else if (dy < -SCROLL_DELTA)
287 if (listener !=
null)
288 listener.onTouchPointerScroll(
false);
289 scrollLastRawY = e.getRawY();
293 case MotionEvent.ACTION_UP:
294 case MotionEvent.ACTION_CANCEL:
295 scrollButton.setActivated(
false);
303 private void morphScroll(
boolean expand)
305 if (scrollBaseHeight == 0)
306 scrollBaseHeight = scrollButton.getHeight();
307 float target = expand ? getResources().getDimensionPixelSize(R.dimen.tp_cluster_size)
309 if (pillAnimator !=
null)
310 pillAnimator.cancel();
311 pillAnimator = ValueAnimator.ofFloat(scrollButton.getHeight(), target);
312 pillAnimator.setDuration(140);
313 pillAnimator.addUpdateListener(a -> {
314 float h = (float)a.getAnimatedValue();
315 ViewGroup.LayoutParams lp = scrollButton.getLayoutParams();
316 lp.height = Math.round(h);
317 scrollButton.setLayoutParams(lp);
318 scrollButton.setTranslationY(-(h - scrollBaseHeight) / 2.0f);
320 pillAnimator.start();
324 public void setRemoteCursor(
int[] pixels,
int width,
int height,
int hotX,
int hotY)
326 ViewGroup.LayoutParams lp = cursor.getLayoutParams();
327 if (pixels ==
null || width <= 0 || height <= 0)
329 cursor.setImageResource(R.drawable.ic_cursor);
330 ImageViewCompat.setImageTintList(cursor, ColorStateList.valueOf(cursorTint));
331 int s = getResources().getDimensionPixelSize(R.dimen.tp_cursor_size);
334 cursor.setLayoutParams(lp);
335 cursor.setTranslationX(0);
336 cursor.setTranslationY(0);
339 Bitmap bmp = Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);
340 float scale = 40 * density / height;
345 ImageViewCompat.setImageTintList(cursor,
null);
347 BitmapDrawable bd =
new BitmapDrawable(getResources(), bmp);
348 bd.setFilterBitmap(
false);
349 cursor.setImageDrawable(bd);
350 lp.width = Math.round(width * scale);
351 lp.height = Math.round(height * scale);
352 cursor.setLayoutParams(lp);
354 cursor.setTranslationX(-hotX * scale);
355 cursor.setTranslationY(-hotY * scale);
361 void onTouchPointerClose();
363 void onTouchPointerLeftClick(
int x,
int y,
boolean down);
365 void onTouchPointerRightClick(
int x,
int y,
boolean down);
367 void onTouchPointerMove(
int x,
int y);
369 void onTouchPointerMoveEnd();
371 void onTouchPointerScroll(
boolean down);
373 void onTouchPointerToggleKeyboard();
375 void onTouchPointerToggleExtKeyboard();
377 void onTouchPointerResetScrollZoom();