blob: 8bc65635401201658f8fad251818a561e90774ce [file] [log] [blame]
Jason Monk9262c942017-07-28 14:35:13 -04001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android.systemui.statusbar.phone;
16
17import android.content.Context;
18import android.graphics.Rect;
19import android.util.AttributeSet;
20import android.util.Log;
21import android.util.Pair;
22import android.view.MotionEvent;
23import android.view.View;
24import android.view.ViewGroup;
25import android.widget.FrameLayout;
26
27import com.android.systemui.R;
28
29import java.util.ArrayList;
30import java.util.Comparator;
31
32/**
33 * Redirects touches that aren't handled by any child view to the nearest
34 * clickable child. Only takes effect on <sw600dp.
35 */
36public class NearestTouchFrame extends FrameLayout {
37
38 private final ArrayList<View> mClickableChildren = new ArrayList<>();
39 private final boolean mIsActive;
40 private final int[] mTmpInt = new int[2];
41 private final int[] mOffset = new int[2];
42 private View mTouchingChild;
43
44 public NearestTouchFrame(Context context, AttributeSet attrs) {
45 super(context, attrs);
46 mIsActive = context.getResources().getConfiguration().smallestScreenWidthDp < 600;
47 }
48
49 @Override
50 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
51 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
52 mClickableChildren.clear();
53 addClickableChildren(this);
54 }
55
56 @Override
57 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
58 super.onLayout(changed, left, top, right, bottom);
59 getLocationInWindow(mOffset);
60 }
61
62 private void addClickableChildren(ViewGroup group) {
63 final int N = group.getChildCount();
64 for (int i = 0; i < N; i++) {
65 View child = group.getChildAt(i);
66 if (child.isClickable()) {
67 mClickableChildren.add(child);
68 } else if (child instanceof ViewGroup) {
69 addClickableChildren((ViewGroup) child);
70 }
71 }
72 }
73
74 @Override
75 public boolean onTouchEvent(MotionEvent event) {
76 if (mIsActive) {
77 if (event.getAction() == MotionEvent.ACTION_DOWN) {
78 mTouchingChild = findNearestChild(event);
79 }
80 if (mTouchingChild != null) {
81 event.offsetLocation(mTouchingChild.getWidth() / 2 - event.getX(),
82 mTouchingChild.getHeight() / 2 - event.getY());
83 return mTouchingChild.dispatchTouchEvent(event);
84 }
85 }
86 return super.onTouchEvent(event);
87 }
88
89 private View findNearestChild(MotionEvent event) {
90 return mClickableChildren.stream().map(v -> new Pair<>(distance(v, event), v))
91 .min(Comparator.comparingInt(f -> f.first)).get().second;
92 }
93
94 private int distance(View v, MotionEvent event) {
95 v.getLocationInWindow(mTmpInt);
96 int left = mTmpInt[0] - mOffset[0];
97 int top = mTmpInt[1] - mOffset[1];
98 int right = left + v.getWidth();
99 int bottom = top + v.getHeight();
100
101 int x = Math.min(Math.abs(left - (int) event.getX()),
102 Math.abs((int) event.getX() - right));
103 int y = Math.min(Math.abs(top - (int) event.getY()),
104 Math.abs((int) event.getY() - bottom));
105
106 return Math.max(x, y);
107 }
108}