I am trying to create the effect of lollipop toolbar icons for pre-lollipop versions.
I wrote this class that extends a LinearLayout and in the dispatchTouchEvent checks to see which child is closer to the touch position and send that event only to that child, so that it ignores the overlapping area and z-index of children:
public class OverlappingLayout extends LinearLayout {
public OverlappingLayout(Context context) {
super(context);
}
public OverlappingLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
int lastAction = 0;
View lastView = null;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(lastView != null){
lastView.dispatchTouchEvent(ev);
if(ev.getAction() == MotionEvent.ACTION_CANCEL || ev.getAction() == MotionEvent.ACTION_UP)
lastView = null;
return true;
}
float min = 99999999f;
int minIndex = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
float centerX = child.getX() + child.getWidth() / 2;
float centerY = child.getY() + child.getHeight() / 2;
float dis = (float) Math.sqrt(
(ev.getX() - centerX) * (ev.getX() - centerX) +
(ev.getY() - centerY) * (ev.getY() - centerY)
);
if(dis < min) {
min = dis;
minIndex = i;
}
}
getChildAt(minIndex).setClickable(true);
getChildAt(minIndex).dispatchTouchEvent(ev);
lastView = getChildAt(minIndex);
lastAction = ev.getAction();
lastView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//never works for the last child, or the right half of the second child
return false;
}
});
return true;
}
}
but the issue is that the code works fine for the first half of the layout and doesnt work for the second half.
the code works in debug mode, and finds the closest child and pass the event to it, but the press state of the drawable of the child doesnt work.
I also changed it to RelativeLayout and still have the same issue.
I figured out what the problem was. when i send the motionEvent object to the children, the x and y of the touch is outside of the child's rect(as seen by child). so it only works for the first half because the first and second child see the touch position inside their rect. so I changed my code to this:
private Rect rect;
View lastView;
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).setClickable(false);
}
if(lastView == null)
lastView = getClosestChildToTouch(event);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
lastView.setPressed(true);
rect = new Rect(lastView.getLeft(), lastView.getTop(), lastView.getRight(), lastView.getBottom());
}
if (event.getAction() == MotionEvent.ACTION_UP) {
if (!rect.contains((int) event.getX(), (int) event.getY())) {
lastView.setPressed(false);
lastView=null;
} else {
lastView.performClick();
lastView.setPressed(false);
lastView=null;
}
}
if(event.getAction() == MotionEvent.ACTION_MOVE){
if(!rect.contains((int) event.getX(),(int) event.getY())){
lastView.setPressed(false);
lastView=null;
}
}
if (event.getAction() == MotionEvent.ACTION_CANCEL){
lastView.setPressed(false);
lastView=null;
}
return true;
}
public View getClosestChildToTouch(MotionEvent ev){
float min = 99999999f;
int minIndex = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
float centerX = child.getX() + child.getWidth() / 2;
float centerY = child.getY() + child.getHeight() / 2;
float dis = (float) Math.sqrt(
(ev.getX() - centerX) * (ev.getX() - centerX) +
(ev.getY() - centerY) * (ev.getY() - centerY)
);
if(dis < min) {
min = dis;
minIndex = i;
}
}
return getChildAt(minIndex);
}
I also could have modified the motionEvent object and add child's left or right position to the x attribute.