/**
* Wire
* Copyright (C) 2016 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* This part of the Wire software uses source code from the Google I/O Android App.
* (https://github.com/google/iosched)
*
* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.waz.zclient.views;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import android.view.MotionEvent;
import android.view.TouchDelegate;
import android.view.View;
/**
* {@link TouchDelegate} that gates {@link MotionEvent} instances by comparing
* then against fractional dimensions of the source view.
* <p/>
* This is particularly useful when you want to define a rectangle in terms of
* the source dimensions, but when those dimensions might change due to pending
* or future layout passes.
* <p/>
* One example is catching touches that occur in the top-right quadrant of
* {@code sourceParent}, and relaying them to {@code targetChild}. This could be
* done with: <code>
* FractionalTouchDelegate.setupDelegate(sourceParent, targetChild, new RectF(0.5f, 0f, 1f, 0.5f));
* </code>
*/
public class FractionalTouchDelegate extends TouchDelegate {
private View source;
private View target;
private RectF sourceFraction;
private Rect scrap = new Rect();
/**
* Cached full dimensions of {@link #source}.
*/
private Rect sourceFull = new Rect();
/**
* Cached projection of {@link #sourceFraction} onto {@link #source}.
*/
private Rect sourcePartial = new Rect();
private boolean delegateTargeted;
public FractionalTouchDelegate(View source, View target, RectF sourceFraction) {
super(new Rect(0, 0, 0, 0), target);
this.source = source;
this.target = target;
this.sourceFraction = sourceFraction;
}
/**
* Helper to create and setup a {@link FractionalTouchDelegate} between the
* given {@link View}.
*
* @param source Larger source {@link View}, usually a parent, that will be
* assigned {@link View#setTouchDelegate(TouchDelegate)}.
* @param target Smaller target {@link View} which will receive
* {@link MotionEvent} that land in requested fractional area.
* @param sourceFraction Fractional area projected onto source {@link View}
* which determines when {@link MotionEvent} will be passed to
* target {@link View}.
*/
public static void setupDelegate(View source, View target, RectF sourceFraction) {
source.setTouchDelegate(new FractionalTouchDelegate(source, target, sourceFraction));
}
/**
* Consider updating {@link #sourcePartial} when {@link #source}
* dimensions have changed.
*/
private void updateSourcePartial() {
source.getHitRect(scrap);
if (!scrap.equals(sourceFull)) {
// Copy over and calculate fractional rectangle
sourceFull.set(scrap);
final int width = sourceFull.width();
final int height = sourceFull.height();
sourcePartial.left = (int) (sourceFraction.left * width);
sourcePartial.top = (int) (sourceFraction.top * height);
sourcePartial.right = (int) (sourceFraction.right * width);
sourcePartial.bottom = (int) (sourceFraction.bottom * height);
}
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
updateSourcePartial();
// The logic below is mostly copied from the parent class, since we
// can't update private mBounds variable.
// http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;
// f=core/java/android/view/TouchDelegate.java;hb=eclair#l98
final Rect sourcePartial = this.sourcePartial;
final View target = this.target;
int x = (int) event.getX();
int y = (int) event.getY();
boolean sendToDelegate = false;
boolean hit = true;
boolean handled = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (sourcePartial.contains(x, y)) {
delegateTargeted = true;
sendToDelegate = true;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_MOVE:
sendToDelegate = delegateTargeted;
if (sendToDelegate && !sourcePartial.contains(x, y)) {
hit = false;
}
break;
case MotionEvent.ACTION_CANCEL:
sendToDelegate = delegateTargeted;
delegateTargeted = false;
break;
}
if (sendToDelegate) {
if (hit) {
event.setLocation(target.getWidth() / 2, target.getHeight() / 2);
} else {
event.setLocation(-1, -1);
}
handled = target.dispatchTouchEvent(event);
}
return handled;
}
}