Android 自定义GridView网格布局

一、需求

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>

 

转载于:https://my.oschina.net/ososchina/blog/3018471

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值