blob: de1bc0857539c932fd7ef0da7e8e92504471b345 [file] [log] [blame]
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -07001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings.widget;
18
19import android.content.Context;
Jeff Sharkey52c3f442011-06-23 00:39:38 -070020import android.content.res.TypedArray;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070021import android.graphics.Canvas;
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -070022import android.graphics.Color;
23import android.graphics.Paint;
Jeff Sharkey55d18a52011-08-27 17:09:43 -070024import android.graphics.Paint.Style;
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -070025import android.graphics.Point;
Jeff Sharkey52c3f442011-06-23 00:39:38 -070026import android.graphics.Rect;
27import android.graphics.drawable.Drawable;
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -070028import android.text.DynamicLayout;
Jeff Sharkeybdf98e82011-11-10 17:17:24 -080029import android.text.Layout;
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -070030import android.text.Layout.Alignment;
31import android.text.SpannableStringBuilder;
32import android.text.TextPaint;
Jeff Sharkey52c3f442011-06-23 00:39:38 -070033import android.util.AttributeSet;
34import android.util.MathUtils;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070035import android.view.MotionEvent;
36import android.view.View;
37
Jeff Sharkeye6c50032013-03-06 11:46:54 -080038import com.android.internal.util.Preconditions;
Jeff Sharkey52c3f442011-06-23 00:39:38 -070039import com.android.settings.R;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070040
41/**
42 * Sweep across a {@link ChartView} at a specific {@link ChartAxis} value, which
43 * a user can drag.
44 */
Jeff Sharkey54d0af52011-08-11 18:26:57 -070045public class ChartSweepView extends View {
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070046
Jeff Sharkey55d18a52011-08-27 17:09:43 -070047 private static final boolean DRAW_OUTLINE = false;
48
Jeff Sharkey5d706792011-09-08 18:57:17 -070049 // TODO: clean up all the various padding/offset/margins
50
Jeff Sharkey52c3f442011-06-23 00:39:38 -070051 private Drawable mSweep;
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -070052 private Rect mSweepPadding = new Rect();
Jeff Sharkey55d18a52011-08-27 17:09:43 -070053
54 /** Offset of content inside this view. */
Jeff Sharkey5d706792011-09-08 18:57:17 -070055 private Rect mContentOffset = new Rect();
Jeff Sharkey55d18a52011-08-27 17:09:43 -070056 /** Offset of {@link #mSweep} inside this view. */
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -070057 private Point mSweepOffset = new Point();
58
59 private Rect mMargins = new Rect();
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -070060 private float mNeighborMargin;
Jeff Sharkeyb6548462014-07-21 15:38:06 -070061 private int mSafeRegion;
Jeff Sharkeyf54f4352011-06-23 22:15:54 -070062
Jeff Sharkey52c3f442011-06-23 00:39:38 -070063 private int mFollowAxis;
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -070064
Jeff Sharkeybdf98e82011-11-10 17:17:24 -080065 private int mLabelMinSize;
66 private float mLabelSize;
67
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -070068 private int mLabelTemplateRes;
69 private int mLabelColor;
70
71 private SpannableStringBuilder mLabelTemplate;
72 private DynamicLayout mLabelLayout;
Jeff Sharkey52c3f442011-06-23 00:39:38 -070073
74 private ChartAxis mAxis;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070075 private long mValue;
Jeff Sharkey28130d92011-09-02 16:10:24 -070076 private long mLabelValue;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070077
Jeff Sharkeyd360e5e2011-07-26 19:30:26 -070078 private long mValidAfter;
79 private long mValidBefore;
80 private ChartSweepView mValidAfterDynamic;
81 private ChartSweepView mValidBeforeDynamic;
Jeff Sharkey4ec71e72011-07-10 12:23:55 -070082
Jeff Sharkeyb98c55b2011-09-11 17:29:49 -070083 private float mLabelOffset;
84
Jeff Sharkey55d18a52011-08-27 17:09:43 -070085 private Paint mOutlinePaint = new Paint();
86
Jeff Sharkey52c3f442011-06-23 00:39:38 -070087 public static final int HORIZONTAL = 0;
88 public static final int VERTICAL = 1;
89
Jeff Sharkeya53188f2011-09-13 19:56:45 -070090 private int mTouchMode = MODE_NONE;
91
92 private static final int MODE_NONE = 0;
93 private static final int MODE_DRAG = 1;
94 private static final int MODE_LABEL = 2;
95
Jeff Sharkeybdf98e82011-11-10 17:17:24 -080096 private static final int LARGE_WIDTH = 1024;
97
Jeff Sharkeya53188f2011-09-13 19:56:45 -070098 private long mDragInterval = 1;
99
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700100 public interface OnSweepListener {
101 public void onSweep(ChartSweepView sweep, boolean sweepDone);
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700102 public void requestEdit(ChartSweepView sweep);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700103 }
104
105 private OnSweepListener mListener;
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700106
107 private float mTrackingStart;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700108 private MotionEvent mTracking;
109
Jeff Sharkey461842a2011-09-25 18:22:48 -0700110 private ChartSweepView[] mNeighbors = new ChartSweepView[0];
111
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700112 public ChartSweepView(Context context) {
Jeff Sharkey54d0af52011-08-11 18:26:57 -0700113 this(context, null);
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700114 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700115
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700116 public ChartSweepView(Context context, AttributeSet attrs) {
117 this(context, attrs, 0);
118 }
119
120 public ChartSweepView(Context context, AttributeSet attrs, int defStyle) {
121 super(context, attrs, defStyle);
122
123 final TypedArray a = context.obtainStyledAttributes(
124 attrs, R.styleable.ChartSweepView, defStyle, 0);
125
Jason Monk2d499532015-06-03 10:53:03 -0400126 final int color = a.getColor(R.styleable.ChartSweepView_labelColor, Color.BLUE);
127 setSweepDrawable(a.getDrawable(R.styleable.ChartSweepView_sweepDrawable), color);
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700128 setFollowAxis(a.getInt(R.styleable.ChartSweepView_followAxis, -1));
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -0700129 setNeighborMargin(a.getDimensionPixelSize(R.styleable.ChartSweepView_neighborMargin, 0));
Jeff Sharkeyb6548462014-07-21 15:38:06 -0700130 setSafeRegion(a.getDimensionPixelSize(R.styleable.ChartSweepView_safeRegion, 0));
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700131
Jeff Sharkeybdf98e82011-11-10 17:17:24 -0800132 setLabelMinSize(a.getDimensionPixelSize(R.styleable.ChartSweepView_labelSize, 0));
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700133 setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
Jason Monk2d499532015-06-03 10:53:03 -0400134 setLabelColor(color);
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700135
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700136 // TODO: moved focused state directly into assets
137 setBackgroundResource(R.drawable.data_usage_sweep_background);
138
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700139 mOutlinePaint.setColor(Color.RED);
140 mOutlinePaint.setStrokeWidth(1f);
141 mOutlinePaint.setStyle(Style.STROKE);
142
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700143 a.recycle();
144
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700145 setClickable(true);
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700146 setOnClickListener(mClickListener);
147
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700148 setWillNotDraw(false);
149 }
150
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700151 private OnClickListener mClickListener = new OnClickListener() {
152 public void onClick(View v) {
153 dispatchRequestEdit();
154 }
155 };
156
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700157 void init(ChartAxis axis) {
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700158 mAxis = Preconditions.checkNotNull(axis, "missing axis");
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700159 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700160
Jeff Sharkey461842a2011-09-25 18:22:48 -0700161 public void setNeighbors(ChartSweepView... neighbors) {
162 mNeighbors = neighbors;
163 }
164
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700165 public int getFollowAxis() {
166 return mFollowAxis;
167 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700168
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700169 public Rect getMargins() {
170 return mMargins;
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700171 }
172
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700173 public void setDragInterval(long dragInterval) {
174 mDragInterval = dragInterval;
175 }
176
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700177 /**
178 * Return the number of pixels that the "target" area is inset from the
179 * {@link View} edge, along the current {@link #setFollowAxis(int)}.
180 */
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700181 private float getTargetInset() {
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700182 if (mFollowAxis == VERTICAL) {
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700183 final float targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
184 - mSweepPadding.bottom;
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700185 return mSweepPadding.top + (targetHeight / 2) + mSweepOffset.y;
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700186 } else {
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700187 final float targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
188 - mSweepPadding.right;
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700189 return mSweepPadding.left + (targetWidth / 2) + mSweepOffset.x;
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700190 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700191 }
192
193 public void addOnSweepListener(OnSweepListener listener) {
194 mListener = listener;
195 }
196
197 private void dispatchOnSweep(boolean sweepDone) {
198 if (mListener != null) {
199 mListener.onSweep(this, sweepDone);
200 }
201 }
202
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700203 private void dispatchRequestEdit() {
204 if (mListener != null) {
205 mListener.requestEdit(this);
206 }
207 }
208
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700209 @Override
210 public void setEnabled(boolean enabled) {
211 super.setEnabled(enabled);
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700212 setFocusable(enabled);
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700213 requestLayout();
214 }
215
Jason Monk2d499532015-06-03 10:53:03 -0400216 public void setSweepDrawable(Drawable sweep, int color) {
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700217 if (mSweep != null) {
218 mSweep.setCallback(null);
219 unscheduleDrawable(mSweep);
220 }
221
222 if (sweep != null) {
223 sweep.setCallback(this);
224 if (sweep.isStateful()) {
225 sweep.setState(getDrawableState());
226 }
227 sweep.setVisible(getVisibility() == VISIBLE, false);
228 mSweep = sweep;
Jason Monk2d499532015-06-03 10:53:03 -0400229 // Match the text.
230 mSweep.setTint(color);
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700231 sweep.getPadding(mSweepPadding);
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700232 } else {
233 mSweep = null;
234 }
235
236 invalidate();
237 }
238
239 public void setFollowAxis(int followAxis) {
240 mFollowAxis = followAxis;
241 }
242
Jeff Sharkeybdf98e82011-11-10 17:17:24 -0800243 public void setLabelMinSize(int minSize) {
244 mLabelMinSize = minSize;
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700245 invalidateLabelTemplate();
246 }
247
248 public void setLabelTemplate(int resId) {
249 mLabelTemplateRes = resId;
250 invalidateLabelTemplate();
251 }
252
253 public void setLabelColor(int color) {
254 mLabelColor = color;
255 invalidateLabelTemplate();
256 }
257
258 private void invalidateLabelTemplate() {
259 if (mLabelTemplateRes != 0) {
260 final CharSequence template = getResources().getText(mLabelTemplateRes);
261
262 final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
263 paint.density = getResources().getDisplayMetrics().density;
264 paint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
265 paint.setColor(mLabelColor);
266
267 mLabelTemplate = new SpannableStringBuilder(template);
Roozbeh Pournadera7f6bb52017-08-22 16:54:27 -0700268 mLabelLayout = DynamicLayout.Builder.obtain(mLabelTemplate, paint, LARGE_WIDTH)
269 .setAlignment(Alignment.ALIGN_RIGHT)
270 .setIncludePad(false)
271 .setUseLineSpacingFromFallbacks(true)
272 .build();
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700273 invalidateLabel();
274
275 } else {
276 mLabelTemplate = null;
277 mLabelLayout = null;
278 }
279
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700280 invalidate();
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700281 requestLayout();
282 }
283
284 private void invalidateLabel() {
285 if (mLabelTemplate != null && mAxis != null) {
Jeff Sharkey28130d92011-09-02 16:10:24 -0700286 mLabelValue = mAxis.buildLabel(getResources(), mLabelTemplate, mValue);
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700287 setContentDescription(mLabelTemplate);
Jeff Sharkeyb98c55b2011-09-11 17:29:49 -0700288 invalidateLabelOffset();
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700289 invalidate();
Jeff Sharkey5d706792011-09-08 18:57:17 -0700290 } else {
291 mLabelValue = mValue;
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700292 }
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700293 }
294
Jeff Sharkeyb98c55b2011-09-11 17:29:49 -0700295 /**
296 * When overlapping with neighbor, split difference and push label.
297 */
298 public void invalidateLabelOffset() {
299 float margin;
300 float labelOffset = 0;
301 if (mFollowAxis == VERTICAL) {
302 if (mValidAfterDynamic != null) {
Jeff Sharkeybdf98e82011-11-10 17:17:24 -0800303 mLabelSize = Math.max(getLabelWidth(this), getLabelWidth(mValidAfterDynamic));
Jeff Sharkeyb98c55b2011-09-11 17:29:49 -0700304 margin = getLabelTop(mValidAfterDynamic) - getLabelBottom(this);
305 if (margin < 0) {
306 labelOffset = margin / 2;
307 }
308 } else if (mValidBeforeDynamic != null) {
Jeff Sharkeybdf98e82011-11-10 17:17:24 -0800309 mLabelSize = Math.max(getLabelWidth(this), getLabelWidth(mValidBeforeDynamic));
Jeff Sharkeyb98c55b2011-09-11 17:29:49 -0700310 margin = getLabelTop(this) - getLabelBottom(mValidBeforeDynamic);
311 if (margin < 0) {
312 labelOffset = -margin / 2;
313 }
Jeff Sharkeybdf98e82011-11-10 17:17:24 -0800314 } else {
315 mLabelSize = getLabelWidth(this);
Jeff Sharkeyb98c55b2011-09-11 17:29:49 -0700316 }
317 } else {
318 // TODO: implement horizontal labels
319 }
320
Jeff Sharkeybdf98e82011-11-10 17:17:24 -0800321 mLabelSize = Math.max(mLabelSize, mLabelMinSize);
322
Jeff Sharkeyb98c55b2011-09-11 17:29:49 -0700323 // when offsetting label, neighbor probably needs to offset too
324 if (labelOffset != mLabelOffset) {
325 mLabelOffset = labelOffset;
326 invalidate();
327 if (mValidAfterDynamic != null) mValidAfterDynamic.invalidateLabelOffset();
328 if (mValidBeforeDynamic != null) mValidBeforeDynamic.invalidateLabelOffset();
329 }
330 }
331
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700332 @Override
333 public void jumpDrawablesToCurrentState() {
334 super.jumpDrawablesToCurrentState();
335 if (mSweep != null) {
336 mSweep.jumpToCurrentState();
337 }
338 }
339
340 @Override
341 public void setVisibility(int visibility) {
342 super.setVisibility(visibility);
343 if (mSweep != null) {
344 mSweep.setVisible(visibility == VISIBLE, false);
345 }
346 }
347
348 @Override
349 protected boolean verifyDrawable(Drawable who) {
350 return who == mSweep || super.verifyDrawable(who);
351 }
352
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700353 public ChartAxis getAxis() {
354 return mAxis;
355 }
356
Jeff Sharkey8a503642011-06-10 13:31:21 -0700357 public void setValue(long value) {
358 mValue = value;
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700359 invalidateLabel();
Jeff Sharkey8a503642011-06-10 13:31:21 -0700360 }
361
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700362 public long getValue() {
363 return mValue;
364 }
365
Jeff Sharkey28130d92011-09-02 16:10:24 -0700366 public long getLabelValue() {
367 return mLabelValue;
368 }
369
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700370 public float getPoint() {
Jeff Sharkey2af35fb2011-06-24 17:30:27 -0700371 if (isEnabled()) {
372 return mAxis.convertToPoint(mValue);
373 } else {
374 // when disabled, show along top edge
375 return 0;
376 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700377 }
378
Jeff Sharkeyd360e5e2011-07-26 19:30:26 -0700379 /**
380 * Set valid range this sweep can move within, in {@link #mAxis} values. The
381 * most restrictive combination of all valid ranges is used.
382 */
383 public void setValidRange(long validAfter, long validBefore) {
384 mValidAfter = validAfter;
385 mValidBefore = validBefore;
Jeff Sharkey4ec71e72011-07-10 12:23:55 -0700386 }
387
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -0700388 public void setNeighborMargin(float neighborMargin) {
389 mNeighborMargin = neighborMargin;
390 }
391
Jeff Sharkeyb6548462014-07-21 15:38:06 -0700392 public void setSafeRegion(int safeRegion) {
393 mSafeRegion = safeRegion;
394 }
395
Jeff Sharkeyd360e5e2011-07-26 19:30:26 -0700396 /**
397 * Set valid range this sweep can move within, defined by the given
398 * {@link ChartSweepView}. The most restrictive combination of all valid
399 * ranges is used.
400 */
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -0700401 public void setValidRangeDynamic(ChartSweepView validAfter, ChartSweepView validBefore) {
Jeff Sharkeyd360e5e2011-07-26 19:30:26 -0700402 mValidAfterDynamic = validAfter;
403 mValidBeforeDynamic = validBefore;
Jeff Sharkey4ec71e72011-07-10 12:23:55 -0700404 }
405
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700406 /**
407 * Test if given {@link MotionEvent} is closer to another
408 * {@link ChartSweepView} compared to ourselves.
409 */
410 public boolean isTouchCloserTo(MotionEvent eventInParent, ChartSweepView another) {
Jeff Sharkey461842a2011-09-25 18:22:48 -0700411 final float selfDist = getTouchDistanceFromTarget(eventInParent);
412 final float anotherDist = another.getTouchDistanceFromTarget(eventInParent);
413 return anotherDist < selfDist;
414 }
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700415
Jeff Sharkey461842a2011-09-25 18:22:48 -0700416 private float getTouchDistanceFromTarget(MotionEvent eventInParent) {
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700417 if (mFollowAxis == HORIZONTAL) {
Jeff Sharkey461842a2011-09-25 18:22:48 -0700418 return Math.abs(eventInParent.getX() - (getX() + getTargetInset()));
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700419 } else {
Jeff Sharkey461842a2011-09-25 18:22:48 -0700420 return Math.abs(eventInParent.getY() - (getY() + getTargetInset()));
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700421 }
422 }
423
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700424 @Override
425 public boolean onTouchEvent(MotionEvent event) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700426 if (!isEnabled()) return false;
427
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700428 final View parent = (View) getParent();
429 switch (event.getAction()) {
430 case MotionEvent.ACTION_DOWN: {
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700431
432 // only start tracking when in sweet spot
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700433 final boolean acceptDrag;
434 final boolean acceptLabel;
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700435 if (mFollowAxis == VERTICAL) {
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700436 acceptDrag = event.getX() > getWidth() - (mSweepPadding.right * 8);
437 acceptLabel = mLabelLayout != null ? event.getX() < mLabelLayout.getWidth()
438 : false;
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700439 } else {
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700440 acceptDrag = event.getY() > getHeight() - (mSweepPadding.bottom * 8);
441 acceptLabel = mLabelLayout != null ? event.getY() < mLabelLayout.getHeight()
442 : false;
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700443 }
444
445 final MotionEvent eventInParent = event.copy();
446 eventInParent.offsetLocation(getLeft(), getTop());
447
448 // ignore event when closer to a neighbor
Jeff Sharkey461842a2011-09-25 18:22:48 -0700449 for (ChartSweepView neighbor : mNeighbors) {
450 if (isTouchCloserTo(eventInParent, neighbor)) {
451 return false;
452 }
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700453 }
454
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700455 if (acceptDrag) {
456 if (mFollowAxis == VERTICAL) {
457 mTrackingStart = getTop() - mMargins.top;
458 } else {
459 mTrackingStart = getLeft() - mMargins.left;
460 }
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700461 mTracking = event.copy();
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700462 mTouchMode = MODE_DRAG;
Jeff Sharkeyd360e5e2011-07-26 19:30:26 -0700463
464 // starting drag should activate entire chart
465 if (!parent.isActivated()) {
466 parent.setActivated(true);
467 }
468
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700469 return true;
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700470 } else if (acceptLabel) {
471 mTouchMode = MODE_LABEL;
472 return true;
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700473 } else {
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700474 mTouchMode = MODE_NONE;
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700475 return false;
476 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700477 }
478 case MotionEvent.ACTION_MOVE: {
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700479 if (mTouchMode == MODE_LABEL) {
480 return true;
481 }
482
Jeff Sharkey8a503642011-06-10 13:31:21 -0700483 getParent().requestDisallowInterceptTouchEvent(true);
484
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700485 // content area of parent
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -0700486 final Rect parentContent = getParentContentRect();
Jeff Sharkey4ec71e72011-07-10 12:23:55 -0700487 final Rect clampRect = computeClampRect(parentContent);
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700488 if (clampRect.isEmpty()) return true;
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700489
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700490 long value;
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700491 if (mFollowAxis == VERTICAL) {
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700492 final float currentTargetY = getTop() - mMargins.top;
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700493 final float requestedTargetY = mTrackingStart
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700494 + (event.getRawY() - mTracking.getRawY());
495 final float clampedTargetY = MathUtils.constrain(
Jeff Sharkey4ec71e72011-07-10 12:23:55 -0700496 requestedTargetY, clampRect.top, clampRect.bottom);
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700497 setTranslationY(clampedTargetY - currentTargetY);
498
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700499 value = mAxis.convertToValue(clampedTargetY - parentContent.top);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700500 } else {
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700501 final float currentTargetX = getLeft() - mMargins.left;
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700502 final float requestedTargetX = mTrackingStart
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700503 + (event.getRawX() - mTracking.getRawX());
504 final float clampedTargetX = MathUtils.constrain(
Jeff Sharkey4ec71e72011-07-10 12:23:55 -0700505 requestedTargetX, clampRect.left, clampRect.right);
Jeff Sharkeyf54f4352011-06-23 22:15:54 -0700506 setTranslationX(clampedTargetX - currentTargetX);
507
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700508 value = mAxis.convertToValue(clampedTargetX - parentContent.left);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700509 }
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700510
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700511 // round value from drag to nearest increment
512 value -= value % mDragInterval;
513 setValue(value);
514
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700515 dispatchOnSweep(false);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700516 return true;
517 }
518 case MotionEvent.ACTION_UP: {
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700519 if (mTouchMode == MODE_LABEL) {
520 performClick();
521 } else if (mTouchMode == MODE_DRAG) {
522 mTrackingStart = 0;
523 mTracking = null;
524 mValue = mLabelValue;
525 dispatchOnSweep(true);
526 setTranslationX(0);
527 setTranslationY(0);
528 requestLayout();
529 }
530
531 mTouchMode = MODE_NONE;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700532 return true;
533 }
534 default: {
535 return false;
536 }
537 }
538 }
539
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -0700540 /**
541 * Update {@link #mValue} based on current position, including any
542 * {@link #onTouchEvent(MotionEvent)} in progress. Typically used when
543 * {@link ChartAxis} changes during sweep adjustment.
544 */
545 public void updateValueFromPosition() {
546 final Rect parentContent = getParentContentRect();
547 if (mFollowAxis == VERTICAL) {
548 final float effectiveY = getY() - mMargins.top - parentContent.top;
549 setValue(mAxis.convertToValue(effectiveY));
550 } else {
551 final float effectiveX = getX() - mMargins.left - parentContent.left;
552 setValue(mAxis.convertToValue(effectiveX));
553 }
554 }
555
556 public int shouldAdjustAxis() {
557 return mAxis.shouldAdjustAxis(getValue());
558 }
559
560 private Rect getParentContentRect() {
561 final View parent = (View) getParent();
562 return new Rect(parent.getPaddingLeft(), parent.getPaddingTop(),
563 parent.getWidth() - parent.getPaddingRight(),
564 parent.getHeight() - parent.getPaddingBottom());
565 }
566
Jeff Sharkeyd360e5e2011-07-26 19:30:26 -0700567 @Override
568 public void addOnLayoutChangeListener(OnLayoutChangeListener listener) {
569 // ignored to keep LayoutTransition from animating us
570 }
571
572 @Override
573 public void removeOnLayoutChangeListener(OnLayoutChangeListener listener) {
574 // ignored to keep LayoutTransition from animating us
575 }
576
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -0700577 private long getValidAfterDynamic() {
Jeff Sharkeyd360e5e2011-07-26 19:30:26 -0700578 final ChartSweepView dynamic = mValidAfterDynamic;
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -0700579 return dynamic != null && dynamic.isEnabled() ? dynamic.getValue() : Long.MIN_VALUE;
Jeff Sharkeyd360e5e2011-07-26 19:30:26 -0700580 }
581
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -0700582 private long getValidBeforeDynamic() {
Jeff Sharkeyd360e5e2011-07-26 19:30:26 -0700583 final ChartSweepView dynamic = mValidBeforeDynamic;
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -0700584 return dynamic != null && dynamic.isEnabled() ? dynamic.getValue() : Long.MAX_VALUE;
Jeff Sharkeyd360e5e2011-07-26 19:30:26 -0700585 }
586
Jeff Sharkey4ec71e72011-07-10 12:23:55 -0700587 /**
588 * Compute {@link Rect} in {@link #getParent()} coordinates that we should
Jeff Sharkeyd360e5e2011-07-26 19:30:26 -0700589 * be clamped inside of, usually from {@link #setValidRange(long, long)}
Jeff Sharkey4ec71e72011-07-10 12:23:55 -0700590 * style rules.
591 */
592 private Rect computeClampRect(Rect parentContent) {
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -0700593 // create two rectangles, and pick most restrictive combination
594 final Rect rect = buildClampRect(parentContent, mValidAfter, mValidBefore, 0f);
595 final Rect dynamicRect = buildClampRect(
596 parentContent, getValidAfterDynamic(), getValidBeforeDynamic(), mNeighborMargin);
Jeff Sharkey4ec71e72011-07-10 12:23:55 -0700597
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700598 if (!rect.intersect(dynamicRect)) {
599 rect.setEmpty();
600 }
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -0700601 return rect;
602 }
603
604 private Rect buildClampRect(
605 Rect parentContent, long afterValue, long beforeValue, float margin) {
606 if (mAxis instanceof InvertedChartAxis) {
607 long temp = beforeValue;
608 beforeValue = afterValue;
609 afterValue = temp;
Jeff Sharkeyd360e5e2011-07-26 19:30:26 -0700610 }
Jeff Sharkey4ec71e72011-07-10 12:23:55 -0700611
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -0700612 final boolean afterValid = afterValue != Long.MIN_VALUE && afterValue != Long.MAX_VALUE;
613 final boolean beforeValid = beforeValue != Long.MIN_VALUE && beforeValue != Long.MAX_VALUE;
614
615 final float afterPoint = mAxis.convertToPoint(afterValue) + margin;
616 final float beforePoint = mAxis.convertToPoint(beforeValue) - margin;
617
618 final Rect clampRect = new Rect(parentContent);
Jeff Sharkey4ec71e72011-07-10 12:23:55 -0700619 if (mFollowAxis == VERTICAL) {
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -0700620 if (beforeValid) clampRect.bottom = clampRect.top + (int) beforePoint;
621 if (afterValid) clampRect.top += afterPoint;
Jeff Sharkey4ec71e72011-07-10 12:23:55 -0700622 } else {
Jeff Sharkeye2afc0f2011-08-01 15:29:30 -0700623 if (beforeValid) clampRect.right = clampRect.left + (int) beforePoint;
624 if (afterValid) clampRect.left += afterPoint;
Jeff Sharkey4ec71e72011-07-10 12:23:55 -0700625 }
626 return clampRect;
627 }
628
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700629 @Override
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700630 protected void drawableStateChanged() {
631 super.drawableStateChanged();
632 if (mSweep.isStateful()) {
633 mSweep.setState(getDrawableState());
634 }
635 }
636
637 @Override
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700638 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700639
640 // TODO: handle vertical labels
641 if (isEnabled() && mLabelLayout != null) {
642 final int sweepHeight = mSweep.getIntrinsicHeight();
643 final int templateHeight = mLabelLayout.getHeight();
644
645 mSweepOffset.x = 0;
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700646 mSweepOffset.y = 0;
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700647 mSweepOffset.y = (int) ((templateHeight / 2) - getTargetInset());
648 setMeasuredDimension(mSweep.getIntrinsicWidth(), Math.max(sweepHeight, templateHeight));
649
650 } else {
651 mSweepOffset.x = 0;
652 mSweepOffset.y = 0;
653 setMeasuredDimension(mSweep.getIntrinsicWidth(), mSweep.getIntrinsicHeight());
654 }
655
656 if (mFollowAxis == VERTICAL) {
657 final int targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
658 - mSweepPadding.bottom;
659 mMargins.top = -(mSweepPadding.top + (targetHeight / 2));
660 mMargins.bottom = 0;
661 mMargins.left = -mSweepPadding.left;
662 mMargins.right = mSweepPadding.right;
663 } else {
664 final int targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
665 - mSweepPadding.right;
666 mMargins.left = -(mSweepPadding.left + (targetWidth / 2));
667 mMargins.right = 0;
668 mMargins.top = -mSweepPadding.top;
669 mMargins.bottom = mSweepPadding.bottom;
670 }
671
Jeff Sharkey5d706792011-09-08 18:57:17 -0700672 mContentOffset.set(0, 0, 0, 0);
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700673
674 // make touch target area larger
Jeff Sharkey5d706792011-09-08 18:57:17 -0700675 final int widthBefore = getMeasuredWidth();
676 final int heightBefore = getMeasuredHeight();
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700677 if (mFollowAxis == HORIZONTAL) {
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700678 final int widthAfter = widthBefore * 3;
Jeff Sharkey5d706792011-09-08 18:57:17 -0700679 setMeasuredDimension(widthAfter, heightBefore);
680 mContentOffset.left = (widthAfter - widthBefore) / 2;
681
682 final int offset = mSweepPadding.bottom * 2;
683 mContentOffset.bottom -= offset;
684 mMargins.bottom += offset;
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700685 } else {
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700686 final int heightAfter = heightBefore * 2;
Jeff Sharkey5d706792011-09-08 18:57:17 -0700687 setMeasuredDimension(widthBefore, heightAfter);
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700688 mContentOffset.offset(0, (heightAfter - heightBefore) / 2);
Jeff Sharkey5d706792011-09-08 18:57:17 -0700689
690 final int offset = mSweepPadding.right * 2;
691 mContentOffset.right -= offset;
692 mMargins.right += offset;
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700693 }
694
Jeff Sharkey5d706792011-09-08 18:57:17 -0700695 mSweepOffset.offset(mContentOffset.left, mContentOffset.top);
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700696 mMargins.offset(-mSweepOffset.x, -mSweepOffset.y);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700697 }
698
699 @Override
Jeff Sharkeyb98c55b2011-09-11 17:29:49 -0700700 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
701 super.onLayout(changed, left, top, right, bottom);
702 invalidateLabelOffset();
703 }
704
705 @Override
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700706 protected void onDraw(Canvas canvas) {
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700707 super.onDraw(canvas);
708
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700709 final int width = getWidth();
710 final int height = getHeight();
711
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700712 final int labelSize;
713 if (isEnabled() && mLabelLayout != null) {
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700714 final int count = canvas.save();
715 {
Jeff Sharkeybdf98e82011-11-10 17:17:24 -0800716 final float alignOffset = mLabelSize - LARGE_WIDTH;
717 canvas.translate(
718 mContentOffset.left + alignOffset, mContentOffset.top + mLabelOffset);
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700719 mLabelLayout.draw(canvas);
720 }
721 canvas.restoreToCount(count);
Jeff Sharkeyb6548462014-07-21 15:38:06 -0700722 labelSize = (int) mLabelSize + mSafeRegion;
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700723 } else {
724 labelSize = 0;
725 }
726
727 if (mFollowAxis == VERTICAL) {
Jeff Sharkey5d706792011-09-08 18:57:17 -0700728 mSweep.setBounds(labelSize, mSweepOffset.y, width + mContentOffset.right,
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700729 mSweepOffset.y + mSweep.getIntrinsicHeight());
730 } else {
Jeff Sharkey5d706792011-09-08 18:57:17 -0700731 mSweep.setBounds(mSweepOffset.x, labelSize, mSweepOffset.x + mSweep.getIntrinsicWidth(),
732 height + mContentOffset.bottom);
Jeff Sharkeyec3be8a2011-07-09 01:02:56 -0700733 }
734
Jeff Sharkey52c3f442011-06-23 00:39:38 -0700735 mSweep.draw(canvas);
Jeff Sharkeya53188f2011-09-13 19:56:45 -0700736
737 if (DRAW_OUTLINE) {
738 mOutlinePaint.setColor(Color.RED);
739 canvas.drawRect(0, 0, width, height, mOutlinePaint);
740 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700741 }
742
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700743 public static float getLabelTop(ChartSweepView view) {
Jeff Sharkey5d706792011-09-08 18:57:17 -0700744 return view.getY() + view.mContentOffset.top;
Jeff Sharkey55d18a52011-08-27 17:09:43 -0700745 }
746
747 public static float getLabelBottom(ChartSweepView view) {
748 return getLabelTop(view) + view.mLabelLayout.getHeight();
749 }
Jeff Sharkeybdf98e82011-11-10 17:17:24 -0800750
751 public static float getLabelWidth(ChartSweepView view) {
752 return Layout.getDesiredWidth(view.mLabelLayout.getText(), view.mLabelLayout.getPaint());
753 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700754}