ListViewCompat.java revision 3228edfc4f8385dad5ae502f4a1d1343b9ec728f
1/* 2 * Copyright (C) 2014 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 android.support.v7.internal.widget; 18 19import android.content.Context; 20import android.graphics.Canvas; 21import android.graphics.Rect; 22import android.graphics.drawable.Drawable; 23import android.support.v4.graphics.drawable.DrawableCompat; 24import android.util.AttributeSet; 25import android.view.View; 26import android.view.ViewGroup; 27import android.widget.AbsListView; 28import android.widget.ListAdapter; 29import android.widget.ListView; 30 31import java.lang.reflect.Field; 32 33/** 34 * This class contains a number of useful things for ListView. Mainly used by 35 * {@link android.support.v7.widget.ListPopupWindow}. 36 * 37 * @hide 38 */ 39public class ListViewCompat extends ListView { 40 41 public static final int INVALID_POSITION = -1; 42 public static final int NO_POSITION = -1; 43 44 private static final int[] STATE_SET_NOTHING = new int[] { 0 }; 45 46 final Rect mSelectorRect = new Rect(); 47 int mSelectionLeftPadding = 0; 48 int mSelectionTopPadding = 0; 49 int mSelectionRightPadding = 0; 50 int mSelectionBottomPadding = 0; 51 52 private Field mIsChildViewEnabled; 53 54 public ListViewCompat(Context context) { 55 this(context, null); 56 } 57 58 public ListViewCompat(Context context, AttributeSet attrs) { 59 this(context, attrs, 0); 60 } 61 62 public ListViewCompat(Context context, AttributeSet attrs, int defStyleAttr) { 63 super(context, attrs, defStyleAttr); 64 65 try { 66 mIsChildViewEnabled = AbsListView.class.getDeclaredField("mIsChildViewEnabled"); 67 mIsChildViewEnabled.setAccessible(true); 68 } catch (NoSuchFieldException e) { 69 e.printStackTrace(); 70 } 71 } 72 73 @Override 74 public void setSelector(Drawable sel) { 75 super.setSelector(sel); 76 77 Rect padding = new Rect(); 78 sel.getPadding(padding); 79 mSelectionLeftPadding = padding.left; 80 mSelectionTopPadding = padding.top; 81 mSelectionRightPadding = padding.right; 82 mSelectionBottomPadding = padding.bottom; 83 } 84 85 @Override 86 protected void drawableStateChanged() { 87 super.drawableStateChanged(); 88 updateSelectorStateCompat(); 89 } 90 91 @Override 92 protected void dispatchDraw(Canvas canvas) { 93 final boolean drawSelectorOnTop = false; 94 if (!drawSelectorOnTop) { 95 drawSelectorCompat(canvas); 96 } 97 98 super.dispatchDraw(canvas); 99 100 if (drawSelectorOnTop) { 101 drawSelectorCompat(canvas); 102 } 103 } 104 105 protected void updateSelectorStateCompat() { 106 Drawable selector = getSelector(); 107 if (selector != null && shouldShowSelectorCompat()) { 108 selector.setState(getDrawableState()); 109 } 110 } 111 112 protected boolean shouldShowSelectorCompat() { 113 return touchModeDrawsInPressedStateCompat() && isPressed(); 114 } 115 116 protected boolean touchModeDrawsInPressedStateCompat() { 117 return false; 118 } 119 120 protected void drawSelectorCompat(Canvas canvas) { 121 if (!mSelectorRect.isEmpty()) { 122 final Drawable selector = getSelector(); 123 selector.setBounds(mSelectorRect); 124 selector.draw(canvas); 125 } 126 } 127 128 /** 129 * Find a position that can be selected (i.e., is not a separator). 130 * 131 * @param position The starting position to look at. 132 * @param lookDown Whether to look down for other positions. 133 * @return The next selectable position starting at position and then searching either up or 134 * down. Returns {@link #INVALID_POSITION} if nothing can be found. 135 */ 136 public int lookForSelectablePosition(int position, boolean lookDown) { 137 final ListAdapter adapter = getAdapter(); 138 if (adapter == null || isInTouchMode()) { 139 return INVALID_POSITION; 140 } 141 142 final int count = adapter.getCount(); 143 if (!getAdapter().areAllItemsEnabled()) { 144 if (lookDown) { 145 position = Math.max(0, position); 146 while (position < count && !adapter.isEnabled(position)) { 147 position++; 148 } 149 } else { 150 position = Math.min(position, count - 1); 151 while (position >= 0 && !adapter.isEnabled(position)) { 152 position--; 153 } 154 } 155 156 if (position < 0 || position >= count) { 157 return INVALID_POSITION; 158 } 159 return position; 160 } else { 161 if (position < 0 || position >= count) { 162 return INVALID_POSITION; 163 } 164 return position; 165 } 166 } 167 168 protected void positionSelectorLikeTouchCompat(int position, View sel, float x, float y) { 169 positionSelectorLikeFocusCompat(position, sel); 170 171 Drawable selector = getSelector(); 172 if (selector != null && position != INVALID_POSITION) { 173 DrawableCompat.setHotspot(selector, x, y); 174 } 175 } 176 177 protected void positionSelectorLikeFocusCompat(int position, View sel) { 178 // If we're changing position, update the visibility since the selector 179 // is technically being detached from the previous selection. 180 final Drawable selector = getSelector(); 181 final boolean manageState = selector != null && position != INVALID_POSITION; 182 if (manageState) { 183 selector.setVisible(false, false); 184 } 185 186 positionSelectorCompat(position, sel); 187 188 if (manageState) { 189 final Rect bounds = mSelectorRect; 190 final float x = bounds.exactCenterX(); 191 final float y = bounds.exactCenterY(); 192 selector.setVisible(getVisibility() == VISIBLE, false); 193 DrawableCompat.setHotspot(selector, x, y); 194 } 195 } 196 197 protected void positionSelectorCompat(int position, View sel) { 198 final Rect selectorRect = mSelectorRect; 199 selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom()); 200 201 // Adjust for selection padding. 202 selectorRect.left -= mSelectionLeftPadding; 203 selectorRect.top -= mSelectionTopPadding; 204 selectorRect.right += mSelectionRightPadding; 205 selectorRect.bottom += mSelectionBottomPadding; 206 207 try { 208 // AbsListView.mIsChildViewEnabled controls the selector's state so we need to 209 // modify it's value 210 final boolean isChildViewEnabled = mIsChildViewEnabled.getBoolean(this); 211 if (sel.isEnabled() != isChildViewEnabled) { 212 mIsChildViewEnabled.set(this, !isChildViewEnabled); 213 if (position != INVALID_POSITION) { 214 refreshDrawableState(); 215 } 216 } 217 } catch (IllegalAccessException e) { 218 e.printStackTrace(); 219 } 220 } 221 222 /** 223 * Measures the height of the given range of children (inclusive) and returns the height 224 * with this ListView's padding and divider heights included. If maxHeight is provided, the 225 * measuring will stop when the current height reaches maxHeight. 226 * 227 * @param widthMeasureSpec The width measure spec to be given to a child's 228 * {@link View#measure(int, int)}. 229 * @param startPosition The position of the first child to be shown. 230 * @param endPosition The (inclusive) position of the last child to be 231 * shown. Specify {@link #NO_POSITION} if the last child 232 * should be the last available child from the adapter. 233 * @param maxHeight The maximum height that will be returned (if all the 234 * children don't fit in this value, this value will be 235 * returned). 236 * @param disallowPartialChildPosition In general, whether the returned height should only 237 * contain entire children. This is more powerful--it is 238 * the first inclusive position at which partial 239 * children will not be allowed. Example: it looks nice 240 * to have at least 3 completely visible children, and 241 * in portrait this will most likely fit; but in 242 * landscape there could be times when even 2 children 243 * can not be completely shown, so a value of 2 244 * (remember, inclusive) would be good (assuming 245 * startPosition is 0). 246 * @return The height of this ListView with the given children. 247 */ 248 public int measureHeightOfChildrenCompat(int widthMeasureSpec, int startPosition, 249 int endPosition, final int maxHeight, 250 int disallowPartialChildPosition) { 251 252 final int paddingTop = getListPaddingTop(); 253 final int paddingBottom = getListPaddingBottom(); 254 final int paddingLeft = getListPaddingLeft(); 255 final int paddingRight = getListPaddingRight(); 256 final int reportedDividerHeight = getDividerHeight(); 257 final Drawable divider = getDivider(); 258 259 final ListAdapter adapter = getAdapter(); 260 261 if (adapter == null) { 262 return paddingTop + paddingBottom; 263 } 264 265 // Include the padding of the list 266 int returnedHeight = paddingTop + paddingBottom; 267 final int dividerHeight = ((reportedDividerHeight > 0) && divider != null) 268 ? reportedDividerHeight : 0; 269 270 // The previous height value that was less than maxHeight and contained 271 // no partial children 272 int prevHeightWithoutPartialChild = 0; 273 274 View child = null; 275 int viewType = 0; 276 int count = adapter.getCount(); 277 for (int i = 0; i < count; i++) { 278 int newType = adapter.getItemViewType(i); 279 if (newType != viewType) { 280 child = null; 281 viewType = newType; 282 } 283 child = adapter.getView(i, child, this); 284 285 // Compute child height spec 286 int heightMeasureSpec; 287 final ViewGroup.LayoutParams childLp = child.getLayoutParams(); 288 if (childLp != null && childLp.height > 0) { 289 heightMeasureSpec = MeasureSpec.makeMeasureSpec(childLp.height, 290 MeasureSpec.EXACTLY); 291 } else { 292 heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 293 } 294 child.measure(widthMeasureSpec, heightMeasureSpec); 295 296 if (i > 0) { 297 // Count the divider for all but one child 298 returnedHeight += dividerHeight; 299 } 300 301 returnedHeight += child.getMeasuredHeight(); 302 303 if (returnedHeight >= maxHeight) { 304 // We went over, figure out which height to return. If returnedHeight > 305 // maxHeight, then the i'th position did not fit completely. 306 return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1) 307 && (i > disallowPartialChildPosition) // We've past the min pos 308 && (prevHeightWithoutPartialChild > 0) // We have a prev height 309 && (returnedHeight != maxHeight) // i'th child did not fit completely 310 ? prevHeightWithoutPartialChild 311 : maxHeight; 312 } 313 314 if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) { 315 prevHeightWithoutPartialChild = returnedHeight; 316 } 317 } 318 319 // At this point, we went through the range of children, and they each 320 // completely fit, so return the returnedHeight 321 return returnedHeight; 322 } 323 324} 325