lecho.lib.hellocharts.gesture.ChartTouchHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hellocharts-library Show documentation
Show all versions of hellocharts-library Show documentation
Charting library for Android compatible with API 8+(Android 2.2).
The newest version!
package lecho.lib.hellocharts.gesture;
import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ViewParent;
import lecho.lib.hellocharts.computator.ChartComputator;
import lecho.lib.hellocharts.gesture.ChartScroller.ScrollResult;
import lecho.lib.hellocharts.model.SelectedValue;
import lecho.lib.hellocharts.renderer.ChartRenderer;
import lecho.lib.hellocharts.view.Chart;
/**
* Default touch handler for most charts. Handles value touch, scroll, fling and zoom.
*/
public class ChartTouchHandler {
protected GestureDetector gestureDetector;
protected ScaleGestureDetector scaleGestureDetector;
protected ChartScroller chartScroller;
protected ChartZoomer chartZoomer;
protected Chart chart;
protected ChartComputator computator;
protected ChartRenderer renderer;
protected boolean isZoomEnabled = true;
protected boolean isScrollEnabled = true;
protected boolean isValueTouchEnabled = true;
protected boolean isValueSelectionEnabled = false;
/**
* Used only for selection mode to avoid calling listener multiple times for the same selection. Small thing but it
* is more intuitive this way.
*/
protected SelectedValue selectionModeOldValue = new SelectedValue();
protected SelectedValue selectedValue = new SelectedValue();
protected SelectedValue oldSelectedValue = new SelectedValue();
/**
* ViewParent to disallow touch events interception if chart is within scroll container.
*/
protected ViewParent viewParent;
/**
* Type of scroll of container, horizontal or vertical.
*/
protected ContainerScrollType containerScrollType;
public ChartTouchHandler(Context context, Chart chart) {
this.chart = chart;
this.computator = chart.getChartComputator();
this.renderer = chart.getChartRenderer();
gestureDetector = new GestureDetector(context, new ChartGestureListener());
scaleGestureDetector = new ScaleGestureDetector(context, new ChartScaleGestureListener());
chartScroller = new ChartScroller(context);
chartZoomer = new ChartZoomer(context, ZoomType.HORIZONTAL_AND_VERTICAL);
}
public void resetTouchHandler() {
this.computator = chart.getChartComputator();
this.renderer = chart.getChartRenderer();
}
/**
* Computes scroll and zoom using {@link ChartScroller} and {@link ChartZoomer}. This method returns true if
* scroll/zoom was computed and chart needs to be invalidated.
*/
public boolean computeScroll() {
boolean needInvalidate = false;
if (isScrollEnabled && chartScroller.computeScrollOffset(computator)) {
needInvalidate = true;
}
if (isZoomEnabled && chartZoomer.computeZoom(computator)) {
needInvalidate = true;
}
return needInvalidate;
}
/**
* Handle chart touch event(gestures, clicks). Return true if gesture was handled and chart needs to be
* invalidated.
*/
public boolean handleTouchEvent(MotionEvent event) {
boolean needInvalidate = false;
// TODO: detectors always return true, use class member needInvalidate instead local variable as workaround.
// This flag should be computed inside gesture listeners methods to avoid invalidation.
needInvalidate = gestureDetector.onTouchEvent(event);
needInvalidate = scaleGestureDetector.onTouchEvent(event) || needInvalidate;
if (isZoomEnabled && scaleGestureDetector.isInProgress()) {
// Special case: if view is inside scroll container and user is scaling disable touch interception by
// parent.
disallowParentInterceptTouchEvent();
}
if (isValueTouchEnabled) {
needInvalidate = computeTouch(event) || needInvalidate;
}
return needInvalidate;
}
/**
* Handle chart touch event(gestures, clicks). Return true if gesture was handled and chart needs to be
* invalidated.
* If viewParent and containerScrollType are not null chart can be scrolled and scaled within horizontal or
* vertical
* scroll container like ViewPager.
*/
public boolean handleTouchEvent(MotionEvent event, ViewParent viewParent,
ContainerScrollType containerScrollType) {
this.viewParent = viewParent;
this.containerScrollType = containerScrollType;
return handleTouchEvent(event);
}
/**
* Disallow parent view from intercepting touch events. Use it for chart that is within some scroll container i.e.
* ViewPager.
*/
private void disallowParentInterceptTouchEvent() {
if (null != viewParent) {
viewParent.requestDisallowInterceptTouchEvent(true);
}
}
/**
* Allow parent view to intercept touch events if chart cannot be scroll horizontally or vertically according to
* the
* current value of {@link #containerScrollType}.
*/
private void allowParentInterceptTouchEvent(ScrollResult scrollResult) {
if (null != viewParent) {
if (ContainerScrollType.HORIZONTAL == containerScrollType && !scrollResult.canScrollX
&& !scaleGestureDetector.isInProgress()) {
viewParent.requestDisallowInterceptTouchEvent(false);
} else if (ContainerScrollType.VERTICAL == containerScrollType && !scrollResult.canScrollY
&& !scaleGestureDetector.isInProgress()) {
viewParent.requestDisallowInterceptTouchEvent(false);
}
}
}
private boolean computeTouch(MotionEvent event) {
boolean needInvalidate = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
boolean wasTouched = renderer.isTouched();
boolean isTouched = checkTouch(event.getX(), event.getY());
if (wasTouched != isTouched) {
needInvalidate = true;
if (isValueSelectionEnabled) {
selectionModeOldValue.clear();
if (wasTouched && !renderer.isTouched()) {
chart.callTouchListener();
}
}
}
break;
case MotionEvent.ACTION_UP:
if (renderer.isTouched()) {
if (checkTouch(event.getX(), event.getY())) {
if (isValueSelectionEnabled) {
// For selection mode call listener only if selected value changed,
// that means that should be
// first(selection) click on given value.
if (!selectionModeOldValue.equals(selectedValue)) {
selectionModeOldValue.set(selectedValue);
chart.callTouchListener();
}
} else {
chart.callTouchListener();
renderer.clearTouch();
}
} else {
renderer.clearTouch();
}
needInvalidate = true;
}
break;
case MotionEvent.ACTION_MOVE:
// If value was touched and now touch point is outside of value area - clear touch and invalidate, user
// probably moved finger away from given chart value.
if (renderer.isTouched()) {
if (!checkTouch(event.getX(), event.getY())) {
renderer.clearTouch();
needInvalidate = true;
}
}
break;
case MotionEvent.ACTION_CANCEL:
if (renderer.isTouched()) {
renderer.clearTouch();
needInvalidate = true;
}
break;
}
return needInvalidate;
}
private boolean checkTouch(float touchX, float touchY) {
oldSelectedValue.set(selectedValue);
selectedValue.clear();
if (renderer.checkTouch(touchX, touchY)) {
selectedValue.set(renderer.getSelectedValue());
}
// Check if selection is still on the same value, if not return false.
if (oldSelectedValue.isSet() && selectedValue.isSet() && !oldSelectedValue.equals(selectedValue)) {
return false;
} else {
return renderer.isTouched();
}
}
public boolean isZoomEnabled() {
return isZoomEnabled;
}
public void setZoomEnabled(boolean isZoomEnabled) {
this.isZoomEnabled = isZoomEnabled;
}
public boolean isScrollEnabled() {
return isScrollEnabled;
}
public void setScrollEnabled(boolean isScrollEnabled) {
this.isScrollEnabled = isScrollEnabled;
}
public ZoomType getZoomType() {
return chartZoomer.getZoomType();
}
public void setZoomType(ZoomType zoomType) {
chartZoomer.setZoomType(zoomType);
}
public boolean isValueTouchEnabled() {
return isValueTouchEnabled;
}
public void setValueTouchEnabled(boolean isValueTouchEnabled) {
this.isValueTouchEnabled = isValueTouchEnabled;
}
public boolean isValueSelectionEnabled() {
return isValueSelectionEnabled;
}
public void setValueSelectionEnabled(boolean isValueSelectionEnabled) {
this.isValueSelectionEnabled = isValueSelectionEnabled;
}
protected class ChartScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
if (isZoomEnabled) {
float scale = 2.0f - detector.getScaleFactor();
if (Float.isInfinite(scale)) {
scale = 1;
}
return chartZoomer.scale(computator, detector.getFocusX(), detector.getFocusY(), scale);
}
return false;
}
}
protected class ChartGestureListener extends GestureDetector.SimpleOnGestureListener {
protected ScrollResult scrollResult = new ScrollResult();
@Override
public boolean onDown(MotionEvent e) {
if (isScrollEnabled) {
disallowParentInterceptTouchEvent();
return chartScroller.startScroll(computator);
}
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
if (isZoomEnabled) {
return chartZoomer.startZoom(e, computator);
}
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (isScrollEnabled) {
boolean canScroll = chartScroller
.scroll(computator, distanceX, distanceY, scrollResult);
allowParentInterceptTouchEvent(scrollResult);
return canScroll;
}
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (isScrollEnabled) {
return chartScroller.fling((int) -velocityX, (int) -velocityY, computator);
}
return false;
}
}
}