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 CircleButtonsLinearLayout(Context context)31 public CircleButtonsLinearLayout(Context context) { 32 this(context, null); 33 mContext = context; 34 } 35 CircleButtonsLinearLayout(Context context, AttributeSet attrs)36 public CircleButtonsLinearLayout(Context context, AttributeSet attrs) { 37 super(context, attrs); 38 mContext = context; 39 } 40 setCircleTimerViewIds(int circleTimerViewId, int leftButtonId, int rightButtonId, int stopButtonId, int leftButtonPaddingDimenId, int rightButtonPaddingDimenId, int labelId, int labelTextId)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 onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 remeasureViews()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 }