DividerSnapAlgorithm.java revision 737af724eb31f513386e91ee5510cc6991350937
1/* 2 * Copyright (C) 2015 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 17package com.android.internal.policy; 18 19import android.content.Context; 20import android.content.res.Resources; 21import android.graphics.Rect; 22 23import java.util.ArrayList; 24 25/** 26 * Calculates the snap targets and the snap position given a position and a velocity. All positions 27 * here are to be interpreted as the left/top edge of the divider rectangle. 28 * 29 * @hide 30 */ 31public class DividerSnapAlgorithm { 32 33 /** 34 * 3 snap targets: left/top has 16:9 ratio (for videos), 1:1, and right/bottom has 16:9 ratio 35 */ 36 private static final int SNAP_MODE_16_9 = 0; 37 38 /** 39 * 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio) 40 */ 41 private static final int SNAP_FIXED_RATIO = 1; 42 43 /** 44 * 1 snap target: 1:1 45 */ 46 private static final int SNAP_ONLY_1_1 = 2; 47 48 private final float mMinFlingVelocityPxPerSecond; 49 private final int mDisplayWidth; 50 private final int mDisplayHeight; 51 private final int mDividerSize; 52 private final ArrayList<SnapTarget> mTargets = new ArrayList<>(); 53 private final Rect mInsets = new Rect(); 54 private final int mSnapMode; 55 private final float mFixedRatio; 56 57 /** The first target which is still splitting the screen */ 58 private final SnapTarget mFirstSplitTarget; 59 60 /** The last target which is still splitting the screen */ 61 private final SnapTarget mLastSplitTarget; 62 63 private final SnapTarget mDismissStartTarget; 64 private final SnapTarget mDismissEndTarget; 65 66 public DividerSnapAlgorithm(Resources res, float minFlingVelocityPxPerSecond, 67 int displayWidth, int displayHeight, int dividerSize, boolean isHorizontalDivision, 68 Rect insets) { 69 mMinFlingVelocityPxPerSecond = minFlingVelocityPxPerSecond; 70 mDividerSize = dividerSize; 71 mDisplayWidth = displayWidth; 72 mDisplayHeight = displayHeight; 73 mInsets.set(insets); 74 mSnapMode = res.getInteger( 75 com.android.internal.R.integer.config_dockedStackDividerSnapMode); 76 mFixedRatio = res.getFraction( 77 com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1); 78 calculateTargets(isHorizontalDivision); 79 mFirstSplitTarget = mTargets.get(1); 80 mLastSplitTarget = mTargets.get(mTargets.size() - 2); 81 mDismissStartTarget = mTargets.get(0); 82 mDismissEndTarget = mTargets.get(mTargets.size() - 1); 83 } 84 85 public SnapTarget calculateSnapTarget(int position, float velocity) { 86 if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) { 87 return snap(position); 88 } 89 if (position < mFirstSplitTarget.position && velocity < 0) { 90 return mDismissStartTarget; 91 } 92 if (position > mLastSplitTarget.position && velocity > 0) { 93 return mDismissEndTarget; 94 } 95 if (velocity < 0) { 96 return mFirstSplitTarget; 97 } else { 98 return mLastSplitTarget; 99 } 100 } 101 102 public SnapTarget calculateNonDismissingSnapTarget(int position) { 103 SnapTarget target = snap(position); 104 if (target == mDismissStartTarget) { 105 return mFirstSplitTarget; 106 } else if (target == mDismissEndTarget) { 107 return mLastSplitTarget; 108 } else { 109 return target; 110 } 111 } 112 113 public float calculateDismissingFraction(int position) { 114 if (position < mFirstSplitTarget.position) { 115 return 1f - (float) position / mFirstSplitTarget.position; 116 } else if (position > mLastSplitTarget.position) { 117 return (float) (position - mLastSplitTarget.position) 118 / (mDismissEndTarget.position - mLastSplitTarget.position); 119 } 120 return 0f; 121 } 122 123 public SnapTarget getClosestDismissTarget(int position) { 124 if (position - mDismissStartTarget.position < mDismissEndTarget.position - position) { 125 return mDismissStartTarget; 126 } else { 127 return mDismissEndTarget; 128 } 129 } 130 131 public SnapTarget getFirstSplitTarget() { 132 return mFirstSplitTarget; 133 } 134 135 public SnapTarget getLastSplitTarget() { 136 return mLastSplitTarget; 137 } 138 139 public SnapTarget getDismissStartTarget() { 140 return mDismissStartTarget; 141 } 142 143 public SnapTarget getDismissEndTarget() { 144 return mDismissEndTarget; 145 } 146 147 private SnapTarget snap(int position) { 148 int minIndex = -1; 149 int minDistance = Integer.MAX_VALUE; 150 int size = mTargets.size(); 151 for (int i = 0; i < size; i++) { 152 int distance = Math.abs(position - mTargets.get(i).position); 153 if (distance < minDistance) { 154 minIndex = i; 155 minDistance = distance; 156 } 157 } 158 return mTargets.get(minIndex); 159 } 160 161 private void calculateTargets(boolean isHorizontalDivision) { 162 mTargets.clear(); 163 int dividerMax = isHorizontalDivision 164 ? mDisplayHeight 165 : mDisplayWidth; 166 mTargets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START)); 167 switch (mSnapMode) { 168 case SNAP_MODE_16_9: 169 addRatio16_9Targets(isHorizontalDivision); 170 break; 171 case SNAP_FIXED_RATIO: 172 addFixedDivisionTargets(isHorizontalDivision); 173 break; 174 case SNAP_ONLY_1_1: 175 addMiddleTarget(isHorizontalDivision); 176 break; 177 } 178 mTargets.add(new SnapTarget(dividerMax, SnapTarget.FLAG_DISMISS_END)); 179 } 180 181 private void addFixedDivisionTargets(boolean isHorizontalDivision) { 182 int start = isHorizontalDivision ? mInsets.top : mInsets.left; 183 int end = isHorizontalDivision 184 ? mDisplayHeight - mInsets.bottom 185 : mDisplayWidth - mInsets.right; 186 mTargets.add(new SnapTarget((int) (start + mFixedRatio * (end - start)) - mDividerSize / 2, 187 SnapTarget.FLAG_NONE)); 188 addMiddleTarget(isHorizontalDivision); 189 mTargets.add(new SnapTarget((int) (start + (1 - mFixedRatio) * (end - start)) 190 - mDividerSize / 2, SnapTarget.FLAG_NONE)); 191 } 192 193 private void addRatio16_9Targets(boolean isHorizontalDivision) { 194 int start = isHorizontalDivision ? mInsets.top : mInsets.left; 195 int end = isHorizontalDivision 196 ? mDisplayHeight - mInsets.bottom 197 : mDisplayWidth - mInsets.right; 198 int startOther = isHorizontalDivision ? mInsets.left : mInsets.top; 199 int endOther = isHorizontalDivision 200 ? mDisplayWidth - mInsets.right 201 : mDisplayHeight - mInsets.bottom; 202 float size = 9.0f / 16.0f * (endOther - startOther); 203 int sizeInt = (int) Math.floor(size); 204 mTargets.add(new SnapTarget(start + sizeInt, SnapTarget.FLAG_NONE)); 205 addMiddleTarget(isHorizontalDivision); 206 mTargets.add(new SnapTarget(end - sizeInt - mDividerSize, SnapTarget.FLAG_NONE)); 207 } 208 209 private void addMiddleTarget(boolean isHorizontalDivision) { 210 int start = isHorizontalDivision ? mInsets.top : mInsets.left; 211 int end = isHorizontalDivision 212 ? mDisplayHeight - mInsets.bottom 213 : mDisplayWidth - mInsets.right; 214 mTargets.add(new SnapTarget(start + (end - start) / 2 - mDividerSize / 2, 215 SnapTarget.FLAG_NONE)); 216 } 217 218 /** 219 * Represents a snap target for the divider. 220 */ 221 public static class SnapTarget { 222 public static final int FLAG_NONE = 0; 223 224 /** If the divider reaches this value, the left/top task should be dismissed. */ 225 public static final int FLAG_DISMISS_START = 1; 226 227 /** If the divider reaches this value, the right/bottom task should be dismissed */ 228 public static final int FLAG_DISMISS_END = 2; 229 230 public final int position; 231 public final int flag; 232 233 public SnapTarget(int position, int flag) { 234 this.position = position; 235 this.flag = flag; 236 } 237 } 238} 239