Home | History | Annotate | Download | only in impl
      1 /*
      2  * Copyright (C) 2017 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.incallui.incall.impl;
     18 
     19 import android.animation.ValueAnimator;
     20 import android.content.Context;
     21 import android.graphics.Canvas;
     22 import android.graphics.Paint;
     23 import android.graphics.Path;
     24 import android.support.annotation.VisibleForTesting;
     25 import android.support.v4.view.ViewPager;
     26 import android.support.v4.view.ViewPager.OnPageChangeListener;
     27 import android.util.AttributeSet;
     28 import android.view.View;
     29 import com.android.dialer.common.Assert;
     30 
     31 /**
     32  * This is the view class for incall paginator visible when a user has EC data attached to their
     33  * call. It contains animation methods when the swipe gesture is performed.
     34  */
     35 public class InCallPaginator extends View implements OnPageChangeListener {
     36 
     37   private int dotRadius;
     38   private int dotsSeparation;
     39 
     40   private Paint activeDotPaintPortrait;
     41   private Paint inactiveDotPaintPortrait;
     42 
     43   private Path inactiveDotPath;
     44   private ValueAnimator transitionAnimator;
     45   private boolean useModeSwitchTransition;
     46 
     47   private float progress;
     48   private boolean toFirstPage;
     49   private boolean pageChanged;
     50 
     51   public InCallPaginator(Context context) {
     52     super(context);
     53     init(context);
     54   }
     55 
     56   public InCallPaginator(Context context, AttributeSet attrs) {
     57     super(context, attrs);
     58     init(context);
     59   }
     60 
     61   private void init(Context context) {
     62     dotRadius = getResources().getDimensionPixelSize(R.dimen.paginator_dot_radius);
     63     dotsSeparation = getResources().getDimensionPixelSize(R.dimen.paginator_dots_separation);
     64 
     65     int activeDotColor = context.getColor(R.color.paginator_dot);
     66     int inactiveDotColor = context.getColor(R.color.paginator_path);
     67     activeDotPaintPortrait = new Paint(Paint.ANTI_ALIAS_FLAG);
     68     activeDotPaintPortrait.setColor(activeDotColor);
     69     inactiveDotPaintPortrait = new Paint(Paint.ANTI_ALIAS_FLAG);
     70     inactiveDotPaintPortrait.setColor(inactiveDotColor);
     71 
     72     inactiveDotPath = new Path();
     73     transitionAnimator = ValueAnimator.ofFloat(0f, 1f);
     74     transitionAnimator.setInterpolator(null);
     75     transitionAnimator.setCurrentFraction(0f);
     76     transitionAnimator.addUpdateListener(animation -> invalidate());
     77   }
     78 
     79   @VisibleForTesting
     80   public void setProgress(float progress, boolean toFirstPage) {
     81     this.progress = progress;
     82     this.toFirstPage = toFirstPage;
     83 
     84     // Ensure the dot transition keeps up with the swipe progress.
     85     if (transitionAnimator.isStarted() && progress > transitionAnimator.getAnimatedFraction()) {
     86       transitionAnimator.setCurrentFraction(progress);
     87     }
     88 
     89     invalidate();
     90   }
     91 
     92   private void startTransition() {
     93     if (transitionAnimator.getAnimatedFraction() < 1f) {
     94       transitionAnimator.setCurrentFraction(progress);
     95       useModeSwitchTransition = false;
     96       transitionAnimator.cancel();
     97       transitionAnimator.start();
     98     }
     99   }
    100 
    101   private void endTransition(boolean snapBack) {
    102     if (transitionAnimator.getAnimatedFraction() > 0f) {
    103       useModeSwitchTransition = !snapBack;
    104       transitionAnimator.cancel();
    105       transitionAnimator.reverse();
    106     }
    107   }
    108 
    109   @Override
    110   public void onDraw(Canvas canvas) {
    111     super.onDraw(canvas);
    112 
    113     int centerX = getWidth() / 2;
    114     int centerY = getHeight() / 2;
    115 
    116     float transitionFraction = (float) transitionAnimator.getAnimatedValue();
    117 
    118     // Draw the inactive "dots".
    119     inactiveDotPath.reset();
    120     if (useModeSwitchTransition) {
    121       float trackWidth = 2 * dotRadius + transitionFraction * (2 * dotRadius + dotsSeparation);
    122       float indicatorRadius = dotRadius * (1f - 2f * Math.min(transitionFraction, 0.5f));
    123       float indicatorOffset = dotRadius + dotsSeparation / 2;
    124       if (toFirstPage) {
    125         float trackLeft = centerX - indicatorOffset - dotRadius;
    126         inactiveDotPath.addRoundRect(
    127             trackLeft,
    128             centerY - dotRadius,
    129             trackLeft + trackWidth,
    130             centerY + dotRadius,
    131             dotRadius,
    132             dotRadius,
    133             Path.Direction.CW);
    134         inactiveDotPath.addCircle(
    135             centerX + indicatorOffset, centerY, indicatorRadius, Path.Direction.CW);
    136       } else {
    137         float trackRight = centerX + indicatorOffset + dotRadius;
    138         inactiveDotPath.addRoundRect(
    139             trackRight - trackWidth,
    140             centerY - dotRadius,
    141             trackRight,
    142             centerY + dotRadius,
    143             dotRadius,
    144             dotRadius,
    145             Path.Direction.CW);
    146         inactiveDotPath.addCircle(
    147             centerX - indicatorOffset, centerY, indicatorRadius, Path.Direction.CW);
    148       }
    149     } else {
    150       float centerOffset = dotsSeparation / 2f;
    151       float innerOffset = centerOffset - transitionFraction * (dotRadius + centerOffset);
    152       float outerOffset = 2f * dotRadius + centerOffset;
    153       inactiveDotPath.addRoundRect(
    154           centerX - outerOffset,
    155           centerY - dotRadius,
    156           centerX - innerOffset,
    157           centerY + dotRadius,
    158           dotRadius,
    159           dotRadius,
    160           Path.Direction.CW);
    161       inactiveDotPath.addRoundRect(
    162           centerX + innerOffset,
    163           centerY - dotRadius,
    164           centerX + outerOffset,
    165           centerY + dotRadius,
    166           dotRadius,
    167           dotRadius,
    168           Path.Direction.CW);
    169     }
    170     Paint inactivePaint = inactiveDotPaintPortrait;
    171     canvas.drawPath(inactiveDotPath, inactivePaint);
    172 
    173     // Draw the white active dot.
    174     float activeDotOffset =
    175         (toFirstPage ? 1f - 2f * progress : 2f * progress - 1f) * (dotRadius + dotsSeparation / 2);
    176     Paint activePaint = activeDotPaintPortrait;
    177     canvas.drawCircle(centerX + activeDotOffset, centerY, dotRadius, activePaint);
    178   }
    179 
    180   public void setupWithViewPager(ViewPager pager) {
    181     Assert.checkArgument(pager.getAdapter().getCount() == 2, "Invalid page count.");
    182     pager.addOnPageChangeListener(this);
    183   }
    184 
    185   @Override
    186   public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    187     setProgress(positionOffset, position != 0);
    188   }
    189 
    190   @Override
    191   public void onPageSelected(int position) {
    192     pageChanged = true;
    193   }
    194 
    195   @Override
    196   public void onPageScrollStateChanged(int state) {
    197     switch (state) {
    198       case ViewPager.SCROLL_STATE_IDLE:
    199         endTransition(!pageChanged);
    200         pageChanged = false;
    201         break;
    202       case ViewPager.SCROLL_STATE_DRAGGING:
    203         startTransition();
    204         break;
    205       case ViewPager.SCROLL_STATE_SETTLING:
    206       default:
    207         break;
    208     }
    209   }
    210 }
    211