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