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