1ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey/*
2ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * Copyright (C) 2011 The Android Open Source Project
3ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey *
4ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
5ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * you may not use this file except in compliance with the License.
6ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * You may obtain a copy of the License at
7ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey *
8ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
9ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey *
10ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * Unless required by applicable law or agreed to in writing, software
11ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
12ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * See the License for the specific language governing permissions and
14ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * limitations under the License.
15ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey */
16ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
17ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkeypackage com.android.settings.widget;
18ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
19ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkeyimport android.content.Context;
2052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkeyimport android.content.res.TypedArray;
21ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkeyimport android.graphics.Canvas;
22ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkeyimport android.graphics.Color;
23ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkeyimport android.graphics.Paint;
2455d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkeyimport android.graphics.Paint.Style;
25ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkeyimport android.graphics.Point;
2652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkeyimport android.graphics.Rect;
2752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkeyimport android.graphics.drawable.Drawable;
28ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkeyimport android.text.DynamicLayout;
29bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkeyimport android.text.Layout;
30ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkeyimport android.text.Layout.Alignment;
31ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkeyimport android.text.SpannableStringBuilder;
32ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkeyimport android.text.TextPaint;
3352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkeyimport android.util.AttributeSet;
3452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkeyimport android.util.MathUtils;
35ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkeyimport android.view.MotionEvent;
36ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkeyimport android.view.View;
37ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
3852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkeyimport com.android.settings.R;
39ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkeyimport com.google.common.base.Preconditions;
40ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
41ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey/**
42ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * Sweep across a {@link ChartView} at a specific {@link ChartAxis} value, which
43ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey * a user can drag.
44ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey */
4554d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkeypublic class ChartSweepView extends View {
46ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
4755d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    private static final boolean DRAW_OUTLINE = false;
4855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
495d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey    // TODO: clean up all the various padding/offset/margins
505d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey
5152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    private Drawable mSweep;
52ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private Rect mSweepPadding = new Rect();
5355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
5455d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    /** Offset of content inside this view. */
555d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey    private Rect mContentOffset = new Rect();
5655d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    /** Offset of {@link #mSweep} inside this view. */
57ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private Point mSweepOffset = new Point();
58ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
59ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private Rect mMargins = new Rect();
60e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    private float mNeighborMargin;
61f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
6252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    private int mFollowAxis;
63ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
64bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey    private int mLabelMinSize;
65bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey    private float mLabelSize;
66bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey
67ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private int mLabelTemplateRes;
68ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private int mLabelColor;
69ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
70ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private SpannableStringBuilder mLabelTemplate;
71ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private DynamicLayout mLabelLayout;
7252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
7352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    private ChartAxis mAxis;
74ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    private long mValue;
7528130d96385d7d7b17992b45fb5d124836d85880Jeff Sharkey    private long mLabelValue;
76ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
77d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    private long mValidAfter;
78d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    private long mValidBefore;
79d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    private ChartSweepView mValidAfterDynamic;
80d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    private ChartSweepView mValidBeforeDynamic;
814ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey
82b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey    private float mLabelOffset;
83b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey
8455d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    private Paint mOutlinePaint = new Paint();
8555d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
8652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public static final int HORIZONTAL = 0;
8752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public static final int VERTICAL = 1;
8852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
89a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private int mTouchMode = MODE_NONE;
90a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
91a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private static final int MODE_NONE = 0;
92a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private static final int MODE_DRAG = 1;
93a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private static final int MODE_LABEL = 2;
94a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
95bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey    private static final int LARGE_WIDTH = 1024;
96bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey
97a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private long mDragInterval = 1;
98a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
99ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    public interface OnSweepListener {
100ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        public void onSweep(ChartSweepView sweep, boolean sweepDone);
101a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        public void requestEdit(ChartSweepView sweep);
102ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
103ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
104ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    private OnSweepListener mListener;
105a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
106a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private float mTrackingStart;
107ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    private MotionEvent mTracking;
108ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
109461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey    private ChartSweepView[] mNeighbors = new ChartSweepView[0];
110461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey
11152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public ChartSweepView(Context context) {
11254d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey        this(context, null);
11352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
114ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
11552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public ChartSweepView(Context context, AttributeSet attrs) {
11652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        this(context, attrs, 0);
11752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
118ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
11952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public ChartSweepView(Context context, AttributeSet attrs, int defStyle) {
12052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        super(context, attrs, defStyle);
121ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
12252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        final TypedArray a = context.obtainStyledAttributes(
12352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey                attrs, R.styleable.ChartSweepView, defStyle, 0);
1248a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey
12552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        setSweepDrawable(a.getDrawable(R.styleable.ChartSweepView_sweepDrawable));
12652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        setFollowAxis(a.getInt(R.styleable.ChartSweepView_followAxis, -1));
127e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        setNeighborMargin(a.getDimensionPixelSize(R.styleable.ChartSweepView_neighborMargin, 0));
128ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
129bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey        setLabelMinSize(a.getDimensionPixelSize(R.styleable.ChartSweepView_labelSize, 0));
130ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
131ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        setLabelColor(a.getColor(R.styleable.ChartSweepView_labelColor, Color.BLUE));
13252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
133a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        // TODO: moved focused state directly into assets
134a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        setBackgroundResource(R.drawable.data_usage_sweep_background);
135a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
13655d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        mOutlinePaint.setColor(Color.RED);
13755d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        mOutlinePaint.setStrokeWidth(1f);
13855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        mOutlinePaint.setStyle(Style.STROKE);
13955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
14052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        a.recycle();
14152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
142a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        setClickable(true);
143a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        setFocusable(true);
144a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        setOnClickListener(mClickListener);
145a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
14652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        setWillNotDraw(false);
14752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
14852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
149a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private OnClickListener mClickListener = new OnClickListener() {
150a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        public void onClick(View v) {
151a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey            dispatchRequestEdit();
152a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        }
153a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    };
154a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
15552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    void init(ChartAxis axis) {
15652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        mAxis = Preconditions.checkNotNull(axis, "missing axis");
15752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
158ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
159461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey    public void setNeighbors(ChartSweepView... neighbors) {
160461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey        mNeighbors = neighbors;
161461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey    }
162461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey
16352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public int getFollowAxis() {
16452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        return mFollowAxis;
16552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
16652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
167ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    public Rect getMargins() {
168ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        return mMargins;
169f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey    }
170f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
171a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    public void setDragInterval(long dragInterval) {
172a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        mDragInterval = dragInterval;
173a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    }
174a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
175f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey    /**
176f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey     * Return the number of pixels that the "target" area is inset from the
177f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey     * {@link View} edge, along the current {@link #setFollowAxis(int)}.
178f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey     */
179ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private float getTargetInset() {
180f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey        if (mFollowAxis == VERTICAL) {
181ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final float targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
182ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                    - mSweepPadding.bottom;
18355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            return mSweepPadding.top + (targetHeight / 2) + mSweepOffset.y;
184f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey        } else {
185ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final float targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
186ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                    - mSweepPadding.right;
18755d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            return mSweepPadding.left + (targetWidth / 2) + mSweepOffset.x;
188f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey        }
189ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
190ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
191ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    public void addOnSweepListener(OnSweepListener listener) {
192ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        mListener = listener;
193ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
194ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
195ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    private void dispatchOnSweep(boolean sweepDone) {
196ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        if (mListener != null) {
197ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            mListener.onSweep(this, sweepDone);
198ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        }
199ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
200ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
201a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private void dispatchRequestEdit() {
202a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        if (mListener != null) {
203a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey            mListener.requestEdit(this);
204a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        }
205a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    }
206a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
207ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    @Override
208ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    public void setEnabled(boolean enabled) {
209ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        super.setEnabled(enabled);
210a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        setFocusable(enabled);
211ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        requestLayout();
212ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    }
213ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
21452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public void setSweepDrawable(Drawable sweep) {
21552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        if (mSweep != null) {
21652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            mSweep.setCallback(null);
21752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            unscheduleDrawable(mSweep);
21852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        }
21952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
22052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        if (sweep != null) {
22152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            sweep.setCallback(this);
22252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            if (sweep.isStateful()) {
22352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey                sweep.setState(getDrawableState());
22452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            }
22552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            sweep.setVisible(getVisibility() == VISIBLE, false);
22652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            mSweep = sweep;
227ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            sweep.getPadding(mSweepPadding);
22852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        } else {
22952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            mSweep = null;
23052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        }
23152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
23252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        invalidate();
23352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
23452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
23552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public void setFollowAxis(int followAxis) {
23652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        mFollowAxis = followAxis;
23752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
23852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
239bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey    public void setLabelMinSize(int minSize) {
240bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey        mLabelMinSize = minSize;
241ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        invalidateLabelTemplate();
242ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    }
243ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
244ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    public void setLabelTemplate(int resId) {
245ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        mLabelTemplateRes = resId;
246ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        invalidateLabelTemplate();
247ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    }
248ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
249ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    public void setLabelColor(int color) {
250ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        mLabelColor = color;
251ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        invalidateLabelTemplate();
252ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    }
253ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
254ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private void invalidateLabelTemplate() {
255ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        if (mLabelTemplateRes != 0) {
256ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final CharSequence template = getResources().getText(mLabelTemplateRes);
257ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
258ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
259ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            paint.density = getResources().getDisplayMetrics().density;
260ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            paint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
261ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            paint.setColor(mLabelColor);
26255d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            paint.setShadowLayer(4 * paint.density, 0, 0, Color.BLACK);
263ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
264ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mLabelTemplate = new SpannableStringBuilder(template);
265ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mLabelLayout = new DynamicLayout(
266bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey                    mLabelTemplate, paint, LARGE_WIDTH, Alignment.ALIGN_RIGHT, 1f, 0f, false);
267ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            invalidateLabel();
268ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
269ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        } else {
270ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mLabelTemplate = null;
271ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mLabelLayout = null;
272ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        }
273ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
27452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        invalidate();
275ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        requestLayout();
276ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    }
277ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
278ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private void invalidateLabel() {
279ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        if (mLabelTemplate != null && mAxis != null) {
28028130d96385d7d7b17992b45fb5d124836d85880Jeff Sharkey            mLabelValue = mAxis.buildLabel(getResources(), mLabelTemplate, mValue);
281a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey            setContentDescription(mLabelTemplate);
282b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            invalidateLabelOffset();
283ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            invalidate();
2845d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey        } else {
2855d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mLabelValue = mValue;
286ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        }
28752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
28852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
289b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey    /**
290b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey     * When overlapping with neighbor, split difference and push label.
291b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey     */
292b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey    public void invalidateLabelOffset() {
293b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        float margin;
294b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        float labelOffset = 0;
295b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        if (mFollowAxis == VERTICAL) {
296b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            if (mValidAfterDynamic != null) {
297bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey                mLabelSize = Math.max(getLabelWidth(this), getLabelWidth(mValidAfterDynamic));
298b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                margin = getLabelTop(mValidAfterDynamic) - getLabelBottom(this);
299b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                if (margin < 0) {
300b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                    labelOffset = margin / 2;
301b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                }
302b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            } else if (mValidBeforeDynamic != null) {
303bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey                mLabelSize = Math.max(getLabelWidth(this), getLabelWidth(mValidBeforeDynamic));
304b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                margin = getLabelTop(this) - getLabelBottom(mValidBeforeDynamic);
305b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                if (margin < 0) {
306b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                    labelOffset = -margin / 2;
307b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                }
308bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey            } else {
309bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey                mLabelSize = getLabelWidth(this);
310b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            }
311b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        } else {
312b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            // TODO: implement horizontal labels
313b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        }
314b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey
315bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey        mLabelSize = Math.max(mLabelSize, mLabelMinSize);
316bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey
317b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        // when offsetting label, neighbor probably needs to offset too
318b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        if (labelOffset != mLabelOffset) {
319b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            mLabelOffset = labelOffset;
320b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            invalidate();
321b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            if (mValidAfterDynamic != null) mValidAfterDynamic.invalidateLabelOffset();
322b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            if (mValidBeforeDynamic != null) mValidBeforeDynamic.invalidateLabelOffset();
323b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        }
324b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey    }
325b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey
32652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    @Override
32752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public void jumpDrawablesToCurrentState() {
32852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        super.jumpDrawablesToCurrentState();
32952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        if (mSweep != null) {
33052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            mSweep.jumpToCurrentState();
33152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        }
33252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
33352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
33452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    @Override
33552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public void setVisibility(int visibility) {
33652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        super.setVisibility(visibility);
33752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        if (mSweep != null) {
33852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            mSweep.setVisible(visibility == VISIBLE, false);
33952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        }
34052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
34152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
34252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    @Override
34352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    protected boolean verifyDrawable(Drawable who) {
34452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        return who == mSweep || super.verifyDrawable(who);
34552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
34652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
347ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    public ChartAxis getAxis() {
348ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        return mAxis;
349ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
350ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
3518a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey    public void setValue(long value) {
3528a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey        mValue = value;
353ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        invalidateLabel();
3548a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey    }
3558a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey
356ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    public long getValue() {
357ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        return mValue;
358ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
359ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
36028130d96385d7d7b17992b45fb5d124836d85880Jeff Sharkey    public long getLabelValue() {
36128130d96385d7d7b17992b45fb5d124836d85880Jeff Sharkey        return mLabelValue;
36228130d96385d7d7b17992b45fb5d124836d85880Jeff Sharkey    }
36328130d96385d7d7b17992b45fb5d124836d85880Jeff Sharkey
364ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    public float getPoint() {
3652af35fb44d385d7a04ea1b421c426a0350f0e896Jeff Sharkey        if (isEnabled()) {
3662af35fb44d385d7a04ea1b421c426a0350f0e896Jeff Sharkey            return mAxis.convertToPoint(mValue);
3672af35fb44d385d7a04ea1b421c426a0350f0e896Jeff Sharkey        } else {
3682af35fb44d385d7a04ea1b421c426a0350f0e896Jeff Sharkey            // when disabled, show along top edge
3692af35fb44d385d7a04ea1b421c426a0350f0e896Jeff Sharkey            return 0;
3702af35fb44d385d7a04ea1b421c426a0350f0e896Jeff Sharkey        }
371ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
372ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
373d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    /**
374d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     * Set valid range this sweep can move within, in {@link #mAxis} values. The
375d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     * most restrictive combination of all valid ranges is used.
376d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     */
377d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    public void setValidRange(long validAfter, long validBefore) {
378d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        mValidAfter = validAfter;
379d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        mValidBefore = validBefore;
3804ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey    }
3814ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey
382e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    public void setNeighborMargin(float neighborMargin) {
383e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        mNeighborMargin = neighborMargin;
384e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    }
385e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey
386d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    /**
387d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     * Set valid range this sweep can move within, defined by the given
388d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     * {@link ChartSweepView}. The most restrictive combination of all valid
389d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     * ranges is used.
390d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     */
391e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    public void setValidRangeDynamic(ChartSweepView validAfter, ChartSweepView validBefore) {
392d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        mValidAfterDynamic = validAfter;
393d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        mValidBeforeDynamic = validBefore;
3944ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey    }
3954ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey
39655d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    /**
39755d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey     * Test if given {@link MotionEvent} is closer to another
39855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey     * {@link ChartSweepView} compared to ourselves.
39955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey     */
40055d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    public boolean isTouchCloserTo(MotionEvent eventInParent, ChartSweepView another) {
401461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey        final float selfDist = getTouchDistanceFromTarget(eventInParent);
402461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey        final float anotherDist = another.getTouchDistanceFromTarget(eventInParent);
403461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey        return anotherDist < selfDist;
404461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey    }
40555d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
406461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey    private float getTouchDistanceFromTarget(MotionEvent eventInParent) {
40755d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        if (mFollowAxis == HORIZONTAL) {
408461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey            return Math.abs(eventInParent.getX() - (getX() + getTargetInset()));
40955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        } else {
410461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey            return Math.abs(eventInParent.getY() - (getY() + getTargetInset()));
41155d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        }
41255d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    }
41355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
414ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    @Override
415ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    public boolean onTouchEvent(MotionEvent event) {
4168a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey        if (!isEnabled()) return false;
4178a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey
418ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        final View parent = (View) getParent();
419ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        switch (event.getAction()) {
420ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            case MotionEvent.ACTION_DOWN: {
421f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
422f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                // only start tracking when in sweet spot
423a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                final boolean acceptDrag;
424a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                final boolean acceptLabel;
425f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                if (mFollowAxis == VERTICAL) {
426a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    acceptDrag = event.getX() > getWidth() - (mSweepPadding.right * 8);
427a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    acceptLabel = mLabelLayout != null ? event.getX() < mLabelLayout.getWidth()
428a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                            : false;
429f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                } else {
430a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    acceptDrag = event.getY() > getHeight() - (mSweepPadding.bottom * 8);
431a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    acceptLabel = mLabelLayout != null ? event.getY() < mLabelLayout.getHeight()
432a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                            : false;
43355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey                }
43455d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
43555d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey                final MotionEvent eventInParent = event.copy();
43655d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey                eventInParent.offsetLocation(getLeft(), getTop());
43755d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
43855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey                // ignore event when closer to a neighbor
439461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey                for (ChartSweepView neighbor : mNeighbors) {
440461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey                    if (isTouchCloserTo(eventInParent, neighbor)) {
441461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey                        return false;
442461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey                    }
443f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                }
444f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
445a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                if (acceptDrag) {
446a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    if (mFollowAxis == VERTICAL) {
447a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                        mTrackingStart = getTop() - mMargins.top;
448a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    } else {
449a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                        mTrackingStart = getLeft() - mMargins.left;
450a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    }
451f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                    mTracking = event.copy();
452a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    mTouchMode = MODE_DRAG;
453d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey
454d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey                    // starting drag should activate entire chart
455d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey                    if (!parent.isActivated()) {
456d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey                        parent.setActivated(true);
457d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey                    }
458d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey
459f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                    return true;
460a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                } else if (acceptLabel) {
461a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    mTouchMode = MODE_LABEL;
462a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    return true;
463f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                } else {
464a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    mTouchMode = MODE_NONE;
465f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                    return false;
466f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                }
467ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            }
468ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            case MotionEvent.ACTION_MOVE: {
469a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                if (mTouchMode == MODE_LABEL) {
470a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    return true;
471a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                }
472a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
4738a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey                getParent().requestDisallowInterceptTouchEvent(true);
4748a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey
475f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                // content area of parent
476e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey                final Rect parentContent = getParentContentRect();
4774ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey                final Rect clampRect = computeClampRect(parentContent);
478a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                if (clampRect.isEmpty()) return true;
479f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
480a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                long value;
48152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey                if (mFollowAxis == VERTICAL) {
482ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                    final float currentTargetY = getTop() - mMargins.top;
483a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    final float requestedTargetY = mTrackingStart
484f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                            + (event.getRawY() - mTracking.getRawY());
485f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                    final float clampedTargetY = MathUtils.constrain(
4864ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey                            requestedTargetY, clampRect.top, clampRect.bottom);
487f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                    setTranslationY(clampedTargetY - currentTargetY);
488f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
489a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    value = mAxis.convertToValue(clampedTargetY - parentContent.top);
490ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey                } else {
491ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                    final float currentTargetX = getLeft() - mMargins.left;
492a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    final float requestedTargetX = mTrackingStart
493f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                            + (event.getRawX() - mTracking.getRawX());
494f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                    final float clampedTargetX = MathUtils.constrain(
4954ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey                            requestedTargetX, clampRect.left, clampRect.right);
496f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                    setTranslationX(clampedTargetX - currentTargetX);
497f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
498a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    value = mAxis.convertToValue(clampedTargetX - parentContent.left);
499ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey                }
500ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
501a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                // round value from drag to nearest increment
502a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                value -= value % mDragInterval;
503a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                setValue(value);
504a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
505ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                dispatchOnSweep(false);
506ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey                return true;
507ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            }
508ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            case MotionEvent.ACTION_UP: {
509a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                if (mTouchMode == MODE_LABEL) {
510a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    performClick();
511a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                } else if (mTouchMode == MODE_DRAG) {
512a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    mTrackingStart = 0;
513a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    mTracking = null;
514a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    mValue = mLabelValue;
515a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    dispatchOnSweep(true);
516a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    setTranslationX(0);
517a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    setTranslationY(0);
518a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    requestLayout();
519a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                }
520a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
521a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                mTouchMode = MODE_NONE;
522ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey                return true;
523ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            }
524ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            default: {
525ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey                return false;
526ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            }
527ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        }
528ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
529ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
530e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    /**
531e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey     * Update {@link #mValue} based on current position, including any
532e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey     * {@link #onTouchEvent(MotionEvent)} in progress. Typically used when
533e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey     * {@link ChartAxis} changes during sweep adjustment.
534e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey     */
535e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    public void updateValueFromPosition() {
536e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final Rect parentContent = getParentContentRect();
537e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        if (mFollowAxis == VERTICAL) {
538e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            final float effectiveY = getY() - mMargins.top - parentContent.top;
539e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            setValue(mAxis.convertToValue(effectiveY));
540e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        } else {
541e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            final float effectiveX = getX() - mMargins.left - parentContent.left;
542e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            setValue(mAxis.convertToValue(effectiveX));
543e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        }
544e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    }
545e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey
546e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    public int shouldAdjustAxis() {
547e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        return mAxis.shouldAdjustAxis(getValue());
548e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    }
549e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey
550e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    private Rect getParentContentRect() {
551e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final View parent = (View) getParent();
552e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        return new Rect(parent.getPaddingLeft(), parent.getPaddingTop(),
553e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey                parent.getWidth() - parent.getPaddingRight(),
554e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey                parent.getHeight() - parent.getPaddingBottom());
555e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    }
556e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey
557d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    @Override
558d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    public void addOnLayoutChangeListener(OnLayoutChangeListener listener) {
559d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        // ignored to keep LayoutTransition from animating us
560d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    }
561d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey
562d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    @Override
563d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    public void removeOnLayoutChangeListener(OnLayoutChangeListener listener) {
564d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        // ignored to keep LayoutTransition from animating us
565d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    }
566d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey
567e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    private long getValidAfterDynamic() {
568d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        final ChartSweepView dynamic = mValidAfterDynamic;
569e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        return dynamic != null && dynamic.isEnabled() ? dynamic.getValue() : Long.MIN_VALUE;
570d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    }
571d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey
572e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    private long getValidBeforeDynamic() {
573d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        final ChartSweepView dynamic = mValidBeforeDynamic;
574e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        return dynamic != null && dynamic.isEnabled() ? dynamic.getValue() : Long.MAX_VALUE;
575d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    }
576d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey
5774ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey    /**
5784ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey     * Compute {@link Rect} in {@link #getParent()} coordinates that we should
579d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     * be clamped inside of, usually from {@link #setValidRange(long, long)}
5804ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey     * style rules.
5814ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey     */
5824ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey    private Rect computeClampRect(Rect parentContent) {
583e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        // create two rectangles, and pick most restrictive combination
584e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final Rect rect = buildClampRect(parentContent, mValidAfter, mValidBefore, 0f);
585e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final Rect dynamicRect = buildClampRect(
586e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey                parentContent, getValidAfterDynamic(), getValidBeforeDynamic(), mNeighborMargin);
5874ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey
588a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        if (!rect.intersect(dynamicRect)) {
589a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey            rect.setEmpty();
590a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        }
591e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        return rect;
592e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    }
593e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey
594e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    private Rect buildClampRect(
595e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            Rect parentContent, long afterValue, long beforeValue, float margin) {
596e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        if (mAxis instanceof InvertedChartAxis) {
597e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            long temp = beforeValue;
598e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            beforeValue = afterValue;
599e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            afterValue = temp;
600d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        }
6014ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey
602e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final boolean afterValid = afterValue != Long.MIN_VALUE && afterValue != Long.MAX_VALUE;
603e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final boolean beforeValid = beforeValue != Long.MIN_VALUE && beforeValue != Long.MAX_VALUE;
604e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey
605e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final float afterPoint = mAxis.convertToPoint(afterValue) + margin;
606e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final float beforePoint = mAxis.convertToPoint(beforeValue) - margin;
607e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey
608e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final Rect clampRect = new Rect(parentContent);
6094ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey        if (mFollowAxis == VERTICAL) {
610e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            if (beforeValid) clampRect.bottom = clampRect.top + (int) beforePoint;
611e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            if (afterValid) clampRect.top += afterPoint;
6124ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey        } else {
613e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            if (beforeValid) clampRect.right = clampRect.left + (int) beforePoint;
614e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            if (afterValid) clampRect.left += afterPoint;
6154ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey        }
6164ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey        return clampRect;
6174ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey    }
6184ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey
619ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    @Override
62052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    protected void drawableStateChanged() {
62152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        super.drawableStateChanged();
62252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        if (mSweep.isStateful()) {
62352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            mSweep.setState(getDrawableState());
62452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        }
62552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
62652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
62752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    @Override
628ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
629ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
630ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        // TODO: handle vertical labels
631ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        if (isEnabled() && mLabelLayout != null) {
632ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final int sweepHeight = mSweep.getIntrinsicHeight();
633ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final int templateHeight = mLabelLayout.getHeight();
634ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
635ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mSweepOffset.x = 0;
63655d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            mSweepOffset.y = 0;
637ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mSweepOffset.y = (int) ((templateHeight / 2) - getTargetInset());
638ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            setMeasuredDimension(mSweep.getIntrinsicWidth(), Math.max(sweepHeight, templateHeight));
639ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
640ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        } else {
641ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mSweepOffset.x = 0;
642ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mSweepOffset.y = 0;
643ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            setMeasuredDimension(mSweep.getIntrinsicWidth(), mSweep.getIntrinsicHeight());
644ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        }
645ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
646ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        if (mFollowAxis == VERTICAL) {
647ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final int targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
648ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                    - mSweepPadding.bottom;
649ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.top = -(mSweepPadding.top + (targetHeight / 2));
650ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.bottom = 0;
651ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.left = -mSweepPadding.left;
652ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.right = mSweepPadding.right;
653ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        } else {
654ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final int targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
655ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                    - mSweepPadding.right;
656ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.left = -(mSweepPadding.left + (targetWidth / 2));
657ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.right = 0;
658ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.top = -mSweepPadding.top;
659ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.bottom = mSweepPadding.bottom;
660ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        }
661ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
6625d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey        mContentOffset.set(0, 0, 0, 0);
66355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
66455d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        // make touch target area larger
6655d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey        final int widthBefore = getMeasuredWidth();
6665d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey        final int heightBefore = getMeasuredHeight();
66755d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        if (mFollowAxis == HORIZONTAL) {
66855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            final int widthAfter = widthBefore * 3;
6695d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            setMeasuredDimension(widthAfter, heightBefore);
6705d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mContentOffset.left = (widthAfter - widthBefore) / 2;
6715d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey
6725d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            final int offset = mSweepPadding.bottom * 2;
6735d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mContentOffset.bottom -= offset;
6745d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mMargins.bottom += offset;
67555d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        } else {
676a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey            final int heightAfter = heightBefore * 2;
6775d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            setMeasuredDimension(widthBefore, heightAfter);
67855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            mContentOffset.offset(0, (heightAfter - heightBefore) / 2);
6795d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey
6805d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            final int offset = mSweepPadding.right * 2;
6815d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mContentOffset.right -= offset;
6825d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mMargins.right += offset;
68355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        }
68455d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
6855d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey        mSweepOffset.offset(mContentOffset.left, mContentOffset.top);
686ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        mMargins.offset(-mSweepOffset.x, -mSweepOffset.y);
687ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
688ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
689ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    @Override
690b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
691b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        super.onLayout(changed, left, top, right, bottom);
692b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        invalidateLabelOffset();
693b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey    }
694b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey
695b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey    @Override
696ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    protected void onDraw(Canvas canvas) {
697a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        super.onDraw(canvas);
698a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
699ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        final int width = getWidth();
700ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        final int height = getHeight();
701ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
702ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        final int labelSize;
703ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        if (isEnabled() && mLabelLayout != null) {
70455d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            final int count = canvas.save();
70555d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            {
706bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey                final float alignOffset = mLabelSize - LARGE_WIDTH;
707bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey                canvas.translate(
708bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey                        mContentOffset.left + alignOffset, mContentOffset.top + mLabelOffset);
70955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey                mLabelLayout.draw(canvas);
71055d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            }
71155d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            canvas.restoreToCount(count);
712bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey            labelSize = (int) mLabelSize;
713ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        } else {
714ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            labelSize = 0;
715ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        }
716ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
717ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        if (mFollowAxis == VERTICAL) {
7185d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mSweep.setBounds(labelSize, mSweepOffset.y, width + mContentOffset.right,
719ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                    mSweepOffset.y + mSweep.getIntrinsicHeight());
720ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        } else {
7215d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mSweep.setBounds(mSweepOffset.x, labelSize, mSweepOffset.x + mSweep.getIntrinsicWidth(),
7225d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey                    height + mContentOffset.bottom);
723ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        }
724ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
72552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        mSweep.draw(canvas);
726a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
727a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        if (DRAW_OUTLINE) {
728a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey            mOutlinePaint.setColor(Color.RED);
729a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey            canvas.drawRect(0, 0, width, height, mOutlinePaint);
730a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        }
731ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
732ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
73355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    public static float getLabelTop(ChartSweepView view) {
7345d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey        return view.getY() + view.mContentOffset.top;
73555d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    }
73655d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
73755d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    public static float getLabelBottom(ChartSweepView view) {
73855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        return getLabelTop(view) + view.mLabelLayout.getHeight();
73955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    }
740bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey
741bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey    public static float getLabelWidth(ChartSweepView view) {
742bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey        return Layout.getDesiredWidth(view.mLabelLayout.getText(), view.mLabelLayout.getPaint());
743bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey    }
744ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey}
745