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