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