143 | * Implementers can call getItemAtPosition(position) if they need to
144 | * access the data associated with the selected item.
145 | *
146 | * @param parent
147 | * The LinearListView where the click happened.
148 | * @param view
149 | * The view within the LinearListView that was clicked (this
150 | * will be a view provided by the adapter)
151 | * @param position
152 | * The position of the view in the adapter.
153 | * @param id
154 | * The row id of the item that was clicked.
155 | */
156 | void onItemClick(LinearListView parent, View view, int position, long id);
157 | }
158 |
159 | /**
160 | * Register a callback to be invoked when an item in this LinearListView has
161 | * been clicked.
162 | *
163 | * @param listener
164 | * The callback that will be invoked.
165 | */
166 | public void setOnItemClickListener(OnItemClickListener listener) {
167 | mOnItemClickListener = listener;
168 | }
169 |
170 | /**
171 | * @return The callback to be invoked with an item in this LinearListView has
172 | * been clicked, or null id no callback has been set.
173 | */
174 | public final OnItemClickListener getOnItemClickListener() {
175 | return mOnItemClickListener;
176 | }
177 |
178 | /**
179 | * Call the OnItemClickListener, if it is defined.
180 | *
181 | * @param view
182 | * The view within the LinearListView that was clicked.
183 | * @param position
184 | * The position of the view in the adapter.
185 | * @param id
186 | * The row id of the item that was clicked.
187 | * @return True if there was an assigned OnItemClickListener that was
188 | * called, false otherwise is returned.
189 | */
190 | public boolean performItemClick(View view, int position, long id) {
191 | if (mOnItemClickListener != null) {
192 | playSoundEffect(SoundEffectConstants.CLICK);
193 | mOnItemClickListener.onItemClick(this, view, position, id);
194 | return true;
195 | }
196 |
197 | return false;
198 | }
199 |
200 | /**
201 | * Sets the view to show if the adapter is empty
202 | */
203 | public void setEmptyView(View emptyView) {
204 | mEmptyView = emptyView;
205 |
206 | final ListAdapter adapter = getAdapter();
207 | final boolean empty = ((adapter == null) || adapter.isEmpty());
208 | updateEmptyStatus(empty);
209 | }
210 |
211 | /**
212 | * When the current adapter is empty, the LinearListView can display a special
213 | * view call the empty view. The empty view is used to provide feedback to
214 | * the user that no data is available in this LinearListView.
215 | *
216 | * @return The view to show if the adapter is empty.
217 | */
218 | public View getEmptyView() {
219 | return mEmptyView;
220 | }
221 |
222 | /**
223 | * Update the status of the list based on the empty parameter. If empty is
224 | * true and we have an empty view, display it. In all the other cases, make
225 | * sure that the layout is VISIBLE and that the empty view is GONE (if
226 | * it's not null).
227 | */
228 | private void updateEmptyStatus(boolean empty) {
229 | if (empty) {
230 | if (mEmptyView != null) {
231 | mEmptyView.setVisibility(View.VISIBLE);
232 | setVisibility(View.GONE);
233 | } else {
234 | // If the caller just removed our empty view, make sure the list
235 | // view is visible
236 | setVisibility(View.VISIBLE);
237 | }
238 | } else {
239 | if (mEmptyView != null)
240 | mEmptyView.setVisibility(View.GONE);
241 | setVisibility(View.VISIBLE);
242 | }
243 | }
244 |
245 | private void setupChildren() {
246 |
247 | removeAllViews();
248 |
249 | updateEmptyStatus((mAdapter == null) || mAdapter.isEmpty());
250 |
251 | if (mAdapter == null) {
252 | return;
253 | }
254 |
255 | for (int i = 0; i < mAdapter.getCount(); i++) {
256 | View child = mAdapter.getView(i, null, this);
257 | if (mAreAllItemsSelectable || mAdapter.isEnabled(i)) {
258 | child.setOnClickListener(new InternalOnClickListener(i));
259 | }
260 | addViewInLayout(child, -1, child.getLayoutParams(), true);
261 | }
262 | }
263 |
264 | /**
265 | * Internal OnClickListener that this view associate of each of its children
266 | * so that they can respond to OnItemClick listener's events. Avoid setting
267 | * an OnClickListener manually. If you need it you can wrap the child in a
268 | * simple {@link android.widget.FrameLayout}.
269 | */
270 | private class InternalOnClickListener implements OnClickListener {
271 |
272 | int mPosition;
273 |
274 | public InternalOnClickListener(int position) {
275 | mPosition = position;
276 | }
277 |
278 | @Override
279 | public void onClick(View v) {
280 | if ((mOnItemClickListener != null) && (mAdapter != null)) {
281 | mOnItemClickListener.onItemClick(LinearListView.this, v,
282 | mPosition, mAdapter.getItemId(mPosition));
283 | }
284 | }
285 | }
286 | }
287 |
--------------------------------------------------------------------------------
/linearlistview/src/main/java/com/linearlistview/internal/IcsLinearLayout.java:
--------------------------------------------------------------------------------
1 | package com.linearlistview.internal;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.drawable.ColorDrawable;
7 | import android.graphics.drawable.Drawable;
8 | import android.os.Build;
9 | import android.util.AttributeSet;
10 | import android.view.View;
11 | import android.widget.LinearLayout;
12 |
13 | /**
14 | * A simple extension of a regular linear layout that supports the divider API
15 | * of Android 4.0+. The dividers are added adjacent to the children by changing
16 | * their layout params. If you need to rely on the margins which fall in the
17 | * same orientation as the layout you should wrap the child in a simple
18 | * {@link android.widget.FrameLayout} so it can receive the margin.
19 | */
20 | public class IcsLinearLayout extends LinearLayout {
21 | private static final int[] R_styleable_LinearLayout = new int[] {
22 | /* 0 */ android.R.attr.divider,
23 | /* 1 */ android.R.attr.measureWithLargestChild,
24 | /* 2 */ android.R.attr.showDividers,
25 | /* 3 */ android.R.attr.dividerPadding,
26 | };
27 | private static final int LinearLayout_divider = 0;
28 | private static final int LinearLayout_measureWithLargestChild = 1;
29 | private static final int LinearLayout_showDividers = 2;
30 | private static final int LinearLayout_dividerPadding = 3;
31 |
32 | /**
33 | * Don't show any dividers.
34 | */
35 | public static final int SHOW_DIVIDER_NONE = 0;
36 | /**
37 | * Show a divider at the beginning of the group.
38 | */
39 | public static final int SHOW_DIVIDER_BEGINNING = 1;
40 | /**
41 | * Show dividers between each item in the group.
42 | */
43 | public static final int SHOW_DIVIDER_MIDDLE = 2;
44 | /**
45 | * Show a divider at the end of the group.
46 | */
47 | public static final int SHOW_DIVIDER_END = 4;
48 |
49 |
50 | private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
51 |
52 |
53 | private Drawable mDivider;
54 | protected int mDividerWidth;
55 | protected int mDividerHeight;
56 | private int mShowDividers;
57 | private int mDividerPadding;
58 | private boolean mClipDivider;
59 |
60 | private boolean mUseLargestChild;
61 |
62 | public IcsLinearLayout(Context context, AttributeSet attrs) {
63 | super(context, attrs);
64 |
65 | TypedArray a = context.obtainStyledAttributes(attrs, /*com.android.internal.R.styleable.*/R_styleable_LinearLayout);
66 |
67 | setDividerDrawable(a.getDrawable(/*com.android.internal.R.styleable.*/LinearLayout_divider));
68 | mShowDividers = a.getInt(/*com.android.internal.R.styleable.*/LinearLayout_showDividers, SHOW_DIVIDER_NONE);
69 | mDividerPadding = a.getDimensionPixelSize(/*com.android.internal.R.styleable.*/LinearLayout_dividerPadding, 0);
70 | mUseLargestChild = a.getBoolean(/*com.android.internal.R.styleable.*/LinearLayout_measureWithLargestChild, false);
71 |
72 | a.recycle();
73 | }
74 |
75 | /**
76 | * Set how dividers should be shown between items in this layout
77 | *
78 | * @param showDividers One or more of {@link #SHOW_DIVIDER_BEGINNING},
79 | * {@link #SHOW_DIVIDER_MIDDLE}, or {@link #SHOW_DIVIDER_END},
80 | * or {@link #SHOW_DIVIDER_NONE} to show no dividers.
81 | */
82 | public void setShowDividers(int showDividers) {
83 | if (showDividers != mShowDividers) {
84 | requestLayout();
85 | invalidate(); //XXX This is required if you are toggling a divider off
86 | }
87 | mShowDividers = showDividers;
88 | }
89 |
90 | /**
91 | * @return A flag set indicating how dividers should be shown around items.
92 | * @see #setShowDividers(int)
93 | */
94 | public int getShowDividers() {
95 | return mShowDividers;
96 | }
97 |
98 | /**
99 | * Set a drawable to be used as a divider between items.
100 | * @param divider Drawable that will divide each item.
101 | * @see #setShowDividers(int)
102 | */
103 | public void setDividerDrawable(Drawable divider) {
104 | if (divider == mDivider) {
105 | return;
106 | }
107 | mDivider = divider;
108 | mClipDivider = divider instanceof ColorDrawable;
109 | if (divider != null) {
110 | mDividerWidth = divider.getIntrinsicWidth();
111 | mDividerHeight = divider.getIntrinsicHeight();
112 | } else {
113 | mDividerWidth = 0;
114 | mDividerHeight = 0;
115 | }
116 | setWillNotDraw(divider == null);
117 | requestLayout();
118 | }
119 |
120 | /**
121 | * Set padding displayed on both ends of dividers.
122 | *
123 | * @param padding Padding value in pixels that will be applied to each end
124 | *
125 | * @see #setShowDividers(int)
126 | * @see #setDividerDrawable(android.graphics.drawable.Drawable)
127 | * @see #getDividerPadding()
128 | */
129 | public void setDividerPadding(int padding) {
130 | mDividerPadding = padding;
131 | }
132 |
133 | /**
134 | * Get the padding size used to inset dividers in pixels
135 | *
136 | * @see #setShowDividers(int)
137 | * @see #setDividerDrawable(android.graphics.drawable.Drawable)
138 | * @see #setDividerPadding(int)
139 | */
140 | public int getDividerPadding() {
141 | return mDividerPadding;
142 | }
143 |
144 | /**
145 | * Get the width of the current divider drawable.
146 | *
147 | * @hide Used internally by framework.
148 | */
149 | public int getDividerWidth() {
150 | return mDividerWidth;
151 | }
152 |
153 | @Override
154 | protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
155 | final int index = indexOfChild(child);
156 | final int orientation = getOrientation();
157 | final LayoutParams params = (LayoutParams) child.getLayoutParams();
158 | if (hasDividerBeforeChildAt(index)) {
159 | if (orientation == VERTICAL) {
160 | //Account for the divider by pushing everything up
161 | params.topMargin = mDividerHeight;
162 | } else {
163 | //Account for the divider by pushing everything left
164 | params.leftMargin = mDividerWidth;
165 | }
166 | }
167 |
168 | final int count = getChildCount();
169 | if (index == count - 1) {
170 | if (hasDividerBeforeChildAt(count)) {
171 | if (orientation == VERTICAL) {
172 | params.bottomMargin = mDividerHeight;
173 | } else {
174 | params.rightMargin = mDividerWidth;
175 | }
176 | }
177 | }
178 | super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
179 | }
180 |
181 | @Override
182 | protected void onDraw(Canvas canvas) {
183 | if (mDivider != null) {
184 | if (getOrientation() == VERTICAL) {
185 | drawDividersVertical(canvas);
186 | } else {
187 | drawDividersHorizontal(canvas);
188 | }
189 | }
190 | super.onDraw(canvas);
191 | }
192 |
193 | void drawDividersVertical(Canvas canvas) {
194 | final int count = getChildCount();
195 | for (int i = 0; i < count; i++) {
196 | final View child = getChildAt(i);
197 |
198 | if (child != null && child.getVisibility() != GONE) {
199 | if (hasDividerBeforeChildAt(i)) {
200 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
201 | final int top = child.getTop() - lp.topMargin/* - mDividerHeight*/;
202 | drawHorizontalDivider(canvas, top);
203 | }
204 | }
205 | }
206 |
207 | if (hasDividerBeforeChildAt(count)) {
208 | final View child = getChildAt(count - 1);
209 | int bottom = 0;
210 | if (child == null) {
211 | bottom = getHeight() - getPaddingBottom() - mDividerHeight;
212 | } else {
213 | //final LayoutParams lp = (LayoutParams) child.getLayoutParams();
214 | bottom = child.getBottom()/* + lp.bottomMargin*/;
215 | }
216 | drawHorizontalDivider(canvas, bottom);
217 | }
218 | }
219 |
220 | void drawDividersHorizontal(Canvas canvas) {
221 | final int count = getChildCount();
222 | for (int i = 0; i < count; i++) {
223 | final View child = getChildAt(i);
224 |
225 | if (child != null && child.getVisibility() != GONE) {
226 | if (hasDividerBeforeChildAt(i)) {
227 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
228 | final int left = child.getLeft() - lp.leftMargin/* - mDividerWidth*/;
229 | drawVerticalDivider(canvas, left);
230 | }
231 | }
232 | }
233 |
234 | if (hasDividerBeforeChildAt(count)) {
235 | final View child = getChildAt(count - 1);
236 | int right = 0;
237 | if (child == null) {
238 | right = getWidth() - getPaddingRight() - mDividerWidth;
239 | } else {
240 | //final LayoutParams lp = (LayoutParams) child.getLayoutParams();
241 | right = child.getRight()/* + lp.rightMargin*/;
242 | }
243 | drawVerticalDivider(canvas, right);
244 | }
245 | }
246 |
247 | void drawHorizontalDivider(Canvas canvas, int top) {
248 | if(mClipDivider && !IS_HONEYCOMB) {
249 | canvas.save();
250 | canvas.clipRect(getPaddingLeft() + mDividerPadding, top,
251 | getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);
252 | mDivider.draw(canvas);
253 | canvas.restore();
254 | } else {
255 | mDivider.setBounds(getPaddingLeft() + mDividerPadding, top,
256 | getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);
257 | mDivider.draw(canvas);
258 | }
259 | }
260 |
261 | void drawVerticalDivider(Canvas canvas, int left) {
262 | if(mClipDivider && !IS_HONEYCOMB) {
263 | canvas.save();
264 | canvas.clipRect(left, getPaddingTop() + mDividerPadding,
265 | left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding);
266 | mDivider.draw(canvas);
267 | canvas.restore();
268 | } else {
269 | mDivider.setBounds(left, getPaddingTop() + mDividerPadding,
270 | left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding);
271 | mDivider.draw(canvas);
272 | }
273 | }
274 |
275 | /**
276 | * Determines where to position dividers between children.
277 | *
278 | * @param childIndex Index of child to check for preceding divider
279 | * @return true if there should be a divider before the child at childIndex
280 | * @hide Pending API consideration. Currently only used internally by the system.
281 | */
282 | protected boolean hasDividerBeforeChildAt(int childIndex) {
283 | if (childIndex == 0) {
284 | return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
285 | } else if (childIndex == getChildCount()) {
286 | return (mShowDividers & SHOW_DIVIDER_END) != 0;
287 | } else if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != 0) {
288 | boolean hasVisibleViewBefore = false;
289 | for (int i = childIndex - 1; i >= 0; i--) {
290 | if (getChildAt(i).getVisibility() != GONE) {
291 | hasVisibleViewBefore = true;
292 | break;
293 | }
294 | }
295 | return hasVisibleViewBefore;
296 | }
297 | return false;
298 | }
299 |
300 | /**
301 | * When true, all children with a weight will be considered having
302 | * the minimum size of the largest child. If false, all children are
303 | * measured normally.
304 | *
305 | * @return True to measure children with a weight using the minimum
306 | * size of the largest child, false otherwise.
307 | *
308 | * @attr ref android.R.styleable#LinearLayout_measureWithLargestChild
309 | */
310 | public boolean isMeasureWithLargestChildEnabled() {
311 | return mUseLargestChild;
312 | }
313 |
314 | /**
315 | * When set to true, all children with a weight will be considered having
316 | * the minimum size of the largest child. If false, all children are
317 | * measured normally.
318 | *
319 | * Disabled by default.
320 | *
321 | * @param enabled True to measure children with a weight using the
322 | * minimum size of the largest child, false otherwise.
323 | *
324 | * @attr ref android.R.styleable#LinearLayout_measureWithLargestChild
325 | */
326 | public void setMeasureWithLargestChildEnabled(boolean enabled) {
327 | mUseLargestChild = enabled;
328 | }
329 |
330 | @Override
331 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
332 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
333 |
334 | if (mUseLargestChild) {
335 | final int orientation = getOrientation();
336 | switch (orientation) {
337 | case HORIZONTAL:
338 | useLargestChildHorizontal();
339 | break;
340 |
341 | case VERTICAL:
342 | useLargestChildVertical();
343 | break;
344 | }
345 | }
346 | }
347 |
348 | private void useLargestChildHorizontal() {
349 | final int childCount = getChildCount();
350 |
351 | // Find largest child width
352 | int largestChildWidth = 0;
353 | for (int i = 0; i < childCount; i++) {
354 | final View child = getChildAt(i);
355 | largestChildWidth = Math.max(child.getMeasuredWidth(), largestChildWidth);
356 | }
357 |
358 | int totalWidth = 0;
359 | // Re-measure childs
360 | for (int i = 0; i < childCount; i++) {
361 | final View child = getChildAt(i);
362 |
363 | if (child == null || child.getVisibility() == View.GONE) {
364 | continue;
365 | }
366 |
367 | final LayoutParams lp =
368 | (LayoutParams) child.getLayoutParams();
369 |
370 | float childExtra = lp.weight;
371 | if (childExtra > 0) {
372 | child.measure(
373 | MeasureSpec.makeMeasureSpec(largestChildWidth,
374 | MeasureSpec.EXACTLY),
375 | MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(),
376 | MeasureSpec.EXACTLY));
377 | totalWidth += largestChildWidth;
378 |
379 | } else {
380 | totalWidth += child.getMeasuredWidth();
381 | }
382 |
383 | totalWidth += lp.leftMargin + lp.rightMargin;
384 | }
385 |
386 | totalWidth += getPaddingLeft() + getPaddingRight();
387 | setMeasuredDimension(totalWidth, getMeasuredHeight());
388 | }
389 |
390 | private void useLargestChildVertical() {
391 | final int childCount = getChildCount();
392 |
393 | // Find largest child width
394 | int largestChildHeight = 0;
395 | for (int i = 0; i < childCount; i++) {
396 | final View child = getChildAt(i);
397 | largestChildHeight = Math.max(child.getMeasuredHeight(), largestChildHeight);
398 | }
399 |
400 | int totalHeight = 0;
401 | // Re-measure childs
402 | for (int i = 0; i < childCount; i++) {
403 | final View child = getChildAt(i);
404 |
405 | if (child == null || child.getVisibility() == View.GONE) {
406 | continue;
407 | }
408 |
409 | final LayoutParams lp =
410 | (LayoutParams) child.getLayoutParams();
411 |
412 | float childExtra = lp.weight;
413 | if (childExtra > 0) {
414 | child.measure(
415 | MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
416 | MeasureSpec.EXACTLY),
417 | MeasureSpec.makeMeasureSpec(largestChildHeight,
418 | MeasureSpec.EXACTLY));
419 | totalHeight += largestChildHeight;
420 |
421 | } else {
422 | totalHeight += child.getMeasuredHeight();
423 | }
424 |
425 | totalHeight += lp.leftMargin + lp.rightMargin;
426 | }
427 |
428 | totalHeight += getPaddingLeft() + getPaddingRight();
429 | setMeasuredDimension(getMeasuredWidth(), totalHeight);
430 | }
431 | }
--------------------------------------------------------------------------------
/linearlistview/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |