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
38e6c5003278184c202833209164ddf1ae8c083f12Jeff Sharkeyimport com.android.internal.util.Preconditions;
3952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkeyimport com.android.settings.R;
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;
61b654846300f79e9e4c605ce62d09f9a05d232feeJeff Sharkey    private int mSafeRegion;
62f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
6352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    private int mFollowAxis;
64ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
65bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey    private int mLabelMinSize;
66bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey    private float mLabelSize;
67bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey
68ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private int mLabelTemplateRes;
69ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private int mLabelColor;
70ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
71ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private SpannableStringBuilder mLabelTemplate;
72ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private DynamicLayout mLabelLayout;
7352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
7452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    private ChartAxis mAxis;
75ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    private long mValue;
7628130d96385d7d7b17992b45fb5d124836d85880Jeff Sharkey    private long mLabelValue;
77ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
78d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    private long mValidAfter;
79d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    private long mValidBefore;
80d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    private ChartSweepView mValidAfterDynamic;
81d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    private ChartSweepView mValidBeforeDynamic;
824ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey
83b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey    private float mLabelOffset;
84b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey
8555d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    private Paint mOutlinePaint = new Paint();
8655d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
8752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public static final int HORIZONTAL = 0;
8852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public static final int VERTICAL = 1;
8952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
90a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private int mTouchMode = MODE_NONE;
91a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
92a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private static final int MODE_NONE = 0;
93a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private static final int MODE_DRAG = 1;
94a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private static final int MODE_LABEL = 2;
95a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
96bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey    private static final int LARGE_WIDTH = 1024;
97bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey
98a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private long mDragInterval = 1;
99a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
100ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    public interface OnSweepListener {
101ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        public void onSweep(ChartSweepView sweep, boolean sweepDone);
102a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        public void requestEdit(ChartSweepView sweep);
103ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
104ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
105ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    private OnSweepListener mListener;
106a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
107a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private float mTrackingStart;
108ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    private MotionEvent mTracking;
109ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
110461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey    private ChartSweepView[] mNeighbors = new ChartSweepView[0];
111461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey
11252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public ChartSweepView(Context context) {
11354d0af57fd2dca14f0c7c34a48942aa6ecdc3f06Jeff Sharkey        this(context, null);
11452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
115ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
11652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public ChartSweepView(Context context, AttributeSet attrs) {
11752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        this(context, attrs, 0);
11852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
119ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
12052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public ChartSweepView(Context context, AttributeSet attrs, int defStyle) {
12152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        super(context, attrs, defStyle);
122ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
12352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        final TypedArray a = context.obtainStyledAttributes(
12452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey                attrs, R.styleable.ChartSweepView, defStyle, 0);
1258a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey
12652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        setSweepDrawable(a.getDrawable(R.styleable.ChartSweepView_sweepDrawable));
12752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        setFollowAxis(a.getInt(R.styleable.ChartSweepView_followAxis, -1));
128e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        setNeighborMargin(a.getDimensionPixelSize(R.styleable.ChartSweepView_neighborMargin, 0));
129b654846300f79e9e4c605ce62d09f9a05d232feeJeff Sharkey        setSafeRegion(a.getDimensionPixelSize(R.styleable.ChartSweepView_safeRegion, 0));
130ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
131bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey        setLabelMinSize(a.getDimensionPixelSize(R.styleable.ChartSweepView_labelSize, 0));
132ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
133ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        setLabelColor(a.getColor(R.styleable.ChartSweepView_labelColor, Color.BLUE));
13452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
135a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        // TODO: moved focused state directly into assets
136a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        setBackgroundResource(R.drawable.data_usage_sweep_background);
137a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
13855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        mOutlinePaint.setColor(Color.RED);
13955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        mOutlinePaint.setStrokeWidth(1f);
14055d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        mOutlinePaint.setStyle(Style.STROKE);
14155d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
14252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        a.recycle();
14352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
144a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        setClickable(true);
145a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        setFocusable(true);
146a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        setOnClickListener(mClickListener);
147a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
14852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        setWillNotDraw(false);
14952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
15052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
151a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private OnClickListener mClickListener = new OnClickListener() {
152a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        public void onClick(View v) {
153a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey            dispatchRequestEdit();
154a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        }
155a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    };
156a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
15752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    void init(ChartAxis axis) {
15852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        mAxis = Preconditions.checkNotNull(axis, "missing axis");
15952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
160ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
161461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey    public void setNeighbors(ChartSweepView... neighbors) {
162461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey        mNeighbors = neighbors;
163461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey    }
164461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey
16552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public int getFollowAxis() {
16652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        return mFollowAxis;
16752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
16852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
169ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    public Rect getMargins() {
170ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        return mMargins;
171f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey    }
172f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
173a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    public void setDragInterval(long dragInterval) {
174a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        mDragInterval = dragInterval;
175a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    }
176a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
177f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey    /**
178f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey     * Return the number of pixels that the "target" area is inset from the
179f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey     * {@link View} edge, along the current {@link #setFollowAxis(int)}.
180f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey     */
181ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private float getTargetInset() {
182f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey        if (mFollowAxis == VERTICAL) {
183ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final float targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
184ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                    - mSweepPadding.bottom;
18555d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            return mSweepPadding.top + (targetHeight / 2) + mSweepOffset.y;
186f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey        } else {
187ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final float targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
188ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                    - mSweepPadding.right;
18955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            return mSweepPadding.left + (targetWidth / 2) + mSweepOffset.x;
190f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey        }
191ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
192ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
193ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    public void addOnSweepListener(OnSweepListener listener) {
194ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        mListener = listener;
195ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
196ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
197ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    private void dispatchOnSweep(boolean sweepDone) {
198ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        if (mListener != null) {
199ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            mListener.onSweep(this, sweepDone);
200ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        }
201ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
202ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
203a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    private void dispatchRequestEdit() {
204a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        if (mListener != null) {
205a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey            mListener.requestEdit(this);
206a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        }
207a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey    }
208a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
209ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    @Override
210ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    public void setEnabled(boolean enabled) {
211ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        super.setEnabled(enabled);
212a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        setFocusable(enabled);
213ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        requestLayout();
214ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    }
215ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
21652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public void setSweepDrawable(Drawable sweep) {
21752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        if (mSweep != null) {
21852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            mSweep.setCallback(null);
21952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            unscheduleDrawable(mSweep);
22052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        }
22152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
22252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        if (sweep != null) {
22352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            sweep.setCallback(this);
22452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            if (sweep.isStateful()) {
22552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey                sweep.setState(getDrawableState());
22652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            }
22752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            sweep.setVisible(getVisibility() == VISIBLE, false);
22852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            mSweep = sweep;
229ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            sweep.getPadding(mSweepPadding);
23052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        } else {
23152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            mSweep = null;
23252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        }
23352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
23452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        invalidate();
23552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
23652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
23752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public void setFollowAxis(int followAxis) {
23852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        mFollowAxis = followAxis;
23952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
24052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
241bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey    public void setLabelMinSize(int minSize) {
242bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey        mLabelMinSize = minSize;
243ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        invalidateLabelTemplate();
244ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    }
245ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
246ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    public void setLabelTemplate(int resId) {
247ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        mLabelTemplateRes = resId;
248ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        invalidateLabelTemplate();
249ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    }
250ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
251ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    public void setLabelColor(int color) {
252ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        mLabelColor = color;
253ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        invalidateLabelTemplate();
254ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    }
255ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
256ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private void invalidateLabelTemplate() {
257ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        if (mLabelTemplateRes != 0) {
258ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final CharSequence template = getResources().getText(mLabelTemplateRes);
259ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
260ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
261ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            paint.density = getResources().getDisplayMetrics().density;
262ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            paint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
263ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            paint.setColor(mLabelColor);
264ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
265ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mLabelTemplate = new SpannableStringBuilder(template);
266ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mLabelLayout = new DynamicLayout(
267bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey                    mLabelTemplate, paint, LARGE_WIDTH, Alignment.ALIGN_RIGHT, 1f, 0f, false);
268ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            invalidateLabel();
269ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
270ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        } else {
271ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mLabelTemplate = null;
272ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mLabelLayout = null;
273ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        }
274ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
27552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        invalidate();
276ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        requestLayout();
277ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    }
278ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
279ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey    private void invalidateLabel() {
280ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        if (mLabelTemplate != null && mAxis != null) {
28128130d96385d7d7b17992b45fb5d124836d85880Jeff Sharkey            mLabelValue = mAxis.buildLabel(getResources(), mLabelTemplate, mValue);
282a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey            setContentDescription(mLabelTemplate);
283b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            invalidateLabelOffset();
284ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            invalidate();
2855d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey        } else {
2865d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mLabelValue = mValue;
287ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        }
28852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
28952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
290b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey    /**
291b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey     * When overlapping with neighbor, split difference and push label.
292b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey     */
293b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey    public void invalidateLabelOffset() {
294b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        float margin;
295b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        float labelOffset = 0;
296b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        if (mFollowAxis == VERTICAL) {
297b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            if (mValidAfterDynamic != null) {
298bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey                mLabelSize = Math.max(getLabelWidth(this), getLabelWidth(mValidAfterDynamic));
299b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                margin = getLabelTop(mValidAfterDynamic) - getLabelBottom(this);
300b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                if (margin < 0) {
301b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                    labelOffset = margin / 2;
302b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                }
303b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            } else if (mValidBeforeDynamic != null) {
304bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey                mLabelSize = Math.max(getLabelWidth(this), getLabelWidth(mValidBeforeDynamic));
305b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                margin = getLabelTop(this) - getLabelBottom(mValidBeforeDynamic);
306b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                if (margin < 0) {
307b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                    labelOffset = -margin / 2;
308b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey                }
309bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey            } else {
310bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey                mLabelSize = getLabelWidth(this);
311b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            }
312b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        } else {
313b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            // TODO: implement horizontal labels
314b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        }
315b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey
316bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey        mLabelSize = Math.max(mLabelSize, mLabelMinSize);
317bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey
318b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        // when offsetting label, neighbor probably needs to offset too
319b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        if (labelOffset != mLabelOffset) {
320b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            mLabelOffset = labelOffset;
321b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            invalidate();
322b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            if (mValidAfterDynamic != null) mValidAfterDynamic.invalidateLabelOffset();
323b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey            if (mValidBeforeDynamic != null) mValidBeforeDynamic.invalidateLabelOffset();
324b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        }
325b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey    }
326b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey
32752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    @Override
32852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public void jumpDrawablesToCurrentState() {
32952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        super.jumpDrawablesToCurrentState();
33052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        if (mSweep != null) {
33152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            mSweep.jumpToCurrentState();
33252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        }
33352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
33452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
33552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    @Override
33652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    public void setVisibility(int visibility) {
33752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        super.setVisibility(visibility);
33852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        if (mSweep != null) {
33952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            mSweep.setVisible(visibility == VISIBLE, false);
34052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        }
34152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
34252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
34352c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    @Override
34452c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    protected boolean verifyDrawable(Drawable who) {
34552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        return who == mSweep || super.verifyDrawable(who);
34652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
34752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
348ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    public ChartAxis getAxis() {
349ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        return mAxis;
350ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
351ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
3528a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey    public void setValue(long value) {
3538a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey        mValue = value;
354ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        invalidateLabel();
3558a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey    }
3568a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey
357ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    public long getValue() {
358ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        return mValue;
359ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
360ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
36128130d96385d7d7b17992b45fb5d124836d85880Jeff Sharkey    public long getLabelValue() {
36228130d96385d7d7b17992b45fb5d124836d85880Jeff Sharkey        return mLabelValue;
36328130d96385d7d7b17992b45fb5d124836d85880Jeff Sharkey    }
36428130d96385d7d7b17992b45fb5d124836d85880Jeff Sharkey
365ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    public float getPoint() {
3662af35fb44d385d7a04ea1b421c426a0350f0e896Jeff Sharkey        if (isEnabled()) {
3672af35fb44d385d7a04ea1b421c426a0350f0e896Jeff Sharkey            return mAxis.convertToPoint(mValue);
3682af35fb44d385d7a04ea1b421c426a0350f0e896Jeff Sharkey        } else {
3692af35fb44d385d7a04ea1b421c426a0350f0e896Jeff Sharkey            // when disabled, show along top edge
3702af35fb44d385d7a04ea1b421c426a0350f0e896Jeff Sharkey            return 0;
3712af35fb44d385d7a04ea1b421c426a0350f0e896Jeff Sharkey        }
372ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
373ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
374d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    /**
375d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     * Set valid range this sweep can move within, in {@link #mAxis} values. The
376d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     * most restrictive combination of all valid ranges is used.
377d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     */
378d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    public void setValidRange(long validAfter, long validBefore) {
379d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        mValidAfter = validAfter;
380d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        mValidBefore = validBefore;
3814ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey    }
3824ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey
383e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    public void setNeighborMargin(float neighborMargin) {
384e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        mNeighborMargin = neighborMargin;
385e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    }
386e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey
387b654846300f79e9e4c605ce62d09f9a05d232feeJeff Sharkey    public void setSafeRegion(int safeRegion) {
388b654846300f79e9e4c605ce62d09f9a05d232feeJeff Sharkey        mSafeRegion = safeRegion;
389b654846300f79e9e4c605ce62d09f9a05d232feeJeff Sharkey    }
390b654846300f79e9e4c605ce62d09f9a05d232feeJeff Sharkey
391d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    /**
392d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     * Set valid range this sweep can move within, defined by the given
393d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     * {@link ChartSweepView}. The most restrictive combination of all valid
394d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     * ranges is used.
395d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     */
396e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    public void setValidRangeDynamic(ChartSweepView validAfter, ChartSweepView validBefore) {
397d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        mValidAfterDynamic = validAfter;
398d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        mValidBeforeDynamic = validBefore;
3994ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey    }
4004ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey
40155d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    /**
40255d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey     * Test if given {@link MotionEvent} is closer to another
40355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey     * {@link ChartSweepView} compared to ourselves.
40455d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey     */
40555d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    public boolean isTouchCloserTo(MotionEvent eventInParent, ChartSweepView another) {
406461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey        final float selfDist = getTouchDistanceFromTarget(eventInParent);
407461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey        final float anotherDist = another.getTouchDistanceFromTarget(eventInParent);
408461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey        return anotherDist < selfDist;
409461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey    }
41055d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
411461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey    private float getTouchDistanceFromTarget(MotionEvent eventInParent) {
41255d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        if (mFollowAxis == HORIZONTAL) {
413461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey            return Math.abs(eventInParent.getX() - (getX() + getTargetInset()));
41455d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        } else {
415461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey            return Math.abs(eventInParent.getY() - (getY() + getTargetInset()));
41655d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        }
41755d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    }
41855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
419ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    @Override
420ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    public boolean onTouchEvent(MotionEvent event) {
4218a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey        if (!isEnabled()) return false;
4228a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey
423ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        final View parent = (View) getParent();
424ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        switch (event.getAction()) {
425ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            case MotionEvent.ACTION_DOWN: {
426f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
427f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                // only start tracking when in sweet spot
428a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                final boolean acceptDrag;
429a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                final boolean acceptLabel;
430f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                if (mFollowAxis == VERTICAL) {
431a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    acceptDrag = event.getX() > getWidth() - (mSweepPadding.right * 8);
432a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    acceptLabel = mLabelLayout != null ? event.getX() < mLabelLayout.getWidth()
433a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                            : false;
434f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                } else {
435a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    acceptDrag = event.getY() > getHeight() - (mSweepPadding.bottom * 8);
436a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    acceptLabel = mLabelLayout != null ? event.getY() < mLabelLayout.getHeight()
437a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                            : false;
43855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey                }
43955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
44055d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey                final MotionEvent eventInParent = event.copy();
44155d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey                eventInParent.offsetLocation(getLeft(), getTop());
44255d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
44355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey                // ignore event when closer to a neighbor
444461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey                for (ChartSweepView neighbor : mNeighbors) {
445461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey                    if (isTouchCloserTo(eventInParent, neighbor)) {
446461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey                        return false;
447461842a8c0000ebc1351ec7fe30b0f820b769ccfJeff Sharkey                    }
448f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                }
449f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
450a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                if (acceptDrag) {
451a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    if (mFollowAxis == VERTICAL) {
452a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                        mTrackingStart = getTop() - mMargins.top;
453a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    } else {
454a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                        mTrackingStart = getLeft() - mMargins.left;
455a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    }
456f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                    mTracking = event.copy();
457a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    mTouchMode = MODE_DRAG;
458d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey
459d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey                    // starting drag should activate entire chart
460d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey                    if (!parent.isActivated()) {
461d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey                        parent.setActivated(true);
462d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey                    }
463d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey
464f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                    return true;
465a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                } else if (acceptLabel) {
466a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    mTouchMode = MODE_LABEL;
467a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    return true;
468f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                } else {
469a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    mTouchMode = MODE_NONE;
470f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                    return false;
471f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                }
472ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            }
473ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            case MotionEvent.ACTION_MOVE: {
474a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                if (mTouchMode == MODE_LABEL) {
475a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    return true;
476a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                }
477a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
4788a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey                getParent().requestDisallowInterceptTouchEvent(true);
4798a50364a71e7c261b54840210f8bacff5abecb34Jeff Sharkey
480f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                // content area of parent
481e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey                final Rect parentContent = getParentContentRect();
4824ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey                final Rect clampRect = computeClampRect(parentContent);
483a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                if (clampRect.isEmpty()) return true;
484f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
485a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                long value;
48652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey                if (mFollowAxis == VERTICAL) {
487ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                    final float currentTargetY = getTop() - mMargins.top;
488a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    final float requestedTargetY = mTrackingStart
489f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                            + (event.getRawY() - mTracking.getRawY());
490f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                    final float clampedTargetY = MathUtils.constrain(
4914ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey                            requestedTargetY, clampRect.top, clampRect.bottom);
492f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                    setTranslationY(clampedTargetY - currentTargetY);
493f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
494a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    value = mAxis.convertToValue(clampedTargetY - parentContent.top);
495ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey                } else {
496ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                    final float currentTargetX = getLeft() - mMargins.left;
497a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    final float requestedTargetX = mTrackingStart
498f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                            + (event.getRawX() - mTracking.getRawX());
499f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                    final float clampedTargetX = MathUtils.constrain(
5004ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey                            requestedTargetX, clampRect.left, clampRect.right);
501f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey                    setTranslationX(clampedTargetX - currentTargetX);
502f54f435f1f3215b39798c671fc64344d1867de4eJeff Sharkey
503a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    value = mAxis.convertToValue(clampedTargetX - parentContent.left);
504ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey                }
505ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
506a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                // round value from drag to nearest increment
507a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                value -= value % mDragInterval;
508a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                setValue(value);
509a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
510ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                dispatchOnSweep(false);
511ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey                return true;
512ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            }
513ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            case MotionEvent.ACTION_UP: {
514a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                if (mTouchMode == MODE_LABEL) {
515a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    performClick();
516a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                } else if (mTouchMode == MODE_DRAG) {
517a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    mTrackingStart = 0;
518a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    mTracking = null;
519a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    mValue = mLabelValue;
520a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    dispatchOnSweep(true);
521a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    setTranslationX(0);
522a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    setTranslationY(0);
523a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                    requestLayout();
524a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                }
525a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
526a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey                mTouchMode = MODE_NONE;
527ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey                return true;
528ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            }
529ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            default: {
530ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey                return false;
531ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey            }
532ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        }
533ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
534ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
535e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    /**
536e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey     * Update {@link #mValue} based on current position, including any
537e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey     * {@link #onTouchEvent(MotionEvent)} in progress. Typically used when
538e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey     * {@link ChartAxis} changes during sweep adjustment.
539e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey     */
540e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    public void updateValueFromPosition() {
541e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final Rect parentContent = getParentContentRect();
542e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        if (mFollowAxis == VERTICAL) {
543e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            final float effectiveY = getY() - mMargins.top - parentContent.top;
544e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            setValue(mAxis.convertToValue(effectiveY));
545e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        } else {
546e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            final float effectiveX = getX() - mMargins.left - parentContent.left;
547e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            setValue(mAxis.convertToValue(effectiveX));
548e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        }
549e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    }
550e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey
551e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    public int shouldAdjustAxis() {
552e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        return mAxis.shouldAdjustAxis(getValue());
553e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    }
554e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey
555e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    private Rect getParentContentRect() {
556e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final View parent = (View) getParent();
557e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        return new Rect(parent.getPaddingLeft(), parent.getPaddingTop(),
558e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey                parent.getWidth() - parent.getPaddingRight(),
559e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey                parent.getHeight() - parent.getPaddingBottom());
560e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    }
561e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey
562d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    @Override
563d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    public void addOnLayoutChangeListener(OnLayoutChangeListener listener) {
564d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        // ignored to keep LayoutTransition from animating us
565d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    }
566d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey
567d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    @Override
568d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    public void removeOnLayoutChangeListener(OnLayoutChangeListener listener) {
569d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        // ignored to keep LayoutTransition from animating us
570d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    }
571d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey
572e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    private long getValidAfterDynamic() {
573d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        final ChartSweepView dynamic = mValidAfterDynamic;
574e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        return dynamic != null && dynamic.isEnabled() ? dynamic.getValue() : Long.MIN_VALUE;
575d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    }
576d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey
577e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    private long getValidBeforeDynamic() {
578d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        final ChartSweepView dynamic = mValidBeforeDynamic;
579e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        return dynamic != null && dynamic.isEnabled() ? dynamic.getValue() : Long.MAX_VALUE;
580d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey    }
581d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey
5824ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey    /**
5834ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey     * Compute {@link Rect} in {@link #getParent()} coordinates that we should
584d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey     * be clamped inside of, usually from {@link #setValidRange(long, long)}
5854ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey     * style rules.
5864ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey     */
5874ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey    private Rect computeClampRect(Rect parentContent) {
588e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        // create two rectangles, and pick most restrictive combination
589e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final Rect rect = buildClampRect(parentContent, mValidAfter, mValidBefore, 0f);
590e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final Rect dynamicRect = buildClampRect(
591e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey                parentContent, getValidAfterDynamic(), getValidBeforeDynamic(), mNeighborMargin);
5924ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey
593a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        if (!rect.intersect(dynamicRect)) {
594a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey            rect.setEmpty();
595a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        }
596e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        return rect;
597e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    }
598e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey
599e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey    private Rect buildClampRect(
600e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            Rect parentContent, long afterValue, long beforeValue, float margin) {
601e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        if (mAxis instanceof InvertedChartAxis) {
602e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            long temp = beforeValue;
603e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            beforeValue = afterValue;
604e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            afterValue = temp;
605d360e5efaaf4ea5f487a84787210a6c5d26337a8Jeff Sharkey        }
6064ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey
607e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final boolean afterValid = afterValue != Long.MIN_VALUE && afterValue != Long.MAX_VALUE;
608e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final boolean beforeValid = beforeValue != Long.MIN_VALUE && beforeValue != Long.MAX_VALUE;
609e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey
610e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final float afterPoint = mAxis.convertToPoint(afterValue) + margin;
611e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final float beforePoint = mAxis.convertToPoint(beforeValue) - margin;
612e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey
613e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey        final Rect clampRect = new Rect(parentContent);
6144ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey        if (mFollowAxis == VERTICAL) {
615e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            if (beforeValid) clampRect.bottom = clampRect.top + (int) beforePoint;
616e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            if (afterValid) clampRect.top += afterPoint;
6174ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey        } else {
618e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            if (beforeValid) clampRect.right = clampRect.left + (int) beforePoint;
619e2afc0f283f58ce60c107643978bfff25ec5d5c1Jeff Sharkey            if (afterValid) clampRect.left += afterPoint;
6204ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey        }
6214ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey        return clampRect;
6224ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey    }
6234ec71e79efdb4250f8971eadf195a5ad826cb457Jeff Sharkey
624ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    @Override
62552c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    protected void drawableStateChanged() {
62652c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        super.drawableStateChanged();
62752c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        if (mSweep.isStateful()) {
62852c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey            mSweep.setState(getDrawableState());
62952c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        }
63052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    }
63152c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey
63252c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey    @Override
633ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
634ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
635ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        // TODO: handle vertical labels
636ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        if (isEnabled() && mLabelLayout != null) {
637ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final int sweepHeight = mSweep.getIntrinsicHeight();
638ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final int templateHeight = mLabelLayout.getHeight();
639ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
640ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mSweepOffset.x = 0;
64155d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            mSweepOffset.y = 0;
642ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mSweepOffset.y = (int) ((templateHeight / 2) - getTargetInset());
643ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            setMeasuredDimension(mSweep.getIntrinsicWidth(), Math.max(sweepHeight, templateHeight));
644ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
645ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        } else {
646ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mSweepOffset.x = 0;
647ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mSweepOffset.y = 0;
648ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            setMeasuredDimension(mSweep.getIntrinsicWidth(), mSweep.getIntrinsicHeight());
649ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        }
650ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
651ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        if (mFollowAxis == VERTICAL) {
652ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final int targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
653ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                    - mSweepPadding.bottom;
654ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.top = -(mSweepPadding.top + (targetHeight / 2));
655ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.bottom = 0;
656ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.left = -mSweepPadding.left;
657ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.right = mSweepPadding.right;
658ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        } else {
659ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            final int targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
660ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                    - mSweepPadding.right;
661ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.left = -(mSweepPadding.left + (targetWidth / 2));
662ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.right = 0;
663ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.top = -mSweepPadding.top;
664ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            mMargins.bottom = mSweepPadding.bottom;
665ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        }
666ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
6675d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey        mContentOffset.set(0, 0, 0, 0);
66855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
66955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        // make touch target area larger
6705d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey        final int widthBefore = getMeasuredWidth();
6715d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey        final int heightBefore = getMeasuredHeight();
67255d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        if (mFollowAxis == HORIZONTAL) {
67355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            final int widthAfter = widthBefore * 3;
6745d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            setMeasuredDimension(widthAfter, heightBefore);
6755d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mContentOffset.left = (widthAfter - widthBefore) / 2;
6765d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey
6775d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            final int offset = mSweepPadding.bottom * 2;
6785d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mContentOffset.bottom -= offset;
6795d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mMargins.bottom += offset;
68055d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        } else {
681a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey            final int heightAfter = heightBefore * 2;
6825d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            setMeasuredDimension(widthBefore, heightAfter);
68355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            mContentOffset.offset(0, (heightAfter - heightBefore) / 2);
6845d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey
6855d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            final int offset = mSweepPadding.right * 2;
6865d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mContentOffset.right -= offset;
6875d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mMargins.right += offset;
68855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        }
68955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
6905d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey        mSweepOffset.offset(mContentOffset.left, mContentOffset.top);
691ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        mMargins.offset(-mSweepOffset.x, -mSweepOffset.y);
692ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
693ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
694ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    @Override
695b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
696b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        super.onLayout(changed, left, top, right, bottom);
697b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey        invalidateLabelOffset();
698b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey    }
699b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey
700b98c55bd097e006703352f84f0271dec5181160aJeff Sharkey    @Override
701ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    protected void onDraw(Canvas canvas) {
702a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        super.onDraw(canvas);
703a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
704ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        final int width = getWidth();
705ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey        final int height = getHeight();
706ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
707ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        final int labelSize;
708ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        if (isEnabled() && mLabelLayout != null) {
70955d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            final int count = canvas.save();
71055d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            {
711bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey                final float alignOffset = mLabelSize - LARGE_WIDTH;
712bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey                canvas.translate(
713bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey                        mContentOffset.left + alignOffset, mContentOffset.top + mLabelOffset);
71455d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey                mLabelLayout.draw(canvas);
71555d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            }
71655d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey            canvas.restoreToCount(count);
717b654846300f79e9e4c605ce62d09f9a05d232feeJeff Sharkey            labelSize = (int) mLabelSize + mSafeRegion;
718ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        } else {
719ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey            labelSize = 0;
720ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        }
721ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
722ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        if (mFollowAxis == VERTICAL) {
7235d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mSweep.setBounds(labelSize, mSweepOffset.y, width + mContentOffset.right,
724ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey                    mSweepOffset.y + mSweep.getIntrinsicHeight());
725ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        } else {
7265d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey            mSweep.setBounds(mSweepOffset.x, labelSize, mSweepOffset.x + mSweep.getIntrinsicWidth(),
7275d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey                    height + mContentOffset.bottom);
728ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey        }
729ec3be8a4a5efa0e98aeefbf22bb876d09f08a023Jeff Sharkey
73052c3f4461b806e4f1ce48455ee2ba0ac05dfdab4Jeff Sharkey        mSweep.draw(canvas);
731a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey
732a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        if (DRAW_OUTLINE) {
733a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey            mOutlinePaint.setColor(Color.RED);
734a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey            canvas.drawRect(0, 0, width, height, mOutlinePaint);
735a53188fe5aa09918dd7b5a9ff79ba050a2bfc4c2Jeff Sharkey        }
736ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey    }
737ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey
73855d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    public static float getLabelTop(ChartSweepView view) {
7395d70679c02a57416eac46581d7242e2382e4b973Jeff Sharkey        return view.getY() + view.mContentOffset.top;
74055d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    }
74155d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey
74255d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    public static float getLabelBottom(ChartSweepView view) {
74355d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey        return getLabelTop(view) + view.mLabelLayout.getHeight();
74455d18a57e45e11f657346cbaa7fd454f92775229Jeff Sharkey    }
745bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey
746bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey    public static float getLabelWidth(ChartSweepView view) {
747bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey        return Layout.getDesiredWidth(view.mLabelLayout.getText(), view.mLabelLayout.getPaint());
748bdf98e84ab1eeedf9662ed3c08dc917f5fa80f85Jeff Sharkey    }
749ab2d8d3a38857b8c155e6c6393c5821f5a341aaeJeff Sharkey}
750