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