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