一、需求
Android提供了实现网格布局的GridView,还有RecyclerView等,但是在互相嵌套的问题上冲突很多,尤其是GridView焦点状态转移导致体验相当不好。为了能够在RecyclerView上自动实现网格排列,我们提供了如下方案。
public class AutoFixedHeightGridView extends ViewGroup { private BaseAdapter mAdapter; private DataSetObserver mDataSetObserver; private int columnNums; private int columnPadding; private int rowPadding; MaprowMaxHeight = new HashMap<>(); public AutoFixedHeightGridView(Context context) { this(context,null); } public AutoFixedHeightGridView(Context context, AttributeSet attrs) { this(context, attrs,0); } public AutoFixedHeightGridView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.AutoFixedHeightGridView, defStyleAttr, 0); columnNums = a.getInt(R.styleable.AutoFixedHeightGridView_numColumns, 1); int hSpacing = a.getDimensionPixelOffset( R.styleable.AutoFixedHeightGridView_horizontalSpacing, 0); columnPadding = hSpacing; int vSpacing = a.getDimensionPixelOffset( R.styleable.AutoFixedHeightGridView_verticalSpacing, 0); rowPadding = vSpacing; a.recycle(); } public void setAdapter(BaseAdapter mAdapter) { if(this.mAdapter!=null && mDataSetObserver != null){ this.mAdapter.unregisterDataSetObserver(mDataSetObserver); } if(mDataSetObserver==null){ mDataSetObserver = new GridDataSetObserver(this); } this.mAdapter = mAdapter; if(this.mAdapter!=null) { this.mAdapter.registerDataSetObserver(mDataSetObserver); this.mAdapter.notifyDataSetChanged(); }else{ datasetChanged(); } } public BaseAdapter getAdapter() { return mAdapter; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int rowNums = 0; if (widthMode == MeasureSpec.UNSPECIFIED) { widthSize = getPaddingLeft() + getPaddingRight(); if(columnNums>1){ widthSize += (columnNums-1) * columnPadding; } } if (heightMode == MeasureSpec.UNSPECIFIED) { heightSize = getPaddingBottom() + getPaddingTop(); int contentWidth = widthSize - (getPaddingLeft() + getPaddingRight()); if (columnNums > 1) { contentWidth = contentWidth - (columnNums - 1) * columnPadding; } int columnWidth = contentWidth / columnNums; int count = this.getAdapter() != null ? this.getAdapter().getCount() : 0; if (count != getChildCount()) return; for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); LayoutParams params = (LayoutParams) child.getLayoutParams(); params.rightMargin = 0; params.leftMargin = 0; int childHeightSpec = getChildMeasureSpec( MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED), 0, params.height); int childWidthSpec = getChildMeasureSpec( MeasureSpec.makeMeasureSpec(columnWidth, MeasureSpec.EXACTLY), 0, columnWidth); child.measure(childWidthSpec, childHeightSpec); int childHeight = child.getMeasuredHeight(); if (i % columnNums == 0 && i > 1) { rowNums++; } Integer lastMaxHeight = rowMaxHeight.get(rowNums); if (lastMaxHeight == null) { lastMaxHeight = 0; } rowMaxHeight.put(rowNums, Math.max(lastMaxHeight, childHeight)); } int rowCount = (int) Math.ceil(count * 1.0f / columnNums); for (int i = 0; i < rowCount; i++) { heightSize += rowMaxHeight.get(i); } if (rowCount > 1) { heightSize += (rowCount - 1) * rowPadding; } }else{ int contentWidth = widthSize - (getPaddingLeft() + getPaddingRight()); if (columnNums > 1) { contentWidth = contentWidth - (columnNums - 1) * columnPadding; } int columnWidth = contentWidth / columnNums; int count = this.getAdapter() != null ? this.getAdapter().getCount() : 0; if (count != getChildCount()) return; for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); LayoutParams params = (LayoutParams) child.getLayoutParams(); params.rightMargin = 0; params.leftMargin = 0; int childHeightSpec = getChildMeasureSpec( MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED), 0, heightSize); int childWidthSpec = getChildMeasureSpec( MeasureSpec.makeMeasureSpec(columnWidth, MeasureSpec.EXACTLY), 0, columnWidth); child.measure(childWidthSpec, childHeightSpec); } } setMeasuredDimension(widthSize, heightSize); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = this.getAdapter()!=null?this.getAdapter().getCount():0; int rowCount = 0; int nextChildLeft = getPaddingLeft(); int nextChildTop = getPaddingTop(); int contentWidth = getMeasuredWidth() - (getPaddingLeft() + getPaddingRight()); if(columnNums>1){ contentWidth = contentWidth - (columnNums-1) * columnPadding; } int columnWidth = contentWidth /columnNums; if(count!=getChildCount()) return; for (int i=0;i 0 && i%columnNums==0){ Integer H = rowMaxHeight.get(rowCount); rowCount++; nextChildLeft = getPaddingLeft(); if(H==null){ H = 0; } nextChildTop = nextChildTop +H + rowPadding; } child.layout(nextChildLeft,nextChildTop,nextChildLeft+columnWidth,nextChildTop+child.getMeasuredHeight()); nextChildLeft = nextChildLeft+child.getMeasuredWidth() + columnPadding ; } } protected void datasetChanged() { if(this.mAdapter==null){ removeAllViews(); return; } final int count = this.mAdapter.getCount(); for (int i=0;i viewTypeCount) { throw new IllegalArgumentException("viewType is correct"); } View view = null; if(i 0){ removeViewsInLayout(getChildCount()-delta,delta); } rquestLayout(); invalidate(); } protected void addViewInLayout(View child){ this.addViewInLayout(child,-1); } /** * * @param child * @param index 位置,如果是-1,则自动添加到末尾 */ protected void addViewInLayout(View child,int index){ if (child == null) { throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup"); } ViewGroup.LayoutParams params = child.getLayoutParams(); if (params == null) { params = generateDefaultLayoutParams(); if (params == null) { throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null"); } } super.addViewInLayout(child,index,params); } public static class GridDataSetObserver extends DataSetObserver{ private AutoFixedHeightGridView fixedHeightGridView; public GridDataSetObserver(FixedHeightGridView fixedHeightGridView) { this.fixedHeightGridView = fixedHeightGridView; } @Override public void onChanged() { super.onChanged(); fixedHeightGridView.datasetChanged(); } @Override public void onInvalidated() { super.onInvalidated(); fixedHeightGridView.datasetChanged(); } } @Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() { //child默认布局参数 return new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT); } @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { //重写该方法,否则 return (p instanceof LayoutParams) ; } @Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } public static class LayoutParams extends ViewGroup.MarginLayoutParams{ private int viewType; private static final int TYPE_UNDEFINED = 0; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a=c.obtainStyledAttributes(attrs, R.styleable.AutoFixedHeightGridView); this.viewType = a.getInt(R.styleable.AutoFixedHeightGridView_viewType, TYPE_UNDEFINED); //该属性定义给child的 a.recycle(); } public LayoutParams(int width, int height) { super(width, height); this.viewType = TYPE_UNDEFINED; } public LayoutParams(MarginLayoutParams source) { super(source); if(source instanceOf LayoutParams){ this.viewType = ((LayoutParams)source).viewType; }else{ this.viewType = TYPE_UNDEFINED; } } public LayoutParams(ViewGroup.LayoutParams source) { super(source); if(source instanceOf LayoutParams){ this.viewType = ((LayoutParams)source).viewType; } else{ this.viewType = TYPE_UNDEFINED; } } public int getViewType(){ return viewType; } }}
自定义属性部分