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 38 public class StateSet { 39 /** 40 * The order here is very important to 41 * {@link android.view.View#getDrawableState()} 42 */ 43 private static final int[][] VIEW_STATE_SETS; 44 45 /** @hide */ 46 public static final int VIEW_STATE_WINDOW_FOCUSED = 1; 47 /** @hide */ 48 public static final int VIEW_STATE_SELECTED = 1 << 1; 49 /** @hide */ 50 public static final int VIEW_STATE_FOCUSED = 1 << 2; 51 /** @hide */ 52 public static final int VIEW_STATE_ENABLED = 1 << 3; 53 /** @hide */ 54 public static final int VIEW_STATE_PRESSED = 1 << 4; 55 /** @hide */ 56 public static final int VIEW_STATE_ACTIVATED = 1 << 5; 57 /** @hide */ 58 public static final int VIEW_STATE_ACCELERATED = 1 << 6; 59 /** @hide */ 60 public static final int VIEW_STATE_HOVERED = 1 << 7; 61 /** @hide */ 62 public static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8; 63 /** @hide */ 64 public static final int VIEW_STATE_DRAG_HOVERED = 1 << 9; 65 66 static final int[] VIEW_STATE_IDS = new int[] { 67 R.attr.state_window_focused, VIEW_STATE_WINDOW_FOCUSED, 68 R.attr.state_selected, VIEW_STATE_SELECTED, 69 R.attr.state_focused, VIEW_STATE_FOCUSED, 70 R.attr.state_enabled, VIEW_STATE_ENABLED, 71 R.attr.state_pressed, VIEW_STATE_PRESSED, 72 R.attr.state_activated, VIEW_STATE_ACTIVATED, 73 R.attr.state_accelerated, VIEW_STATE_ACCELERATED, 74 R.attr.state_hovered, VIEW_STATE_HOVERED, 75 R.attr.state_drag_can_accept, VIEW_STATE_DRAG_CAN_ACCEPT, 76 R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED 77 }; 78 79 static { 80 if ((VIEW_STATE_IDS.length / 2) != R.styleable.ViewDrawableStates.length) { 81 throw new IllegalStateException( 82 "VIEW_STATE_IDs array length does not match ViewDrawableStates style array"); 83 } 84 85 final int[] orderedIds = new int[VIEW_STATE_IDS.length]; 86 for (int i = 0; i < R.styleable.ViewDrawableStates.length; i++) { 87 final int viewState = R.styleable.ViewDrawableStates[i]; 88 for (int j = 0; j < VIEW_STATE_IDS.length; j += 2) { 89 if (VIEW_STATE_IDS[j] == viewState) { 90 orderedIds[i * 2] = viewState; 91 orderedIds[i * 2 + 1] = VIEW_STATE_IDS[j + 1]; 92 } 93 } 94 } 95 96 final int NUM_BITS = VIEW_STATE_IDS.length / 2; 97 VIEW_STATE_SETS = new int[1 << NUM_BITS][]; 98 for (int i = 0; i < VIEW_STATE_SETS.length; i++) { 99 final int numBits = Integer.bitCount(i); 100 final int[] set = new int[numBits]; 101 int pos = 0; 102 for (int j = 0; j < orderedIds.length; j += 2) { 103 if ((i & orderedIds[j + 1]) != 0) { 104 set[pos++] = orderedIds[j]; 105 } 106 } 107 VIEW_STATE_SETS[i] = set; 108 } 109 } 110 111 /** @hide */ get(int mask)112 public static int[] get(int mask) { 113 if (mask >= VIEW_STATE_SETS.length) { 114 throw new IllegalArgumentException("Invalid state set mask"); 115 } 116 return VIEW_STATE_SETS[mask]; 117 } 118 119 /** @hide */ StateSet()120 public StateSet() {} 121 122 /** 123 * A state specification that will be matched by all StateSets. 124 */ 125 public static final int[] WILD_CARD = new int[0]; 126 127 /** 128 * A state set that does not contain any valid states. 129 */ 130 public static final int[] NOTHING = new int[] { 0 }; 131 132 /** 133 * Return whether the stateSetOrSpec is matched by all StateSets. 134 * 135 * @param stateSetOrSpec a state set or state spec. 136 */ isWildCard(int[] stateSetOrSpec)137 public static boolean isWildCard(int[] stateSetOrSpec) { 138 return stateSetOrSpec.length == 0 || stateSetOrSpec[0] == 0; 139 } 140 141 /** 142 * Return whether the stateSet matches the desired stateSpec. 143 * 144 * @param stateSpec an array of required (if positive) or 145 * prohibited (if negative) {@link android.view.View} states. 146 * @param stateSet an array of {@link android.view.View} states 147 */ stateSetMatches(int[] stateSpec, int[] stateSet)148 public static boolean stateSetMatches(int[] stateSpec, int[] stateSet) { 149 if (stateSet == null) { 150 return (stateSpec == null || isWildCard(stateSpec)); 151 } 152 int stateSpecSize = stateSpec.length; 153 int stateSetSize = stateSet.length; 154 for (int i = 0; i < stateSpecSize; i++) { 155 int stateSpecState = stateSpec[i]; 156 if (stateSpecState == 0) { 157 // We've reached the end of the cases to match against. 158 return true; 159 } 160 final boolean mustMatch; 161 if (stateSpecState > 0) { 162 mustMatch = true; 163 } else { 164 // We use negative values to indicate must-NOT-match states. 165 mustMatch = false; 166 stateSpecState = -stateSpecState; 167 } 168 boolean found = false; 169 for (int j = 0; j < stateSetSize; j++) { 170 final int state = stateSet[j]; 171 if (state == 0) { 172 // We've reached the end of states to match. 173 if (mustMatch) { 174 // We didn't find this must-match state. 175 return false; 176 } else { 177 // Continue checking other must-not-match states. 178 break; 179 } 180 } 181 if (state == stateSpecState) { 182 if (mustMatch) { 183 found = true; 184 // Continue checking other other must-match states. 185 break; 186 } else { 187 // Any match of a must-not-match state returns false. 188 return false; 189 } 190 } 191 } 192 if (mustMatch && !found) { 193 // We've reached the end of states to match and we didn't 194 // find a must-match state. 195 return false; 196 } 197 } 198 return true; 199 } 200 201 /** 202 * Return whether the state matches the desired stateSpec. 203 * 204 * @param stateSpec an array of required (if positive) or 205 * prohibited (if negative) {@link android.view.View} states. 206 * @param state a {@link android.view.View} state 207 */ stateSetMatches(int[] stateSpec, int state)208 public static boolean stateSetMatches(int[] stateSpec, int state) { 209 int stateSpecSize = stateSpec.length; 210 for (int i = 0; i < stateSpecSize; i++) { 211 int stateSpecState = stateSpec[i]; 212 if (stateSpecState == 0) { 213 // We've reached the end of the cases to match against. 214 return true; 215 } 216 if (stateSpecState > 0) { 217 if (state != stateSpecState) { 218 return false; 219 } 220 } else { 221 // We use negative values to indicate must-NOT-match states. 222 if (state == -stateSpecState) { 223 // We matched a must-not-match case. 224 return false; 225 } 226 } 227 } 228 return true; 229 } 230 trimStateSet(int[] states, int newSize)231 public static int[] trimStateSet(int[] states, int newSize) { 232 if (states.length == newSize) { 233 return states; 234 } 235 236 int[] trimmedStates = new int[newSize]; 237 System.arraycopy(states, 0, trimmedStates, 0, newSize); 238 return trimmedStates; 239 } 240 dump(int[] states)241 public static String dump(int[] states) { 242 StringBuilder sb = new StringBuilder(); 243 244 int count = states.length; 245 for (int i = 0; i < count; i++) { 246 247 switch (states[i]) { 248 case R.attr.state_window_focused: 249 sb.append("W "); 250 break; 251 case R.attr.state_pressed: 252 sb.append("P "); 253 break; 254 case R.attr.state_selected: 255 sb.append("S "); 256 break; 257 case R.attr.state_focused: 258 sb.append("F "); 259 break; 260 case R.attr.state_enabled: 261 sb.append("E "); 262 break; 263 } 264 } 265 266 return sb.toString(); 267 } 268 } 269