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 InCallPaginator(Context context)51 public InCallPaginator(Context context) { 52 super(context); 53 init(context); 54 } 55 InCallPaginator(Context context, AttributeSet attrs)56 public InCallPaginator(Context context, AttributeSet attrs) { 57 super(context, attrs); 58 init(context); 59 } 60 init(Context context)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 setProgress(float progress, boolean toFirstPage)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 startTransition()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 endTransition(boolean snapBack)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 onDraw(Canvas canvas)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 setupWithViewPager(ViewPager pager)180 public void setupWithViewPager(ViewPager pager) { 181 Assert.checkArgument(pager.getAdapter().getCount() == 2, "Invalid page count."); 182 pager.addOnPageChangeListener(this); 183 } 184 185 @Override onPageScrolled(int position, float positionOffset, int positionOffsetPixels)186 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 187 setProgress(positionOffset, position != 0); 188 } 189 190 @Override onPageSelected(int position)191 public void onPageSelected(int position) { 192 pageChanged = true; 193 } 194 195 @Override onPageScrollStateChanged(int state)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