Home | History | Annotate | Download | only in calculator2
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *   http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.calculator2;
     18 
     19 import android.animation.ArgbEvaluator;
     20 import androidx.recyclerview.widget.RecyclerView;
     21 import android.view.View;
     22 import android.widget.TextView;
     23 
     24 /**
     25  * Contains the logic for animating the recyclerview elements on drag.
     26  */
     27 public final class DragController {
     28 
     29     private static final String TAG = "DragController";
     30 
     31     private static final ArgbEvaluator mColorEvaluator = new ArgbEvaluator();
     32 
     33     // References to views from the Calculator Display.
     34     private CalculatorFormula mDisplayFormula;
     35     private CalculatorResult mDisplayResult;
     36     private View mToolbar;
     37 
     38     private int mFormulaTranslationY;
     39     private int mFormulaTranslationX;
     40     private float mFormulaScale;
     41     private float mResultScale;
     42 
     43     private float mResultTranslationY;
     44     private int mResultTranslationX;
     45 
     46     private int mDisplayHeight;
     47 
     48     private int mFormulaStartColor;
     49     private int mFormulaEndColor;
     50 
     51     private int mResultStartColor;
     52     private int mResultEndColor;
     53 
     54     // The padding at the bottom of the RecyclerView itself.
     55     private int mBottomPaddingHeight;
     56 
     57     private boolean mAnimationInitialized;
     58 
     59     private boolean mOneLine;
     60     private boolean mIsDisplayEmpty;
     61 
     62     private AnimationController mAnimationController;
     63 
     64     private Evaluator mEvaluator;
     65 
     66     public void setEvaluator(Evaluator evaluator) {
     67         mEvaluator = evaluator;
     68     }
     69 
     70     public void initializeController(boolean isResult, boolean oneLine, boolean isDisplayEmpty) {
     71         mOneLine = oneLine;
     72         mIsDisplayEmpty = isDisplayEmpty;
     73         if (mIsDisplayEmpty) {
     74             // Empty display
     75             mAnimationController = new EmptyAnimationController();
     76         } else if (isResult) {
     77             // Result
     78             mAnimationController = new ResultAnimationController();
     79         } else {
     80             // There is something in the formula field. There may or may not be
     81             // a quick result.
     82             mAnimationController = new AnimationController();
     83         }
     84     }
     85 
     86     public void setDisplayFormula(CalculatorFormula formula) {
     87         mDisplayFormula = formula;
     88     }
     89 
     90     public void setDisplayResult(CalculatorResult result) {
     91         mDisplayResult = result;
     92     }
     93 
     94     public void setToolbar(View toolbar) {
     95         mToolbar = toolbar;
     96     }
     97 
     98     public void animateViews(float yFraction, RecyclerView recyclerView) {
     99         if (mDisplayFormula == null
    100                 || mDisplayResult == null
    101                 || mToolbar == null
    102                 || mEvaluator == null) {
    103             // Bail if we aren't yet initialized.
    104             return;
    105         }
    106 
    107         final HistoryAdapter.ViewHolder vh =
    108                 (HistoryAdapter.ViewHolder) recyclerView.findViewHolderForAdapterPosition(0);
    109         if (yFraction > 0 && vh != null) {
    110             recyclerView.setVisibility(View.VISIBLE);
    111         }
    112         if (vh != null && !mIsDisplayEmpty
    113                 && vh.getItemViewType() == HistoryAdapter.HISTORY_VIEW_TYPE) {
    114             final AlignedTextView formula = vh.getFormula();
    115             final CalculatorResult result = vh.getResult();
    116             final TextView date = vh.getDate();
    117             final View divider = vh.getDivider();
    118 
    119             if (!mAnimationInitialized) {
    120                 mBottomPaddingHeight = recyclerView.getPaddingBottom();
    121 
    122                 mAnimationController.initializeScales(formula, result);
    123 
    124                 mAnimationController.initializeColorAnimators(formula, result);
    125 
    126                 mAnimationController.initializeFormulaTranslationX(formula);
    127 
    128                 mAnimationController.initializeFormulaTranslationY(formula, result);
    129 
    130                 mAnimationController.initializeResultTranslationX(result);
    131 
    132                 mAnimationController.initializeResultTranslationY(result);
    133 
    134                 mAnimationInitialized = true;
    135             }
    136 
    137             result.setScaleX(mAnimationController.getResultScale(yFraction));
    138             result.setScaleY(mAnimationController.getResultScale(yFraction));
    139 
    140             formula.setScaleX(mAnimationController.getFormulaScale(yFraction));
    141             formula.setScaleY(mAnimationController.getFormulaScale(yFraction));
    142 
    143             formula.setPivotX(formula.getWidth() - formula.getPaddingEnd());
    144             formula.setPivotY(formula.getHeight() - formula.getPaddingBottom());
    145 
    146             result.setPivotX(result.getWidth() - result.getPaddingEnd());
    147             result.setPivotY(result.getHeight() - result.getPaddingBottom());
    148 
    149             formula.setTranslationX(mAnimationController.getFormulaTranslationX(yFraction));
    150             formula.setTranslationY(mAnimationController.getFormulaTranslationY(yFraction));
    151 
    152             result.setTranslationX(mAnimationController.getResultTranslationX(yFraction));
    153             result.setTranslationY(mAnimationController.getResultTranslationY(yFraction));
    154 
    155             formula.setTextColor((int) mColorEvaluator.evaluate(yFraction, mFormulaStartColor,
    156                     mFormulaEndColor));
    157 
    158             result.setTextColor((int) mColorEvaluator.evaluate(yFraction, mResultStartColor,
    159                     mResultEndColor));
    160 
    161             date.setTranslationY(mAnimationController.getDateTranslationY(yFraction));
    162             divider.setTranslationY(mAnimationController.getDateTranslationY(yFraction));
    163         } else if (mIsDisplayEmpty) {
    164             // There is no current expression but we still need to collect information
    165             // to translate the other viewholders.
    166             if (!mAnimationInitialized) {
    167                 mAnimationController.initializeDisplayHeight();
    168                 mAnimationInitialized = true;
    169             }
    170         }
    171 
    172         // Move up all ViewHolders above the current expression; if there is no current expression,
    173         // we're translating all the viewholders.
    174         for (int i = recyclerView.getChildCount() - 1;
    175              i >= mAnimationController.getFirstTranslatedViewHolderIndex();
    176              --i) {
    177             final RecyclerView.ViewHolder vh2 =
    178                     recyclerView.getChildViewHolder(recyclerView.getChildAt(i));
    179             if (vh2 != null) {
    180                 final View view = vh2.itemView;
    181                 if (view != null) {
    182                     view.setTranslationY(
    183                         mAnimationController.getHistoryElementTranslationY(yFraction));
    184                 }
    185             }
    186         }
    187     }
    188 
    189     /**
    190      * Reset all initialized values.
    191      */
    192     public void initializeAnimation(boolean isResult, boolean oneLine, boolean isDisplayEmpty) {
    193         mAnimationInitialized = false;
    194         initializeController(isResult, oneLine, isDisplayEmpty);
    195     }
    196 
    197     public interface AnimateTextInterface {
    198 
    199         void initializeDisplayHeight();
    200 
    201         void initializeColorAnimators(AlignedTextView formula, CalculatorResult result);
    202 
    203         void initializeScales(AlignedTextView formula, CalculatorResult result);
    204 
    205         void initializeFormulaTranslationX(AlignedTextView formula);
    206 
    207         void initializeFormulaTranslationY(AlignedTextView formula, CalculatorResult result);
    208 
    209         void initializeResultTranslationX(CalculatorResult result);
    210 
    211         void initializeResultTranslationY(CalculatorResult result);
    212 
    213         float getResultTranslationX(float yFraction);
    214 
    215         float getResultTranslationY(float yFraction);
    216 
    217         float getResultScale(float yFraction);
    218 
    219         float getFormulaScale(float yFraction);
    220 
    221         float getFormulaTranslationX(float yFraction);
    222 
    223         float getFormulaTranslationY(float yFraction);
    224 
    225         float getDateTranslationY(float yFraction);
    226 
    227         float getHistoryElementTranslationY(float yFraction);
    228 
    229         // Return the lowest index of the first Viewholder to be translated upwards.
    230         // If there is no current expression, we translate all the viewholders; otherwise,
    231         // we start at index 1.
    232         int getFirstTranslatedViewHolderIndex();
    233     }
    234 
    235     // The default AnimationController when Display is in INPUT state and DisplayFormula is not
    236     // empty. There may or may not be a quick result.
    237     public class AnimationController implements DragController.AnimateTextInterface {
    238 
    239         public void initializeDisplayHeight() {
    240             // no-op
    241         }
    242 
    243         public void initializeColorAnimators(AlignedTextView formula, CalculatorResult result) {
    244             mFormulaStartColor = mDisplayFormula.getCurrentTextColor();
    245             mFormulaEndColor = formula.getCurrentTextColor();
    246 
    247             mResultStartColor = mDisplayResult.getCurrentTextColor();
    248             mResultEndColor = result.getCurrentTextColor();
    249         }
    250 
    251         public void initializeScales(AlignedTextView formula, CalculatorResult result) {
    252             // Calculate the scale for the text
    253             mFormulaScale = mDisplayFormula.getTextSize() / formula.getTextSize();
    254         }
    255 
    256         public void initializeFormulaTranslationY(AlignedTextView formula,
    257                 CalculatorResult result) {
    258             if (mOneLine) {
    259                 // Disregard result since we set it to GONE in the one-line case.
    260                 mFormulaTranslationY =
    261                         mDisplayFormula.getPaddingBottom() - formula.getPaddingBottom()
    262                         - mBottomPaddingHeight;
    263             } else {
    264                 // Baseline of formula moves by the difference in formula bottom padding and the
    265                 // difference in result height.
    266                 mFormulaTranslationY =
    267                         mDisplayFormula.getPaddingBottom() - formula.getPaddingBottom()
    268                                 + mDisplayResult.getHeight() - result.getHeight()
    269                                 - mBottomPaddingHeight;
    270             }
    271         }
    272 
    273         public void initializeFormulaTranslationX(AlignedTextView formula) {
    274             // Right border of formula moves by the difference in formula end padding.
    275             mFormulaTranslationX = mDisplayFormula.getPaddingEnd() - formula.getPaddingEnd();
    276         }
    277 
    278         public void initializeResultTranslationY(CalculatorResult result) {
    279             // Baseline of result moves by the difference in result bottom padding.
    280             mResultTranslationY = mDisplayResult.getPaddingBottom() - result.getPaddingBottom()
    281             - mBottomPaddingHeight;
    282         }
    283 
    284         public void initializeResultTranslationX(CalculatorResult result) {
    285             mResultTranslationX = mDisplayResult.getPaddingEnd() - result.getPaddingEnd();
    286         }
    287 
    288         public float getResultTranslationX(float yFraction) {
    289             return mResultTranslationX * (yFraction - 1f);
    290         }
    291 
    292         public float getResultTranslationY(float yFraction) {
    293             return mResultTranslationY * (yFraction - 1f);
    294         }
    295 
    296         public float getResultScale(float yFraction) {
    297             return 1f;
    298         }
    299 
    300         public float getFormulaScale(float yFraction) {
    301             return mFormulaScale + (1f - mFormulaScale) * yFraction;
    302         }
    303 
    304         public float getFormulaTranslationX(float yFraction) {
    305             return mFormulaTranslationX * (yFraction - 1f);
    306         }
    307 
    308         public float getFormulaTranslationY(float yFraction) {
    309             // Scale linearly between -FormulaTranslationY and 0.
    310             return mFormulaTranslationY * (yFraction - 1f);
    311         }
    312 
    313         public float getDateTranslationY(float yFraction) {
    314             // We also want the date to start out above the visible screen with
    315             // this distance decreasing as it's pulled down.
    316             // Account for the scaled formula height.
    317             return -mToolbar.getHeight() * (1f - yFraction)
    318                     + getFormulaTranslationY(yFraction)
    319                     - mDisplayFormula.getHeight() /getFormulaScale(yFraction) * (1f - yFraction);
    320         }
    321 
    322         public float getHistoryElementTranslationY(float yFraction) {
    323             return getDateTranslationY(yFraction);
    324         }
    325 
    326         public int getFirstTranslatedViewHolderIndex() {
    327             return 1;
    328         }
    329     }
    330 
    331     // The default AnimationController when Display is in RESULT state.
    332     public class ResultAnimationController extends AnimationController
    333             implements DragController.AnimateTextInterface {
    334         @Override
    335         public void initializeScales(AlignedTextView formula, CalculatorResult result) {
    336             final float textSize = mDisplayResult.getTextSize() * mDisplayResult.getScaleX();
    337             mResultScale = textSize / result.getTextSize();
    338             mFormulaScale = 1f;
    339         }
    340 
    341         @Override
    342         public void initializeFormulaTranslationY(AlignedTextView formula,
    343                 CalculatorResult result) {
    344             // Baseline of formula moves by the difference in formula bottom padding and the
    345             // difference in the result height.
    346             mFormulaTranslationY = mDisplayFormula.getPaddingBottom() - formula.getPaddingBottom()
    347                             + mDisplayResult.getHeight() - result.getHeight()
    348                             - mBottomPaddingHeight;
    349         }
    350 
    351         @Override
    352         public void initializeFormulaTranslationX(AlignedTextView formula) {
    353             // Right border of formula moves by the difference in formula end padding.
    354             mFormulaTranslationX = mDisplayFormula.getPaddingEnd() - formula.getPaddingEnd();
    355         }
    356 
    357         @Override
    358         public void initializeResultTranslationY(CalculatorResult result) {
    359             // Baseline of result moves by the difference in result bottom padding.
    360             mResultTranslationY =  mDisplayResult.getPaddingBottom() - result.getPaddingBottom()
    361                     - mDisplayResult.getTranslationY()
    362                     - mBottomPaddingHeight;
    363         }
    364 
    365         @Override
    366         public void initializeResultTranslationX(CalculatorResult result) {
    367             mResultTranslationX = mDisplayResult.getPaddingEnd() - result.getPaddingEnd();
    368         }
    369 
    370         @Override
    371         public float getResultTranslationX(float yFraction) {
    372             return (mResultTranslationX * yFraction) - mResultTranslationX;
    373         }
    374 
    375         @Override
    376         public float getResultTranslationY(float yFraction) {
    377             return (mResultTranslationY * yFraction) - mResultTranslationY;
    378         }
    379 
    380         @Override
    381         public float getFormulaTranslationX(float yFraction) {
    382             return (mFormulaTranslationX * yFraction) -
    383                     mFormulaTranslationX;
    384         }
    385 
    386         @Override
    387         public float getFormulaTranslationY(float yFraction) {
    388             return getDateTranslationY(yFraction);
    389         }
    390 
    391         @Override
    392         public float getResultScale(float yFraction) {
    393             return mResultScale - (mResultScale * yFraction) + yFraction;
    394         }
    395 
    396         @Override
    397         public float getFormulaScale(float yFraction) {
    398             return 1f;
    399         }
    400 
    401         @Override
    402         public float getDateTranslationY(float yFraction) {
    403             // We also want the date to start out above the visible screen with
    404             // this distance decreasing as it's pulled down.
    405             return -mToolbar.getHeight() * (1f - yFraction)
    406                     + (mResultTranslationY * yFraction) - mResultTranslationY
    407                     - mDisplayFormula.getPaddingTop() +
    408                     (mDisplayFormula.getPaddingTop() * yFraction);
    409         }
    410 
    411         @Override
    412         public int getFirstTranslatedViewHolderIndex() {
    413             return 1;
    414         }
    415     }
    416 
    417     // The default AnimationController when Display is completely empty.
    418     public class EmptyAnimationController extends AnimationController
    419             implements DragController.AnimateTextInterface {
    420         @Override
    421         public void initializeDisplayHeight() {
    422             mDisplayHeight = mToolbar.getHeight() + mDisplayResult.getHeight()
    423                     + mDisplayFormula.getHeight();
    424         }
    425 
    426         @Override
    427         public void initializeScales(AlignedTextView formula, CalculatorResult result) {
    428             // no-op
    429         }
    430 
    431         @Override
    432         public void initializeFormulaTranslationY(AlignedTextView formula,
    433                 CalculatorResult result) {
    434             // no-op
    435         }
    436 
    437         @Override
    438         public void initializeFormulaTranslationX(AlignedTextView formula) {
    439             // no-op
    440         }
    441 
    442         @Override
    443         public void initializeResultTranslationY(CalculatorResult result) {
    444             // no-op
    445         }
    446 
    447         @Override
    448         public void initializeResultTranslationX(CalculatorResult result) {
    449             // no-op
    450         }
    451 
    452         @Override
    453         public float getResultTranslationX(float yFraction) {
    454             return 0f;
    455         }
    456 
    457         @Override
    458         public float getResultTranslationY(float yFraction) {
    459             return 0f;
    460         }
    461 
    462         @Override
    463         public float getFormulaScale(float yFraction) {
    464             return 1f;
    465         }
    466 
    467         @Override
    468         public float getDateTranslationY(float yFraction) {
    469             return 0f;
    470         }
    471 
    472         @Override
    473         public float getHistoryElementTranslationY(float yFraction) {
    474             return -mDisplayHeight * (1f - yFraction) - mBottomPaddingHeight;
    475         }
    476 
    477         @Override
    478         public int getFirstTranslatedViewHolderIndex() {
    479             return 0;
    480         }
    481     }
    482 }
    483