/*
* ******************************************************************************
* Copyright (c) 2013-2014 Gabriele Mariotti.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* *****************************************************************************
*/
package it.gmariotti.cardslib.library.prototypes;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import it.gmariotti.cardslib.library.R;
/**
* An extension of a linear layout that supports the divider API of Android
* 4.0+. You can populate this layout with data that comes from a
* {@link android.widget.ListAdapter}
*
* @author Gabriele Mariotti (gabri.mariotti@gmail.com)
*/
public class LinearListView extends LinearLayout {
/**
* Adapter
*/
private CardWithList.LinearListAdapter mListAdapter;
/**
* Represents an invalid position. All valid positions are in the range 0 to 1 less than the
* number of items in the current adapter.
*/
public static final int INVALID_POSITION = -1;
// -------------------------------------------------------------
// Constructors
// -------------------------------------------------------------
public LinearListView(Context context) {
super(context);
initAttrs(null, 0);
}
public LinearListView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttrs(attrs, 0);
}
public LinearListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initAttrs(attrs, defStyle);
}
// -------------------------------------------------------------
// Init
// -------------------------------------------------------------
/**
* Init custom attrs.
*
* @param attrs
* @param defStyle
*/
protected void initAttrs(AttributeSet attrs, int defStyle) {
TypedArray a = getContext().getTheme().obtainStyledAttributes(
attrs, /*com.android.internal.R.styleable.*/R_styleable_LinearLayout, defStyle, defStyle);
try {
final Drawable d = a.getDrawable(/*com.android.internal.R.styleable.*/LinearLayout_divider);
if (d != null) {
// If a divider is specified use its intrinsic height for divider height
setDividerDrawable(d);
}
} finally {
a.recycle();
}
a = getContext().getTheme().obtainStyledAttributes(
attrs, R.styleable.card_listItem, defStyle, defStyle);
try {
// Use the height specified, zero being the default
final int dividerHeight = a.getDimensionPixelSize(
R.styleable.card_listItem_card_list_item_dividerHeight, 0);
if (dividerHeight != 0) {
setDividerHeight(dividerHeight);
}
} finally {
a.recycle();
}
}
// -------------------------------------------------------------
// Divider
// -------------------------------------------------------------
private static final int[] R_styleable_LinearLayout = new int[]{
/* 0 */ android.R.attr.divider,
/* 1 */ android.R.attr.measureWithLargestChild,
/* 2 */ android.R.attr.showDividers,
/* 3 */ android.R.attr.dividerPadding,
};
private static final int LinearLayout_divider = 0;
private Drawable mDivider;
protected int mDividerWidth;
protected int mDividerHeight;
/**
* Set a drawable to be used as a divider between items.
*
* @param divider Drawable that will divide each item.
* @see #setShowDividers(int)
*/
public void setDividerDrawable(Drawable divider) {
if (divider == mDivider) {
return;
}
mDivider = divider;
if (divider != null) {
mDividerWidth = divider.getIntrinsicWidth();
mDividerHeight = divider.getIntrinsicHeight();
} else {
mDividerWidth = 0;
mDividerHeight = 0;
}
setWillNotDraw(divider == null);
requestLayout();
}
@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
final int index = indexOfChild(child);
final int orientation = getOrientation();
final LayoutParams params = (LayoutParams) child.getLayoutParams();
if (hasDividerBeforeChildAt(index)) {
if (orientation == VERTICAL) {
//Account for the divider by pushing everything up
params.topMargin = mDividerHeight;
} else {
//Account for the divider by pushing everything left
params.leftMargin = mDividerWidth;
}
}
final int count = getChildCount();
if (index == count - 1) {
if (hasDividerBeforeChildAt(count)) {
if (orientation == VERTICAL) {
params.bottomMargin = mDividerHeight;
} else {
params.rightMargin = mDividerWidth;
}
}
}
super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
}
@Override
protected void onDraw(Canvas canvas) {
if (mDivider != null) {
if (getOrientation() == VERTICAL) {
drawDividersVertical(canvas);
} else {
drawDividersHorizontal(canvas);
}
}
super.onDraw(canvas);
}
void drawDividersVertical(Canvas canvas) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child != null && child.getVisibility() != GONE) {
if (hasDividerBeforeChildAt(i)) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int top = child.getTop() - lp.topMargin/* - mDividerHeight*/;
drawHorizontalDivider(canvas, top);
}
}
}
if (hasDividerBeforeChildAt(count)) {
final View child = getChildAt(count - 1);
int bottom = 0;
if (child == null) {
bottom = getHeight() - getPaddingBottom() - mDividerHeight;
} else {
//final LayoutParams lp = (LayoutParams) child.getLayoutParams();
bottom = child.getBottom()/* + lp.bottomMargin*/;
}
drawHorizontalDivider(canvas, bottom);
}
}
void drawDividersHorizontal(Canvas canvas) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child != null && child.getVisibility() != GONE) {
if (hasDividerBeforeChildAt(i)) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int left = child.getLeft() - lp.leftMargin/* - mDividerWidth*/;
drawVerticalDivider(canvas, left);
}
}
}
if (hasDividerBeforeChildAt(count)) {
final View child = getChildAt(count - 1);
int right = 0;
if (child == null) {
right = getWidth() - getPaddingRight() - mDividerWidth;
} else {
//final LayoutParams lp = (LayoutParams) child.getLayoutParams();
right = child.getRight()/* + lp.rightMargin*/;
}
drawVerticalDivider(canvas, right);
}
}
void drawHorizontalDivider(Canvas canvas, int top) {
mDivider.setBounds(getPaddingLeft() + getDividerPadding(), top,
getWidth() - getPaddingRight() - getDividerPadding(), top + mDividerHeight);
mDivider.draw(canvas);
}
void drawVerticalDivider(Canvas canvas, int left) {
mDivider.setBounds(left, getPaddingTop() + getDividerPadding(),
left + mDividerWidth, getHeight() - getPaddingBottom() - getDividerPadding());
mDivider.draw(canvas);
}
/**
* Determines where to position dividers between children.
*
* @param childIndex Index of child to check for preceding divider
* @return true if there should be a divider before the child at childIndex
* @hide Pending API consideration. Currently only used internally by the system.
*/
protected boolean hasDividerBeforeChildAt(int childIndex) {
if (childIndex == 0) {
return (getShowDividers() & SHOW_DIVIDER_BEGINNING) != 0;
} else if (childIndex == getChildCount()) {
return (getShowDividers() & SHOW_DIVIDER_END) != 0;
} else if ((getShowDividers() & SHOW_DIVIDER_MIDDLE) != 0) {
boolean hasVisibleViewBefore = false;
for (int i = childIndex - 1; i >= 0; i--) {
if (getChildAt(i).getVisibility() != GONE) {
hasVisibleViewBefore = true;
break;
}
}
return hasVisibleViewBefore;
}
return false;
}
/**
* @return Returns the height of the divider that will be drawn between each item in the list.
*/
public int getDividerHeight() {
return mDividerHeight;
}
/**
* Sets the height of the divider that will be drawn between each item in the list. Calling
* this will override the intrinsic height as set by {@link #setDividerDrawable(Drawable)}
*
* @param height The new height of the divider in pixels.
*/
public void setDividerHeight(int height) {
if (getOrientation() == VERTICAL) {
mDividerHeight = height;
} else {
mDividerWidth = height;
}
requestLayout();
}
@Override
public void setOrientation(int orientation) {
if (orientation != getOrientation()) {
int tmp = mDividerHeight;
mDividerHeight = mDividerWidth;
mDividerWidth = tmp;
}
super.setOrientation(orientation);
}
// -------------------------------------------------------------
// Adapter
// -------------------------------------------------------------
/**
* Sets the adapter.
* Sets the data behind this LinearListView.
*
* @param listAdapter The ListAdapter which is responsible for maintaining the data
* backing this list and for producing a view to represent an
* item in that data set.
*/
public void setAdapter(CardWithList.LinearListAdapter listAdapter) {
this.mListAdapter = listAdapter;
setOrientation(VERTICAL);
//Populate the list
if (mListAdapter != null) {
for (int i = 0; i < mListAdapter.getCount(); i++) {
View itemView = mListAdapter.getView(i, null, null);
if (itemView != null)
this.addView(itemView);
}
}
}
/**
* Returns the adapter
*
* @return
*/
public CardWithList.LinearListAdapter getAdapter() {
return mListAdapter;
}
/**
* Get the position within the adapter's data set for the view, where view is a an adapter item
* or a descendant of an adapter item.
*
* @param view an adapter item, or a descendant of an adapter item. This must be visible in this
* AdapterView at the time of the call.
* @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION}
* if the view does not correspond to a list item (or it is not currently visible).
*/
public int getPositionForView(View view) {
View listItem = view;
try {
View v;
while (!(v = (View) listItem.getParent()).equals(this)) {
listItem = v;
}
} catch (ClassCastException e) {
// We made it up to the window without find this list itemView
return INVALID_POSITION;
}
// Search the children for the list item
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
if (getChildAt(i).equals(listItem)) {
return i;
}
}
// Child not found!
return INVALID_POSITION;
}
}