1package com.android.deskclock;
2
3import android.content.Context;
4import android.content.res.Resources;
5import android.util.AttributeSet;
6import android.view.View;
7import android.widget.FrameLayout;
8import android.widget.ImageButton;
9import android.widget.TextView;
10
11/**
12 * This class adjusts the locations of children buttons and text of this view group by adjusting the
13 * margins of each item. The left and right buttons are aligned with the bottom of the circle. The
14 * stop button and label text are located within the circle with the stop button near the bottom and
15 * the label text near the top. The maximum text size for the label text view is also calculated.
16 */
17public class CircleButtonsLayout extends FrameLayout {
18
19    private int mCircleTimerViewId;
20    private int mResetAddButtonId;
21    private int mLabelId;
22    private float mDiamOffset;
23    private View mCircleView;
24    private ImageButton mResetAddButton;
25    private TextView mLabel;
26
27    @SuppressWarnings("unused")
28    public CircleButtonsLayout(Context context) {
29        this(context, null);
30    }
31
32    public CircleButtonsLayout(Context context, AttributeSet attrs) {
33        super(context, attrs);
34    }
35
36    public void setCircleTimerViewIds(int circleTimerViewId, int stopButtonId,  int labelId) {
37        mCircleTimerViewId = circleTimerViewId;
38        mResetAddButtonId = stopButtonId;
39        mLabelId = labelId;
40
41        final Resources res = getContext().getResources();
42        final float strokeSize = res.getDimension(R.dimen.circletimer_circle_size);
43        final float dotStrokeSize = res.getDimension(R.dimen.circletimer_dot_size);
44        final float markerStrokeSize = res.getDimension(R.dimen.circletimer_marker_size);
45        mDiamOffset = Utils.calculateRadiusOffset(strokeSize, dotStrokeSize, markerStrokeSize) * 2;
46    }
47
48    @Override
49    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
50        // We must call onMeasure both before and after re-measuring our views because the circle
51        // may not always be drawn here yet. The first onMeasure will force the circle to be drawn,
52        // and the second will force our re-measurements to take effect.
53        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
54        remeasureViews();
55        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
56    }
57
58    protected void remeasureViews() {
59        if (mCircleView == null) {
60            mCircleView = findViewById(mCircleTimerViewId);
61            if (mCircleView == null) {
62                return;
63            }
64            mResetAddButton = (ImageButton) findViewById(mResetAddButtonId);
65            mLabel = (TextView) findViewById(mLabelId);
66        }
67
68        final int frameWidth = mCircleView.getMeasuredWidth();
69        final int frameHeight = mCircleView.getMeasuredHeight();
70        final int minBound = Math.min(frameWidth, frameHeight);
71        final int circleDiam = (int) (minBound - mDiamOffset);
72
73        if (mResetAddButton != null) {
74            final MarginLayoutParams resetAddParams = (MarginLayoutParams) mResetAddButton
75                    .getLayoutParams();
76            resetAddParams.bottomMargin = circleDiam / 6;
77            if (minBound == frameWidth) {
78                resetAddParams.bottomMargin += (frameHeight - frameWidth) / 2;
79            }
80        }
81
82        if (mLabel != null) {
83            MarginLayoutParams labelParams = (MarginLayoutParams) mLabel.getLayoutParams();
84            labelParams.topMargin = circleDiam/6;
85            if (minBound == frameWidth) {
86                labelParams.topMargin += (frameHeight-frameWidth)/2;
87            }
88            /* The following formula has been simplified based on the following:
89             * Our goal is to calculate the maximum width for the label frame.
90             * We may do this with the following diagram to represent the top half of the circle:
91             *                 ___
92             *            .     |     .
93             *        ._________|         .
94             *     .       ^    |            .
95             *   /         x    |              \
96             *  |_______________|_______________|
97             *
98             *  where x represents the value we would like to calculate, and the final width of the
99             *  label will be w = 2 * x.
100             *
101             *  We may find x by drawing a right triangle from the center of the circle:
102             *                 ___
103             *            .     |     .
104             *        ._________|         .
105             *     .    .       |            .
106             *   /          .   | }y           \
107             *  |_____________.t|_______________|
108             *
109             *  where t represents the angle of that triangle, and y is the height of that triangle.
110             *
111             *  If r = radius of the circle, we know the following trigonometric identities:
112             *        cos(t) = y / r
113             *  and   sin(t) = x / r
114             *     => r * sin(t) = x
115             *  and   sin^2(t) = 1 - cos^2(t)
116             *     => sin(t) = +/- sqrt(1 - cos^2(t))
117             *  (note: because we need the positive value, we may drop the +/-).
118             *
119             *  To calculate the final width, we may combine our formulas:
120             *        w = 2 * x
121             *     => w = 2 * r * sin(t)
122             *     => w = 2 * r * sqrt(1 - cos^2(t))
123             *     => w = 2 * r * sqrt(1 - (y / r)^2)
124             *
125             *  Simplifying even further, to mitigate the complexity of the final formula:
126             *        sqrt(1 - (y / r)^2)
127             *     => sqrt(1 - (y^2 / r^2))
128             *     => sqrt((r^2 / r^2) - (y^2 / r^2))
129             *     => sqrt((r^2 - y^2) / (r^2))
130             *     => sqrt(r^2 - y^2) / sqrt(r^2)
131             *     => sqrt(r^2 - y^2) / r
132             *     => sqrt((r + y)*(r - y)) / r
133             *
134             * Placing this back in our formula, we end up with, as our final, reduced equation:
135             *        w = 2 * r * sqrt(1 - (y / r)^2)
136             *     => w = 2 * r * sqrt((r + y)*(r - y)) / r
137             *     => w = 2 * sqrt((r + y)*(r - y))
138             */
139            // Radius of the circle.
140            int r = circleDiam / 2;
141            // Y value of the top of the label, calculated from the center of the circle.
142            int y = frameHeight / 2 - labelParams.topMargin;
143            // New maximum width of the label.
144            double w = 2 * Math.sqrt((r + y) * (r - y));
145
146            mLabel.setMaxWidth((int) w);
147        }
148    }
149}
150