package com.android.deskclock; import android.content.Context; import android.util.AttributeSet; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; /** * TODO: Insert description here. (generated by sblitz) */ public class CircleButtonsLinearLayout extends LinearLayout { private Context mContext; private int mCircleTimerViewId; private int mLeftButtonId; private int mRightButtonId; private int mStopButtonId; private int mLabelId; private int mLabelTextId; private float mLeftButtonPadding; private float mRightButtonPadding; private float mStrokeSize; private float mDiamOffset; private CircleTimerView mCtv; private ImageButton mLeft, mRight; private TextView mStop; private FrameLayout mLabel; private TextView mLabelText; public CircleButtonsLinearLayout(Context context) { this(context, null); mContext = context; } public CircleButtonsLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; } public void setCircleTimerViewIds(int circleTimerViewId, int leftButtonId, int rightButtonId, int stopButtonId, int leftButtonPaddingDimenId, int rightButtonPaddingDimenId, int labelId, int labelTextId) { mCircleTimerViewId = circleTimerViewId; mLeftButtonId = leftButtonId; mRightButtonId = rightButtonId; mStopButtonId = stopButtonId; mLabelId = labelId; mLabelTextId = labelTextId; mLeftButtonPadding = mContext.getResources().getDimension(leftButtonPaddingDimenId); mRightButtonPadding = mContext.getResources().getDimension(rightButtonPaddingDimenId); float diamondStrokeSize = mContext.getResources().getDimension(R.dimen.circletimer_diamond_size); float markerStrokeSize = mContext.getResources().getDimension(R.dimen.circletimer_marker_size); mStrokeSize = mContext.getResources().getDimension(R.dimen.circletimer_circle_size); mDiamOffset = Utils.calculateRadiusOffset(mStrokeSize, diamondStrokeSize, markerStrokeSize) * 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 (mCtv == null) { mCtv = (CircleTimerView) findViewById(mCircleTimerViewId); if (mCtv == null) { return; } mLeft = (ImageButton) findViewById(mLeftButtonId); mRight = (ImageButton) findViewById(mRightButtonId); mStop = (TextView) findViewById(mStopButtonId); mLabel = (FrameLayout) findViewById(mLabelId); mLabelText = (TextView) findViewById(mLabelTextId); } int frameWidth = mCtv.getMeasuredWidth(); int frameHeight = mCtv.getMeasuredHeight(); int minBound = Math.min(frameWidth, frameHeight); int circleDiam = (int) (minBound - mDiamOffset); MarginLayoutParams stopParams = (MarginLayoutParams) mStop.getLayoutParams(); stopParams.bottomMargin = circleDiam/6; if (minBound == frameWidth) { stopParams.bottomMargin += (frameHeight-frameWidth)/2; } if (mLabel != null) { // label will be null if this is a stopwatch, which does not have a label. 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)); mLabelText.setMaxWidth((int) w); } int sideMarginOffset = (int) ((frameWidth - circleDiam - mStrokeSize) / 2) - (int) mContext.getResources().getDimension(R.dimen.timer_button_extra_offset); int leftMarginOffset = Math.max(0, sideMarginOffset - (int) mLeftButtonPadding); int rightMarginOffset = Math.max(0, sideMarginOffset - (int) mRightButtonPadding); int bottomMarginOffset = (frameHeight - minBound) / 2; MarginLayoutParams leftParams = (MarginLayoutParams) mLeft.getLayoutParams(); leftParams.leftMargin = leftMarginOffset; leftParams.bottomMargin = bottomMarginOffset; MarginLayoutParams rightParams = (MarginLayoutParams) mRight.getLayoutParams(); rightParams.rightMargin = rightMarginOffset; rightParams.bottomMargin = bottomMarginOffset; } }