Skip to content
This repository was archived by the owner on Jul 22, 2024. It is now read-only.

Commit 237441f

Browse files
keianhzoMortimerGoro
authored andcommitted
Fixes #2524 Honeycomb button clipping (#2601)
* Honeycomb button clipping * Forward event if not inside
1 parent 91e3eaa commit 237441f

File tree

5 files changed

+424
-27
lines changed

5 files changed

+424
-27
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package org.mozilla.vrbrowser.ui.views;
2+
3+
import android.graphics.Path;
4+
import android.graphics.RectF;
5+
import android.graphics.Region;
6+
import android.view.MotionEvent;
7+
import android.view.View;
8+
import android.view.View.OnClickListener;
9+
10+
import androidx.annotation.DrawableRes;
11+
import androidx.annotation.NonNull;
12+
13+
import org.mozilla.vrbrowser.utils.ViewUtils;
14+
15+
import java.util.Deque;
16+
17+
public class ClippedEventDelegate implements View.OnHoverListener, View.OnTouchListener {
18+
19+
private View mView;
20+
private Region mRegion;
21+
private boolean mHovered;
22+
private boolean mTouched;
23+
private OnClickListener mClickListener;
24+
25+
public ClippedEventDelegate(@DrawableRes int res, @NonNull View view) {
26+
mView = view;
27+
mHovered = false;
28+
mTouched = false;
29+
30+
view.getViewTreeObserver().addOnGlobalLayoutListener(
31+
() -> {
32+
Path path = createPathFromResource(res);
33+
RectF bounds = new RectF();
34+
path.computeBounds(bounds, true);
35+
36+
bounds = new RectF();
37+
path.computeBounds(bounds, true);
38+
mRegion = new Region();
39+
mRegion.setPath(path, new Region((int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom));
40+
});
41+
}
42+
43+
public void setOnClickListener(OnClickListener listener) {
44+
mClickListener = listener;
45+
}
46+
47+
private Path createPathFromResource(@DrawableRes int res) {
48+
VectorShape shape = new VectorShape(mView.getContext(), res);
49+
shape.onResize(mView.getWidth(), mView.getHeight());
50+
Deque<VectorShape.Layer> layers = shape.getLayers();
51+
VectorShape.Layer layer = layers.getFirst();
52+
53+
// TODO Handle state changes and update the Region based on the new current state shape
54+
55+
return layer.transformedPath;
56+
}
57+
58+
@Override
59+
public boolean onHover(View v, MotionEvent event) {
60+
if(!mRegion.contains((int)event.getX(),(int) event.getY())) {
61+
if (mHovered) {
62+
mHovered = false;
63+
event.setAction(MotionEvent.ACTION_HOVER_EXIT);
64+
return v.onHoverEvent(event);
65+
}
66+
67+
return true;
68+
69+
} else {
70+
if (!mHovered) {
71+
mHovered = true;
72+
event.setAction(MotionEvent.ACTION_HOVER_ENTER);
73+
}
74+
75+
return v.onHoverEvent(event);
76+
}
77+
}
78+
79+
@Override
80+
public boolean onTouch(View v, MotionEvent event) {
81+
v.getParent().requestDisallowInterceptTouchEvent(true);
82+
83+
if(!mRegion.contains((int)event.getX(),(int) event.getY())) {
84+
switch (event.getAction()) {
85+
case MotionEvent.ACTION_UP:
86+
if (mTouched) {
87+
v.requestFocus();
88+
v.requestFocusFromTouch();
89+
if (mClickListener != null) {
90+
mClickListener.onClick(v);
91+
}
92+
}
93+
v.setPressed(false);
94+
mTouched = false;
95+
}
96+
97+
return true;
98+
99+
} else {
100+
switch (event.getAction()) {
101+
case MotionEvent.ACTION_DOWN:
102+
v.setPressed(true);
103+
mTouched = true;
104+
return true;
105+
106+
case MotionEvent.ACTION_UP:
107+
if (mTouched && ViewUtils.isInsideView(v, (int)event.getRawX(), (int)event.getRawY())) {
108+
v.requestFocus();
109+
v.requestFocusFromTouch();
110+
if (mClickListener != null) {
111+
mClickListener.onClick(v);
112+
}
113+
}
114+
v.setPressed(false);
115+
mTouched = false;
116+
return true;
117+
118+
case MotionEvent.ACTION_MOVE:
119+
return true;
120+
121+
case MotionEvent.ACTION_CANCEL:
122+
v.setPressed(false);
123+
mTouched = false;
124+
return true;
125+
}
126+
127+
return false;
128+
}
129+
}
130+
131+
}

app/src/common/shared/org/mozilla/vrbrowser/ui/views/HoneycombButton.java

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import org.mozilla.vrbrowser.R;
2020
import org.mozilla.vrbrowser.utils.DeviceType;
2121
import org.mozilla.vrbrowser.utils.SystemUtils;
22-
import org.mozilla.vrbrowser.utils.ViewUtils;
2322

2423
public class HoneycombButton extends LinearLayout {
2524

@@ -33,6 +32,7 @@ public class HoneycombButton extends LinearLayout {
3332
private String mSecondaryButtonText;
3433
private Drawable mButtonIcon;
3534
private boolean mButtonIconHover;
35+
private VectorClippedEventDelegate mEventDelegate;
3636

3737
public HoneycombButton(Context context, @Nullable AttributeSet attrs) {
3838
this(context, attrs, R.style.honeycombButtonTheme);
@@ -109,41 +109,53 @@ private void initialize(Context aContext) {
109109
mSecondaryText.setClickable(false);
110110
}
111111

112-
setOnHoverListener((view, motionEvent) -> false);
112+
mEventDelegate = new VectorClippedEventDelegate(R.drawable.settings_honeycomb_background, this);
113+
setOnHoverListener(mEventDelegate);
114+
setOnTouchListener(mEventDelegate);
113115
}
114116

115-
116117
@Override
117118
public void setOnClickListener(@Nullable OnClickListener aListener) {
118-
ViewUtils.setStickyClickListener(this, aListener);
119+
mEventDelegate.setOnClickListener(aListener);
119120
}
120121

121122
@Override
122-
public void setOnHoverListener(final OnHoverListener l) {
123-
super.setOnHoverListener((view, motionEvent) -> {
124-
switch (motionEvent.getAction()) {
125-
case MotionEvent.ACTION_HOVER_ENTER:
126-
if (mIcon != null && mText != null) {
127-
if (mButtonIconHover) {
128-
mIcon.setColorFilter(new PorterDuffColorFilter(getResources().getColor(R.color.asphalt, getContext().getTheme()), PorterDuff.Mode.MULTIPLY));
129-
}
130-
mText.setTextColor(getContext().getColor(R.color.asphalt));
131-
mSecondaryText.setTextColor(getContext().getColor(R.color.asphalt));
123+
public boolean onHoverEvent(MotionEvent event) {
124+
switch (event.getAction()) {
125+
case MotionEvent.ACTION_HOVER_ENTER:
126+
if (mIcon != null && mText != null) {
127+
if (mButtonIconHover) {
128+
mIcon.setColorFilter(new PorterDuffColorFilter(getResources().getColor(R.color.asphalt, getContext().getTheme()), PorterDuff.Mode.MULTIPLY));
132129
}
133-
break;
134-
case MotionEvent.ACTION_HOVER_EXIT:
135-
if (mIcon != null && mText != null) {
136-
if (mButtonIconHover) {
137-
mIcon.setColorFilter(new PorterDuffColorFilter(getResources().getColor(R.color.fog, getContext().getTheme()), PorterDuff.Mode.MULTIPLY));
138-
}
139-
mText.setTextColor(getContext().getColor(R.color.fog));
140-
mSecondaryText.setTextColor(getContext().getColor(R.color.fog));
130+
mText.setTextColor(getContext().getColor(R.color.asphalt));
131+
mSecondaryText.setTextColor(getContext().getColor(R.color.asphalt));
132+
}
133+
break;
134+
case MotionEvent.ACTION_HOVER_EXIT:
135+
if (mIcon != null && mText != null) {
136+
if (mButtonIconHover) {
137+
mIcon.setColorFilter(new PorterDuffColorFilter(getResources().getColor(R.color.fog, getContext().getTheme()), PorterDuff.Mode.MULTIPLY));
141138
}
142-
break;
143-
}
139+
mText.setTextColor(getContext().getColor(R.color.fog));
140+
mSecondaryText.setTextColor(getContext().getColor(R.color.fog));
141+
}
142+
break;
143+
}
144144

145-
return l.onHover(view, motionEvent);
146-
});
145+
if (mEventDelegate.isInside(event)) {
146+
return super.onHoverEvent(event);
147+
148+
} else {
149+
setHovered(false);
150+
if (mIcon != null && mText != null) {
151+
if (mButtonIconHover) {
152+
mIcon.setColorFilter(new PorterDuffColorFilter(getResources().getColor(R.color.fog, getContext().getTheme()), PorterDuff.Mode.MULTIPLY));
153+
}
154+
mText.setTextColor(getContext().getColor(R.color.fog));
155+
mSecondaryText.setTextColor(getContext().getColor(R.color.fog));
156+
}
157+
return false;
158+
}
147159
}
148160

149161
@Override
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package org.mozilla.vrbrowser.ui.views;
2+
3+
import android.graphics.Path;
4+
import android.graphics.RectF;
5+
import android.graphics.Region;
6+
import android.view.MotionEvent;
7+
import android.view.View;
8+
import android.view.View.OnClickListener;
9+
import android.view.ViewTreeObserver;
10+
11+
import androidx.annotation.DrawableRes;
12+
import androidx.annotation.NonNull;
13+
14+
import org.mozilla.vrbrowser.utils.ViewUtils;
15+
16+
import java.util.Deque;
17+
18+
public class VectorClippedEventDelegate implements View.OnHoverListener, View.OnTouchListener {
19+
20+
private View mView;
21+
private @DrawableRes int mRes;
22+
private Region mRegion;
23+
private boolean mHovered;
24+
private boolean mTouched;
25+
private OnClickListener mClickListener;
26+
27+
VectorClippedEventDelegate(@DrawableRes int res, @NonNull View view) {
28+
mView = view;
29+
mRes = res;
30+
mHovered = false;
31+
mTouched = false;
32+
33+
view.getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener);
34+
}
35+
36+
private ViewTreeObserver.OnGlobalLayoutListener mLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
37+
@Override
38+
public void onGlobalLayout() {
39+
Path path = createPathFromResource(mRes);
40+
RectF bounds = new RectF();
41+
path.computeBounds(bounds, true);
42+
43+
bounds = new RectF();
44+
path.computeBounds(bounds, true);
45+
mRegion = new Region();
46+
mRegion.setPath(path, new Region((int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom));
47+
48+
mView.getViewTreeObserver().removeOnGlobalLayoutListener(mLayoutListener);
49+
}
50+
};
51+
52+
public void setOnClickListener(OnClickListener listener) {
53+
mClickListener = listener;
54+
}
55+
56+
private Path createPathFromResource(@DrawableRes int res) {
57+
VectorShape shape = new VectorShape(mView.getContext(), res);
58+
shape.onResize(mView.getWidth(), mView.getHeight());
59+
Deque<VectorShape.Layer> layers = shape.getLayers();
60+
VectorShape.Layer layer = layers.getFirst();
61+
62+
// TODO Handle state changes and update the Region based on the new current state shape
63+
64+
return layer.transformedPath;
65+
}
66+
67+
@Override
68+
public boolean onHover(View v, MotionEvent event) {
69+
if(!isInside(event)) {
70+
if (mHovered) {
71+
mHovered = false;
72+
event.setAction(MotionEvent.ACTION_HOVER_EXIT);
73+
return v.onHoverEvent(event);
74+
}
75+
76+
return false;
77+
78+
} else {
79+
if (!mHovered) {
80+
mHovered = true;
81+
event.setAction(MotionEvent.ACTION_HOVER_ENTER);
82+
}
83+
84+
return v.onHoverEvent(event);
85+
}
86+
}
87+
88+
@Override
89+
public boolean onTouch(View v, MotionEvent event) {
90+
v.getParent().requestDisallowInterceptTouchEvent(true);
91+
92+
if(!isInside(event)) {
93+
switch (event.getAction()) {
94+
case MotionEvent.ACTION_UP:
95+
if (mTouched) {
96+
v.requestFocus();
97+
v.requestFocusFromTouch();
98+
if (mClickListener != null) {
99+
mClickListener.onClick(v);
100+
}
101+
}
102+
v.setPressed(false);
103+
mTouched = false;
104+
}
105+
106+
return true;
107+
108+
} else {
109+
switch (event.getAction()) {
110+
case MotionEvent.ACTION_DOWN:
111+
v.setPressed(true);
112+
mTouched = true;
113+
return true;
114+
115+
case MotionEvent.ACTION_UP:
116+
if (mTouched && ViewUtils.isInsideView(v, (int)event.getRawX(), (int)event.getRawY())) {
117+
v.requestFocus();
118+
v.requestFocusFromTouch();
119+
if (mClickListener != null) {
120+
mClickListener.onClick(v);
121+
}
122+
}
123+
v.setPressed(false);
124+
mTouched = false;
125+
return true;
126+
127+
case MotionEvent.ACTION_MOVE:
128+
return true;
129+
130+
case MotionEvent.ACTION_CANCEL:
131+
v.setPressed(false);
132+
mTouched = false;
133+
return true;
134+
}
135+
136+
return false;
137+
}
138+
}
139+
140+
public boolean isInside(MotionEvent event) {
141+
return mRegion.contains((int)event.getX(),(int) event.getY());
142+
}
143+
144+
}

0 commit comments

Comments
 (0)