一、需求
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;
Map<Integer,Integer> rowMaxHeight = 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<count;i++){
final View child = getChildAt(i);
if(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<count;i++){
View convertView = null;
final int viewTypeCount = this.mAdapter.getViewTypeCount();
final int viewType = this.mAdapter.getItemViewType(i);
if(viewType>viewTypeCount) {
throw new IllegalArgumentException("viewType is correct");
}
View view = null;
if(i<getChildCount()){
int type = LayoutParams.TYPE_UNDFINED;
convertView = getChildAt(i);
if(convertView!=null){
type = ((LayoutParams)convertView.getLayoutParams()).getViewType();
}else {
type = LayoutParams.TYPE_UNDFINED;
}
if(type!=viewType){
view = this.mAdapter.getView(i, null, this);
if(convertView!=null) {
removeViewsInLayout(i,1);//使用removeViewInLayout避免频繁刷新,最后统一调用requestLayout
addViewInLayout(view, i); //使用addViewInLayout避免频繁刷新,最后统一调用requestLayout
}else{
addViewInLayout(view,1);
}
}else{
view = this.mAdapter.getView(i, convertView, this);
if(convertView!=null && view!=convertView){
removeViewsInLayout(i,1);
addViewInLayout(view,i);
}
}
}else{
view = this.mAdapter.getView(i, convertView, this);
addViewInLayout(view);
}
if(view!=null){
((LayoutParams)(view.getLayoutParams())).viewType = viewType;
}
}
int delta = getChildCount() - count;
if(delta>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;
}
}
}
自定义属性部分
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AutoHeightFiexedGrideView">
<attr name="numColumns" format="integer"/>
<attr name="horizontalSpacing" format="dimen|reference"/>
<attr name="verticalSpacing" format="dimen|reference"/>
<attr name="viewType" format="integer" />
</declare-styleable>
</resources>