1 /* 2 * Copyright (C) 2007 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 android.util; 18 19 import com.android.internal.R; 20 21 /** 22 * State sets are arrays of positive ints where each element 23 * represents the state of a {@link android.view.View} (e.g. focused, 24 * selected, visible, etc.). A {@link android.view.View} may be in 25 * one or more of those states. 26 * 27 * A state spec is an array of signed ints where each element 28 * represents a required (if positive) or an undesired (if negative) 29 * {@link android.view.View} state. 30 * 31 * Utils dealing with state sets. 32 * 33 * In theory we could encapsulate the state set and state spec arrays 34 * and not have static methods here but there is some concern about 35 * performance since these methods are called during view drawing. 36 */ 37 public class StateSet { 38 /** 39 * The order here is very important to 40 * {@link android.view.View#getDrawableState()} 41 */ 42 private static final int[][] VIEW_STATE_SETS; 43 44 /** @hide */ 45 public static final int VIEW_STATE_WINDOW_FOCUSED = 1; 46 /** @hide */ 47 public static final int VIEW_STATE_SELECTED = 1 << 1; 48 /** @hide */ 49 public static final int VIEW_STATE_FOCUSED = 1 << 2; 50 /** @hide */ 51 public static final int VIEW_STATE_ENABLED = 1 << 3; 52 /** @hide */ 53 public static final int VIEW_STATE_PRESSED = 1 << 4; 54 /** @hide */ 55 public static final int VIEW_STATE_ACTIVATED = 1 << 5; 56 /** @hide */ 57 public static final int VIEW_STATE_ACCELERATED = 1 << 6; 58 /** @hide */ 59 public static final int VIEW_STATE_HOVERED = 1 << 7; 60 /** @hide */ 61 public static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8; 62 /** @hide */ 63 public static final int VIEW_STATE_DRAG_HOVERED = 1 << 9; 64 65 static final int[] VIEW_STATE_IDS = new int[] { 66 R.attr.state_window_focused, VIEW_STATE_WINDOW_FOCUSED, 67 R.attr.state_selected, VIEW_STATE_SELECTED, 68 R.attr.state_focused, VIEW_STATE_FOCUSED, 69 R.attr.state_enabled, VIEW_STATE_ENABLED, 70 R.attr.state_pressed, VIEW_STATE_PRESSED, 71 R.attr.state_activated, VIEW_STATE_ACTIVATED, 72 R.attr.state_accelerated, VIEW_STATE_ACCELERATED, 73 R.attr.state_hovered, VIEW_STATE_HOVERED, 74 R.attr.state_drag_can_accept, VIEW_STATE_DRAG_CAN_ACCEPT, 75 R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED 76 }; 77 78 static { 79 if ((VIEW_STATE_IDS.length / 2) != R.styleable.ViewDrawableStates.length) { 80 throw new IllegalStateException( 81 "VIEW_STATE_IDs array length does not match ViewDrawableStates style array"); 82 } 83 84 final int[] orderedIds = new int[VIEW_STATE_IDS.length]; 85 for (int i = 0; i < R.styleable.ViewDrawableStates.length; i++) { 86 final int viewState = R.styleable.ViewDrawableStates[i]; 87 for (int j = 0; j < VIEW_STATE_IDS.length; j += 2) { 88 if (VIEW_STATE_IDS[j] == viewState) { 89 orderedIds[i * 2] = viewState; 90 orderedIds[i * 2 + 1] = VIEW_STATE_IDS[j + 1]; 91 } 92 } 93 } 94 95 final int NUM_BITS = VIEW_STATE_IDS.length / 2; 96 VIEW_STATE_SETS = new int[1 << NUM_BITS][]; 97 for (int i = 0; i < VIEW_STATE_SETS.length; i++) { 98 final int numBits = Integer.bitCount(i); 99 final int[] set = new int[numBits]; 100 int pos = 0; 101 for (int j = 0; j < orderedIds.length; j += 2) { 102 if ((i & orderedIds[j + 1]) != 0) { 103 set[pos++] = orderedIds[j]; 104 } 105 } 106 VIEW_STATE_SETS[i] = set; 107 } 108 } 109 110 /** @hide */ get(int mask)111 public static int[] get(int mask) { 112 if (mask >= VIEW_STATE_SETS.length) { 113 throw new IllegalArgumentException("Invalid state set mask"); 114 } 115 return VIEW_STATE_SETS[mask]; 116 } 117 118 /** @hide */ StateSet()119 public StateSet() {} 120 121 /** 122 * A state specification that will be matched by all StateSets. 123 */ 124 public static final int[] WILD_CARD = new int[0]; 125 126 /** 127 * A state set that does not contain any valid states. 128 */ 129 public static final int[] NOTHING = new int[] { 0 }; 130 131 /** 132 * Return whether the stateSetOrSpec is matched by all StateSets. 133 * 134 * @param stateSetOrSpec a state set or state spec. 135 */ isWildCard(int[] stateSetOrSpec)136 public static boolean isWildCard(int[] stateSetOrSpec) { 137 return stateSetOrSpec.length == 0 || stateSetOrSpec[0] == 0; 138 } 139 140 /** 141 * Return whether the stateSet matches the desired stateSpec. 142 * 143 * @param stateSpec an array of required (if positive) or 144 * prohibited (if negative) {@link android.view.View} states. 145 * @param stateSet an array of {@link android.view.View} states 146 */ stateSetMatches(int[] stateSpec, int[] stateSet)147 public static boolean stateSetMatches(int[] stateSpec, int[] stateSet) { 148 if (stateSet == null) { 149 return (stateSpec == null || isWildCard(stateSpec)); 150 } 151 int stateSpecSize = stateSpec.length; 152 int stateSetSize = stateSet.length; 153 for (int i = 0; i < stateSpecSize; i++) { 154 int stateSpecState = stateSpec[i]; 155 if (stateSpecState == 0) { 156 // We've reached the end of the cases to match against. 157 return true; 158 } 159 final boolean mustMatch; 160 if (stateSpecState > 0) { 161 mustMatch = true; 162 } else { 163 // We use negative values to indicate must-NOT-match states. 164 mustMatch = false; 165 stateSpecState = -stateSpecState; 166 } 167 boolean found = false; 168 for (int j = 0; j < stateSetSize; j++) { 169 final int state = stateSet[j]; 170 if (state == 0) { 171 // We've reached the end of states to match. 172 if (mustMatch) { 173 // We didn't find this must-match state. 174 return false; 175 } else { 176 // Continue checking other must-not-match states. 177 break; 178 } 179 } 180 if (state == stateSpecState) { 181 if (mustMatch) { 182 found = true; 183 // Continue checking other other must-match states. 184 break; 185 } else { 186 // Any match of a must-not-match state returns false. 187 return false; 188 } 189 } 190 } 191 if (mustMatch && !found) { 192 // We've reached the end of states to match and we didn't 193 // find a must-match state. 194 return false; 195 } 196 } 197 return true; 198 } 199 200 /** 201 * Return whether the state matches the desired stateSpec. 202 * 203 * @param stateSpec an array of required (if positive) or 204 * prohibited (if negative) {@link android.view.View} states. 205 * @param state a {@link android.view.View} state 206 */ stateSetMatches(int[] stateSpec, int state)207 public static boolean stateSetMatches(int[] stateSpec, int state) { 208 int stateSpecSize = stateSpec.length; 209 for (int i = 0; i < stateSpecSize; i++) { 210 int stateSpecState = stateSpec[i]; 211 if (stateSpecState == 0) { 212 // We've reached the end of the cases to match against. 213 return true; 214 } 215 if (stateSpecState > 0) { 216 if (state != stateSpecState) { 217 return false; 218 } 219 } else { 220 // We use negative values to indicate must-NOT-match states. 221 if (state == -stateSpecState) { 222 // We matched a must-not-match case. 223 return false; 224 } 225 } 226 } 227 return true; 228 } 229 230 /** 231 * Check whether a list of state specs has an attribute specified. 232 * @param stateSpecs a list of state specs we're checking. 233 * @param attr an attribute we're looking for. 234 * @return {@code true} if the attribute is contained in the state specs. 235 * @hide 236 */ containsAttribute(int[][] stateSpecs, int attr)237 public static boolean containsAttribute(int[][] stateSpecs, int attr) { 238 if (stateSpecs != null) { 239 for (int[] spec : stateSpecs) { 240 if (spec == null) { 241 break; 242 } 243 for (int specAttr : spec) { 244 if (specAttr == attr || -specAttr == attr) { 245 return true; 246 } 247 } 248 } 249 } 250 return false; 251 } 252 trimStateSet(int[] states, int newSize)253 public static int[] trimStateSet(int[] states, int newSize) { 254 if (states.length == newSize) { 255 return states; 256 } 257 258 int[] trimmedStates = new int[newSize]; 259 System.arraycopy(states, 0, trimmedStates, 0, newSize); 260 return trimmedStates; 261 } 262 dump(int[] states)263 public static String dump(int[] states) { 264 StringBuilder sb = new StringBuilder(); 265 266 int count = states.length; 267 for (int i = 0; i < count; i++) { 268 269 switch (states[i]) { 270 case R.attr.state_window_focused: 271 sb.append("W "); 272 break; 273 case R.attr.state_pressed: 274 sb.append("P "); 275 break; 276 case R.attr.state_selected: 277 sb.append("S "); 278 break; 279 case R.attr.state_focused: 280 sb.append("F "); 281 break; 282 case R.attr.state_enabled: 283 sb.append("E "); 284 break; 285 case R.attr.state_checked: 286 sb.append("C "); 287 break; 288 case R.attr.state_activated: 289 sb.append("A "); 290 break; 291 } 292 } 293 294 return sb.toString(); 295 } 296 } 297