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();
129 this.listener = listener;
132 public int getPointerWidth()
134 return cluster.getWidth() > 0
136 : getResources().getDimensionPixelSize(R.dimen.tp_cluster_size);
139 public int getPointerHeight()
141 return cluster.getHeight() > 0
142 ? cluster.getHeight()
143 : getResources().getDimensionPixelSize(R.dimen.tp_cluster_size);
146 public float[] getPointerPosition()
148 return new float[] { cluster.getX(), cluster.getY() };
152 private int[] hotspot()
154 return new int[] { (int)cluster.getX(), (int)cluster.getY() };
157 private void sendLeft(
boolean down)
160 if (listener !=
null)
161 listener.onTouchPointerLeftClick(h[0], h[1], down);
164 private void sendMove()
167 if (listener !=
null)
168 listener.onTouchPointerMove(h[0], h[1]);
171 private void setClusterTranslation(
float tx,
float ty)
173 float maxX = getWidth() - cluster.getWidth();
174 float maxY = getHeight() - cluster.getHeight();
179 if (maxX > 0 && tx > maxX)
181 if (maxY > 0 && ty > maxY)
183 cluster.setTranslationX(tx);
184 cluster.setTranslationY(ty);
187 @Override
protected void onLayout(
boolean changed,
int l,
int t,
int r,
int b)
189 super.onLayout(changed, l, t, r, b);
190 if (!placed && getWidth() > 0 && cluster.getWidth() > 0)
193 setClusterTranslation((getWidth() - cluster.getWidth()) / 2.0f,
194 (getHeight() - cluster.getHeight()) / 2.0f);
198 setClusterTranslation(cluster.getTranslationX(), cluster.getTranslationY());
202 private boolean onPuckTouch(MotionEvent e)
204 switch (e.getActionMasked())
206 case MotionEvent.ACTION_DOWN:
207 downRawX = e.getRawX();
208 downRawY = e.getRawY();
209 startTransX = cluster.getTranslationX();
210 startTransY = cluster.getTranslationY();
212 holdDragging =
false;
213 uiHandler.postDelayed(longPress, LONG_PRESS_MS);
215 case MotionEvent.ACTION_MOVE:
217 float dx = e.getRawX() - downRawX;
218 float dy = e.getRawY() - downRawY;
219 if (!dragging && Math.hypot(dx, dy) > touchSlop)
223 uiHandler.removeCallbacks(longPress);
225 if (dragging || holdDragging)
227 setClusterTranslation(startTransX + dx, startTransY + dy);
232 case MotionEvent.ACTION_UP:
233 uiHandler.removeCallbacks(longPress);
237 holdDragging =
false;
245 if (listener !=
null)
246 listener.onTouchPointerMoveEnd();
248 case MotionEvent.ACTION_CANCEL:
249 uiHandler.removeCallbacks(longPress);
253 holdDragging =
false;
255 if (listener !=
null)
256 listener.onTouchPointerMoveEnd();
262 private boolean onScrollTouch(MotionEvent e)
264 switch (e.getActionMasked())
266 case MotionEvent.ACTION_DOWN:
267 scrollLastRawY = e.getRawY();
268 scrollButton.setActivated(
true);
269 scrollButton.bringToFront();
272 case MotionEvent.ACTION_MOVE:
274 float dy = e.getRawY() - scrollLastRawY;
275 if (dy > SCROLL_DELTA)
277 if (listener !=
null)
278 listener.onTouchPointerScroll(
true);
279 scrollLastRawY = e.getRawY();
281 else if (dy < -SCROLL_DELTA)
283 if (listener !=
null)
284 listener.onTouchPointerScroll(
false);
285 scrollLastRawY = e.getRawY();
289 case MotionEvent.ACTION_UP:
290 case MotionEvent.ACTION_CANCEL:
291 scrollButton.setActivated(
false);
299 private void morphScroll(
boolean expand)
301 if (scrollBaseHeight == 0)
302 scrollBaseHeight = scrollButton.getHeight();
303 float target = expand ? getResources().getDimensionPixelSize(R.dimen.tp_cluster_size)
305 if (pillAnimator !=
null)
306 pillAnimator.cancel();
307 pillAnimator = ValueAnimator.ofFloat(scrollButton.getHeight(), target);
308 pillAnimator.setDuration(140);
309 pillAnimator.addUpdateListener(a -> {
310 float h = (float)a.getAnimatedValue();
311 ViewGroup.LayoutParams lp = scrollButton.getLayoutParams();
312 lp.height = Math.round(h);
313 scrollButton.setLayoutParams(lp);
314 scrollButton.setTranslationY(-(h - scrollBaseHeight) / 2.0f);
316 pillAnimator.start();
320 public void setRemoteCursor(
int[] pixels,
int width,
int height,
int hotX,
int hotY)
322 ViewGroup.LayoutParams lp = cursor.getLayoutParams();
323 if (pixels ==
null || width <= 0 || height <= 0)
325 cursor.setImageResource(R.drawable.ic_cursor);
326 ImageViewCompat.setImageTintList(cursor, ColorStateList.valueOf(cursorTint));
327 int s = getResources().getDimensionPixelSize(R.dimen.tp_cursor_size);
330 cursor.setLayoutParams(lp);
331 cursor.setTranslationX(0);
332 cursor.setTranslationY(0);
335 Bitmap bmp = Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);
336 float scale = 40 * density / height;
341 ImageViewCompat.setImageTintList(cursor,
null);
343 BitmapDrawable bd =
new BitmapDrawable(getResources(), bmp);
344 bd.setFilterBitmap(
false);
345 cursor.setImageDrawable(bd);
346 lp.width = Math.round(width * scale);
347 lp.height = Math.round(height * scale);
348 cursor.setLayoutParams(lp);
350 cursor.setTranslationX(-hotX * scale);
351 cursor.setTranslationY(-hotY * scale);
357 void onTouchPointerClose();
359 void onTouchPointerLeftClick(
int x,
int y,
boolean down);
361 void onTouchPointerRightClick(
int x,
int y,
boolean down);
363 void onTouchPointerMove(
int x,
int y);
365 void onTouchPointerMoveEnd();
367 void onTouchPointerScroll(
boolean down);
369 void onTouchPointerToggleKeyboard();
371 void onTouchPointerResetScrollZoom();