TagLayout
import android.content.Context ;
import android.content.res.TypedArray ;
import android.util.AttributeSet ;
import android.util.Log ;
import android.util.SparseArray ;
import android.view.View ;
import android.view.ViewGroup ;
import java.util.ArrayList ;
import java.util.HashMap ;
import java.util.List ;
/**
* Created by whuthm
*/
public class TagLayout extends ViewGroup implements View.OnClickListener ,
ViewGroup.OnHierarchyChangeListener {
private int mHorizontalSpacing;
private int mVerticalSpacing ;
private boolean mSingleChoice ;
private SparseArray<List<View>> mSparseViews = new SparseArray<List<View>>() ;
private SparseArray<Integer> mSparseHeights = new SparseArray<Integer>();
private HashMap<Object , Boolean> mTagStates = new HashMap<Object , Boolean>();
private OnTagSelectedListener mTagListener;
public TagLayout(Context context) {
this (context, null) ;
}
public TagLayout(Context context, AttributeSet attrs) {
this (context, attrs , 0) ;
}
public TagLayout(Context context, AttributeSet attrs , int defStyle) {
super (context, attrs , defStyle);
final TypedArray a = context.obtainStyledAttributes(attrs , R.styleable.TagLayout);
mSingleChoice = a.getBoolean(R.styleable.TagLayout_singleChoice, true );
mHorizontalSpacing = a.getDimensionPixelOffset(
R.styleable.TagLayout_android_horizontalSpacing, 0 );
mHorizontalSpacing = Math.max(0 , mHorizontalSpacing) ;
mVerticalSpacing = a.getDimensionPixelOffset(
R.styleable.TagLayout_android_verticalSpacing, 0 );
mVerticalSpacing = Math.max(0 , mVerticalSpacing) ;
a.recycle() ;
setOnHierarchyChangeListener( this);
}
public void setOnTagSelectedListener(OnTagSelectedListener l) {
mTagListener = l;
}
private void notifyTagSelectedChange(Object tag, boolean selected) {
mTagStates .put(tag, selected) ;
if ( mTagListener != null ) {
mTagListener .onTagSelectedChange(tag, selected) ;
}
}
public void addTag(Object tag, View child) {
addTag(tag, child, false) ;
}
public void addTag(Object tag, View child, boolean selected) {
child.setTag(tag);
addView(child) ;
setChildrenSelected(child , selected);
}
@Override
public void addView(View child, int index, LayoutParams params) {
Object tag = child.getTag();
if (tag != null) {
if (!mTagStates.containsKey(tag)) {
super.addView(child , index, params);
child.setOnClickListener(this );
mTagStates.put(tag , child.isSelected());
}
else {
Log.e("TagLayout" , "tag duplicate : " + tag) ;
}
}
else {
Log.e ("TagLayout" , "tag = null") ;
}
}
private void setChildrenSelected(View curChild, boolean curSelected) {
int curIndex = indexOfChild(curChild) ;
if (curIndex >= 0) {
if (mSingleChoice) {
if (!curSelected) {
return;
}
int count = getChildCount() ;
for (int i = 0; i < count; i++) {
View v = getChildAt(i) ;
boolean isSelected = v.isSelected();
if (curSelected) {
v.setSelected(i == curIndex) ;
if (isSelected != v.isSelected()) {
notifyTagSelectedChange(v.getTag() , v.isSelected());
}
}
}
}
else {
boolean isSelected = curChild.isSelected() ;
if (isSelected != curSelected) {
curChild.setSelected(curSelected) ;
notifyTagSelectedChange(curChild.getTag(), curChild.isSelected()) ;
}
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mSparseViews .clear();
mSparseHeights .clear();
measureChildren(widthMeasureSpec , heightMeasureSpec);
final int widthSpecMode = MeasureSpec. getMode(widthMeasureSpec) ;
final int widthSpecSize = MeasureSpec. getSize(widthMeasureSpec) ;
final int heightSpecMode = MeasureSpec. getMode(heightMeasureSpec) ;
final int heightSpecSize = MeasureSpec. getSize(heightMeasureSpec) ;
int paddingLeft = getPaddingLeft() ;
int paddingRight = getPaddingRight() ;
int paddingTop = getPaddingTop() ;
int paddingBottom = getPaddingBottom() ;
int maxWidth = widthSpecSize - paddingLeft - paddingRight ;
int newWidth = 0;
int maxHeight = heightSpecSize - paddingTop - paddingBottom ;
int newHeight = 0;
// 未使用,计算minWidth minHeight,以及background的值
// int suggestedWidth = getSuggestedMinimumWidth();
// int suggestedHeight = getSuggestedMinimumHeight();
int count = getChildCount();
int rowIndex = 0;
int rowMaxWidth = 0;
if ((maxWidth > 0 && widthSpecMode != MeasureSpec.UNSPECIFIED)
|| (widthSpecMode == MeasureSpec. UNSPECIFIED)) {
for (int i = 0; i < count ; i++) {
View child = getChildAt(i) ;
if (child.getVisibility() != View.GONE) {
List<View> views = mSparseViews.get(rowIndex);
Integer childMaxHeight = mSparseHeights .get(rowIndex);
if (views == null) {
views = new ArrayList<View>();
mSparseViews.put(rowIndex, views);
}
if (childMaxHeight == null) {
childMaxHeight = new Integer(0 );
mSparseHeights.put(rowIndex, childMaxHeight);
}
int childWidth = child.getMeasuredWidth() ;
int childHeight = child.getMeasuredHeight() ;
if (childHeight > childMaxHeight) {
mSparseHeights.put(rowIndex, childHeight);
}
int rowChildCount = views.size() ;
if (widthSpecMode != MeasureSpec.UNSPECIFIED) {
int tempRowWidth = rowMaxWidth + childWidth
+ (rowChildCount > 0 ? mHorizontalSpacing : 0) ;
if (rowChildCount <= 0 || tempRowWidth <= maxWidth) {
rowMaxWidth = tempRowWidth ;
views.add(child);
}
else {
rowIndex++ ;
views = new ArrayList<View>() ;
mSparseViews.put(rowIndex, views);
mSparseHeights.put(rowIndex, childHeight);
rowMaxWidth = childWidth;
views.add(child);
}
}
else {
rowMaxWidth += childWidth
+ (rowChildCount > 0 ? mHorizontalSpacing : 0) ;
views.add(child);
}
newWidth = rowMaxWidth ;
}
}
int rowCount = mSparseViews.size() ;
if (widthSpecMode == MeasureSpec. AT_MOST) {
if (rowCount <= 1) {
newWidth = Math. min(newWidth , maxWidth);
}
else {
newWidth = maxWidth ;
}
}
else if (widthSpecMode == MeasureSpec.EXACTLY) {
newWidth = maxWidth;
}
}
int rowMaxHeight = 0;
int rowCount = mSparseHeights.size();
for ( int i = 0 ; i < rowCount; i++) {
rowMaxHeight += mSparseHeights.get(i) + (i > 0 ? mVerticalSpacing : 0) ;
}
if (heightSpecMode == MeasureSpec.AT_MOST) {
newHeight = Math.min(rowMaxHeight, maxHeight);
}
else if (heightSpecMode == MeasureSpec.EXACTLY) {
newHeight = maxHeight;
}
else {
newHeight = rowMaxHeight;
}
setMeasuredDimension(newWidth + paddingLeft + paddingLeft, newHeight + paddingTop
+ paddingBottom);
}
@Override
protected void onLayout(boolean change, int left , int top, int right, int bottom) {
int rowCount = mSparseHeights.size() ;
int rowLeft = getPaddingLeft() ;
int rowTop = getPaddingTop() ;
for ( int rowIndex = 0 ; rowIndex < rowCount; rowIndex++) {
List<View> views = mSparseViews.get(rowIndex) ;
int childMaxHeight = mSparseHeights.get(rowIndex);
int rowChildCount = views.size() ;
int l = rowLeft ;
for ( int i = 0 ; i < rowChildCount; i++) {
View child = views.get(i);
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
int t = rowTop + (childMaxHeight - childHeight) / 2;
child.layout(l, t, l + childWidth , t + childHeight);
l += childWidth + mHorizontalSpacing ;
}
rowTop += childMaxHeight + mVerticalSpacing;
}
}
@Override
protected void onDetachedFromWindow() {
super .onDetachedFromWindow();
mSparseViews .clear();
mSparseHeights .clear();
}
@Override
public void onClick(View v) {
if (indexOfChild(v) >= 0) {
setChildrenSelected(v, !v.isSelected()) ;
}
}
@Override
public void onChildViewAdded(View view, View child) {
}
@Override
public void onChildViewRemoved(View view, View child) {
if (view == this) {
mTagStates .remove(child.getTag());
}
}
public interface OnTagSelectedListener {
public void onTagSelectedChange(Object tag, boolean selected);
}
attrs
<declare-styleable name="TagLayout">
<attr name= "android:horizontalSpacing" />
<attr name= "android:verticalSpacing" />
<attr name= "singleChoice" format="boolean" />
</declare-styleable>