1 package com.android.deskclock; 2 3 import android.content.Context; 4 import android.util.AttributeSet; 5 import android.widget.FrameLayout; 6 import android.widget.ImageButton; 7 import android.widget.LinearLayout; 8 import android.widget.TextView; 9 10 /** 11 * TODO: Insert description here. (generated by sblitz) 12 */ 13 public class CircleButtonsLinearLayout extends LinearLayout { 14 private Context mContext; 15 private int mCircleTimerViewId; 16 private int mLeftButtonId; 17 private int mRightButtonId; 18 private int mStopButtonId; 19 private int mLabelId; 20 private int mLabelTextId; 21 private float mLeftButtonPadding; 22 private float mRightButtonPadding; 23 private float mStrokeSize; 24 private float mDiamOffset; 25 private CircleTimerView mCtv; 26 private ImageButton mLeft, mRight; 27 private TextView mStop; 28 private FrameLayout mLabel; 29 private TextView mLabelText; 30 31 public CircleButtonsLinearLayout(Context context) { 32 this(context, null); 33 mContext = context; 34 } 35 36 public CircleButtonsLinearLayout(Context context, AttributeSet attrs) { 37 super(context, attrs); 38 mContext = context; 39 } 40 41 public void setCircleTimerViewIds(int circleTimerViewId, int leftButtonId, int rightButtonId, 42 int stopButtonId, int leftButtonPaddingDimenId, int rightButtonPaddingDimenId, 43 int labelId, int labelTextId) { 44 mCircleTimerViewId = circleTimerViewId; 45 mLeftButtonId = leftButtonId; 46 mRightButtonId = rightButtonId; 47 mStopButtonId = stopButtonId; 48 mLabelId = labelId; 49 mLabelTextId = labelTextId; 50 mLeftButtonPadding = mContext.getResources().getDimension(leftButtonPaddingDimenId); 51 mRightButtonPadding = mContext.getResources().getDimension(rightButtonPaddingDimenId); 52 53 float diamondStrokeSize = 54 mContext.getResources().getDimension(R.dimen.circletimer_diamond_size); 55 float markerStrokeSize = 56 mContext.getResources().getDimension(R.dimen.circletimer_marker_size); 57 mStrokeSize = mContext.getResources().getDimension(R.dimen.circletimer_circle_size); 58 mDiamOffset = 59 Utils.calculateRadiusOffset(mStrokeSize, diamondStrokeSize, markerStrokeSize) * 2; 60 } 61 62 @Override 63 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 64 // We must call onMeasure both before and after re-measuring our views because the circle 65 // may not always be drawn here yet. The first onMeasure will force the circle to be drawn, 66 // and the second will force our re-measurements to take effect. 67 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 68 remeasureViews(); 69 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 70 } 71 72 protected void remeasureViews() { 73 if (mCtv == null) { 74 mCtv = (CircleTimerView) findViewById(mCircleTimerViewId); 75 if (mCtv == null) { 76 return; 77 } 78 mLeft = (ImageButton) findViewById(mLeftButtonId); 79 mRight = (ImageButton) findViewById(mRightButtonId); 80 mStop = (TextView) findViewById(mStopButtonId); 81 mLabel = (FrameLayout) findViewById(mLabelId); 82 mLabelText = (TextView) findViewById(mLabelTextId); 83 } 84 85 int frameWidth = mCtv.getMeasuredWidth(); 86 int frameHeight = mCtv.getMeasuredHeight(); 87 int minBound = Math.min(frameWidth, frameHeight); 88 int circleDiam = (int) (minBound - mDiamOffset); 89 90 MarginLayoutParams stopParams = (MarginLayoutParams) mStop.getLayoutParams(); 91 stopParams.bottomMargin = circleDiam/6; 92 if (minBound == frameWidth) { 93 stopParams.bottomMargin += (frameHeight-frameWidth)/2; 94 } 95 96 if (mLabel != null) { 97 // label will be null if this is a stopwatch, which does not have a label. 98 MarginLayoutParams labelParams = (MarginLayoutParams) mLabel.getLayoutParams(); 99 labelParams.topMargin = circleDiam/6; 100 if (minBound == frameWidth) { 101 labelParams.topMargin += (frameHeight-frameWidth)/2; 102 } 103 /* The following formula has been simplified based on the following: 104 * Our goal is to calculate the maximum width for the label frame. 105 * We may do this with the following diagram to represent the top half of the circle: 106 * ___ 107 * . | . 108 * ._________| . 109 * . ^ | . 110 * / x | \ 111 * |_______________|_______________| 112 * 113 * where x represents the value we would like to calculate, and the final width of the 114 * label will be w = 2 * x. 115 * 116 * We may find x by drawing a right triangle from the center of the circle: 117 * ___ 118 * . | . 119 * ._________| . 120 * . . | . 121 * / . | }y \ 122 * |_____________.t|_______________| 123 * 124 * where t represents the angle of that triangle, and y is the height of that triangle. 125 * 126 * If r = radius of the circle, we know the following trigonometric identities: 127 * cos(t) = y / r 128 * and sin(t) = x / r 129 * => r * sin(t) = x 130 * and sin^2(t) = 1 - cos^2(t) 131 * => sin(t) = +/- sqrt(1 - cos^2(t)) 132 * (note: because we need the positive value, we may drop the +/-). 133 * 134 * To calculate the final width, we may combine our formulas: 135 * w = 2 * x 136 * => w = 2 * r * sin(t) 137 * => w = 2 * r * sqrt(1 - cos^2(t)) 138 * => w = 2 * r * sqrt(1 - (y / r)^2) 139 * 140 * Simplifying even further, to mitigate the complexity of the final formula: 141 * sqrt(1 - (y / r)^2) 142 * => sqrt(1 - (y^2 / r^2)) 143 * => sqrt((r^2 / r^2) - (y^2 / r^2)) 144 * => sqrt((r^2 - y^2) / (r^2)) 145 * => sqrt(r^2 - y^2) / sqrt(r^2) 146 * => sqrt(r^2 - y^2) / r 147 * => sqrt((r + y)*(r - y)) / r 148 * 149 * Placing this back in our formula, we end up with, as our final, reduced equation: 150 * w = 2 * r * sqrt(1 - (y / r)^2) 151 * => w = 2 * r * sqrt((r + y)*(r - y)) / r 152 * => w = 2 * sqrt((r + y)*(r - y)) 153 */ 154 // Radius of the circle. 155 int r = circleDiam / 2; 156 // Y value of the top of the label, calculated from the center of the circle. 157 int y = frameHeight / 2 - labelParams.topMargin; 158 // New maximum width of the label. 159 double w = 2 * Math.sqrt((r + y) * (r - y)); 160 161 mLabelText.setMaxWidth((int) w); 162 } 163 164 int sideMarginOffset = (int) ((frameWidth - circleDiam - mStrokeSize) / 2) 165 - (int) mContext.getResources().getDimension(R.dimen.timer_button_extra_offset); 166 int leftMarginOffset = Math.max(0, sideMarginOffset - (int) mLeftButtonPadding); 167 int rightMarginOffset = Math.max(0, sideMarginOffset - (int) mRightButtonPadding); 168 int bottomMarginOffset = (frameHeight - minBound) / 2; 169 MarginLayoutParams leftParams = (MarginLayoutParams) mLeft.getLayoutParams(); 170 leftParams.leftMargin = leftMarginOffset; 171 leftParams.bottomMargin = bottomMarginOffset; 172 MarginLayoutParams rightParams = (MarginLayoutParams) mRight.getLayoutParams(); 173 rightParams.rightMargin = rightMarginOffset; 174 rightParams.bottomMargin = bottomMarginOffset; 175 } 176 }