ViewGroup 自定义演示

第一部分:利用系统属性自定义ViewGroup

1、ViewGroup的职责是啥?
ViewGroup相当于一个放置View的容器,ViewGroup的职能为:给childView计算出建议的宽和高和测量模式 ;决定childView的位置;为什么只是建议的宽和高,而不是直接确定呢,因为childView宽和高可以设置为wrap_content,这样只有childView才能计算出自己的宽和高。
2、View的职责是啥?
View的职责,根据测量模式和ViewGroup给出的建议的宽和高,计算出自己的宽和高;同时还有个更重要的职责是:在ViewGroup为其指定的区域内绘制自己的形态。
总结: 根据ViewGroup传人的测量值和模式,View对自己宽高进行确定(onMeasure中完成),然后在onDraw中完成对自己的绘制。
ViewGroup需要给View传入view的测量值和模式(onMeasure中完成),而且对于此ViewGroup的父布局,自己也需要在onMeasure中完成对自己宽和高的确定。此外,需要在onLayout中完成对其childView的位置的指定。
举例1: 定义一个ViewGroup,内部可以传入0到4个childView,分别依次显示在左上角,右上角,左下角,右下角。利用系统的 MarginLayoutParams,因为只需要ViewGroup能够支持margin即可



public class ViewGroupTest1 extends ViewGroup {
    public ViewGroupTest1(Context context) {
        super(context);
    }

    public ViewGroupTest1(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ViewGroupTest1(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    //该方法是用来设置ViewGroup 布局参数  指定了其LayoutParams为MarginLayoutParams
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(),attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //对子view 进行测量
        measureChildren(widthMeasureSpec,heightMeasureSpec);
        /**
         * 如果ViewGroup是wrap_content时,需要对ViewGroup采用自定义的测试方式进行测量它的宽和高
         */
        int width = 0;
        int height = 0;

        int cWidth = 0;
        int cHeight = 0;
        MarginLayoutParams cParams = null;
        // 用于计算左边两个childView的高度
        int lHeight = 0;
        // 用于计算右边两个childView的高度,最终高度取二者之间大值
        int rHeight = 0;
        // 用于计算上边两个childView的宽度
        int tWidth = 0;
        // 用于计算下面两个childiew的宽度,最终宽度取二者之间大值
        int bWidth = 0;
        int count = getChildCount();
        for (int i=0;i<count;i++){
            View viewChildren = getChildAt(i);
            cWidth = viewChildren.getMeasuredWidth();
            cHeight = viewChildren.getMeasuredHeight();
            cParams = (MarginLayoutParams) viewChildren.getLayoutParams();
            // 上面两个childView
            if (i == 0 || i == 1)
            {
                tWidth += cWidth + cParams.leftMargin + cParams.rightMargin;
            }

            if (i == 2 || i == 3)
            {
                bWidth += cWidth + cParams.leftMargin + cParams.rightMargin;
            }

            if (i == 0 || i == 2)
            {
                lHeight += cHeight + cParams.topMargin + cParams.bottomMargin;
            }

            if (i == 1 || i == 3)
            {
                rHeight += cHeight + cParams.topMargin + cParams.bottomMargin;
            }
        }
        width = Math.max(tWidth, bWidth);
        height = Math.max(lHeight, rHeight);
        boolean isWidthExActly = widthMode == MeasureSpec.EXACTLY;
        boolean isHeightWxactly = heightMode == MeasureSpec.EXACTLY;
        Log.i("niuniu " , " widthMode  " + isWidthExActly + "  widthSize " + widthSize + "   width " +width);
        Log.i("niuniu " , " heightMode  " + isHeightWxactly + "  heightSize " + heightSize + "   height " +height);
        //将得到的宽高 通过setMeasuredDimension方法设置进去,完成测量工作.
        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY? widthSize:width,heightMode == MeasureSpec.EXACTLY?heightSize:height);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int cCount = getChildCount();
        int cWidth = 0;
        int cHeight = 0;
        MarginLayoutParams cParams = null;

        /**
         * 根据childView的宽和高,以及margin,计算childView在GruopView的位置(l, t, r, b) 并使用layout进行布局
         */
        for (int i = 0; i < cCount; i++)
        {
            View childView = getChildAt(i);
            cWidth = childView.getMeasuredWidth();
            cHeight = childView.getMeasuredHeight();
            cParams = (MarginLayoutParams) childView.getLayoutParams();
//            Log.i("niuniu", " cParams.leftMargin :" +cParams.leftMargin + " cParams.topMargin " +
//                    cParams.topMargin + "  cParams.rightMargin:  " +cParams.rightMargin + "  cParams.bottomMargin  " + cParams.bottomMargin);

            int cl = 0, ct = 0, cr = 0, cb = 0;

            switch (i)
            {
                case 0:
                    cl = cParams.leftMargin;
                    ct = cParams.topMargin;
                    break;
                case 1:
                    cl = getWidth() - cWidth - cParams.rightMargin;
                    ct = cParams.topMargin;
                    break;
                case 2:
                    cl = cParams.leftMargin;
                    ct = getHeight() - cHeight - cParams.bottomMargin;
                    break;
                case 3:
                    cl = getWidth() - cWidth- cParams.rightMargin;
                    ct = getHeight() - cHeight - cParams.bottomMargin;
                    break;
            }
            cr = cl + cWidth;
            cb = cHeight + ct;
            childView.layout(cl, ct, cr, cb);
        }
    }
}

//activity_main.xml 中引用 
<com.example.nft.myapplication.ViewGroupTest1
        android:layout_width="match_parent"
        android:layout_height="500dp"
        android:id="@+id/viewGruop1"
        android:layout_marginTop="15dp">
        <TextView
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:layout_marginLeft="20dp"
            android:layout_marginBottom="800dp"
            android:textSize="50dp"
            android:text="1"
            android:background="#FF4444"
            android:gravity="center"
            android:textStyle="bold"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp"
            android:textSize="50dp"
            android:text="2"
            android:background="#00ff00"
            android:gravity="center"
            android:textStyle="bold"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="45dp"
            android:textSize="50dp"
            android:text="3"
            android:background="#0044ff"
            android:gravity="center"
            android:textStyle="bold"/>
        <TextView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginHorizontal="50dp"
            android:textSize="50dp"
            android:text="4"
            android:background="#ff6600"
            android:gravity="center"
            android:textStyle="bold"/>
    </com.example.nft.myapplication.ViewGroupTest1>

对子view进行测量 也可以使用measureChild方法

  int count = getChildCount();
  for (int i = 0; i < count; i++) {
    final View child = getChildAt(i);
    measureChild(child, widthMeasureSpec, heightMeasureSpec);
  }

而使用measureChildren() 方法来简化上面的代码,这个方法将自动遍历所有子view并让它们测量自己,还可以忽略那些visibility 设置为gone的子view,因此它支持visibility gone标志.

第二部分 自定义ViewGroup 定义自己的属性

当使用不同的布局方式时,子view得布局属性就不太一样,比如当父布局是LinearLayout时,子view可以使用父布局属性如layout_weight、weightSum、layout_gravity等;当使用的是RelativeLayout时,其子view就能使用属于父布局的有效属性layout_centerInParent等;因此不同的布局容器,有不同的布局属性, 当需要我们的自定义容器需要定义自己的布局属性时,就必须使用LayoutParams来实现.
先来简单看看viewGroup的addView方法

public void addView(View child, int index) {
        ...
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = generateDefaultLayoutParams();
         ....
        }
        addView(child, index, params);
    } 
public void addView(View child, int index, LayoutParams params) {
     ...
    addViewInner(child, index, params, false);
}

private void addViewInner(View child, int index, LayoutParams params,
        boolean preventRequestLayout) {
  ...

    if (!checkLayoutParams(params)) {
        params = generateLayoutParams(params);
    }

    if (preventRequestLayout) {
        child.mLayoutParams = params;
    } else {
        child.setLayoutParams(params);
    }
}

首先是checkLayoutParams,目的是检测这个参数是否为空,如果为空的话就给它生成一个普通的LayoutParams; 实现布局参数转换成自定义的参数,如下三个方法就显得尤为重要了。

    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return  p != null;
    }
  protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
      return p;
  }
  public LayoutParams generateLayoutParams(AttributeSet attrs) {
      return new LayoutParams(getContext(), attrs);
  }

比如 FrameLayout.LayoutParams中就自定义了一个Gravity属性,FrameLayout实现了addView的这三个方法

public static class LayoutParams extends MarginLayoutParams {

    public int gravity = -1;

    public LayoutParams(Context c, AttributeSet attrs) {
        super(c, attrs);

        TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout_Layout);
        gravity = a.getInt(com.android.internal.R.styleable.FrameLayout_Layout_layout_gravity, -1);
        a.recycle();
    }

    public LayoutParams(int width, int height) {
        super(width, height);
    }

    public LayoutParams(int width, int height, int gravity) {
        super(width, height);
        this.gravity = gravity;
    }

    ....
    public LayoutParams(LayoutParams source) {
        super(source);
        this.gravity = source.gravity;
    }
}
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new FrameLayout.LayoutParams(getContext(), attrs);        
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

举例2 自定义Group中添加layout_bg,layout_orientation 属性,供子view来使用.


public class ViewGroupTest2 extends ViewGroup {
    private int orientation = 0;
    public ViewGroupTest2(Context context) {
        super(context);
    }

    public ViewGroupTest2(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray  = context.obtainStyledAttributes(attrs,R.styleable.ViewGroupTest2);
        orientation = typedArray.getInt(R.styleable.ViewGroupTest2_layout_orientation,0);
        typedArray.recycle();

    }

    public ViewGroupTest2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec,heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int width;
        int height;
        if(widthMode == MeasureSpec.EXACTLY){
            width = widthSize;
        } else{
            width = widthSize+500;
        }

        if (heightMode == MeasureSpec.EXACTLY){
            height = heightSize;
        }else {
            height = heightSize+600;
        }

        setMeasuredDimension(width,height);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int count = getChildCount();
        int width ;
        int height ;
        int distance = 0;
        MyLayoutParams params = null;
        for (int i = 0;i<count;i++){
             View view = getChildAt(i);
             params = (MyLayoutParams) view.getLayoutParams();
             view.setBackgroundColor(params.color);
             width = view.getMeasuredWidth();
             height = view.getMeasuredHeight();

            int cl = 0, ct = 0, cr = 0, cb = 0;
            if(orientation == 0){ // 水平一次排列
                cl = distance;
                ct = 80;
            } else { //垂直依次排列
                ct = distance;
                cl = 80;
            }
            cr = cl + width;
            cb = height + ct;
            view.layout(cl, ct, cr, cb);
            // 计算下一个子view的左边距离 或者顶部距离
            if (orientation == 0){
                distance += width +20;
            } else {
                distance += height+40;
            }

        }
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        Log.i("niuniu", " generateLayoutParams attrs ");
        return new MyLayoutParams(getContext(),attrs);
    }

    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        Log.i("niuniu", " generateLayoutParams  p ");
        return new MyLayoutParams(p);
    }

    @Override
    protected boolean checkLayoutParams(LayoutParams p) {
        boolean params  = p instanceof  LayoutParams;
        Log.i("niuniu", " checkLayoutParams  params " + params);
        return params;
    }
}

//创建自己的LayoutParams 并获取父容器所支持的属性

public class MyLayoutParams extends ViewGroup.LayoutParams {

    public int color ;
    public MyLayoutParams(Context c, AttributeSet attrs) {
        super(c, attrs);
        Log.i("niuniu", " MyLayoutParams attrs ");
        TypedArray ta = c.obtainStyledAttributes(attrs,R.styleable.MyParams);
        color = ta.getColor(R.styleable.MyParams_layout_bg, Color.DKGRAY);
        ta.recycle();
    }

    public MyLayoutParams(int width, int height) {
        super(width, height);
    }

    public MyLayoutParams(ViewGroup.LayoutParams source) {
        super(source);
    }
    public MyLayoutParams(MyLayoutParams source) {
        super(source);
        Log.i("niuniu", " MyLayoutParams source ");
    }
}
activity_main.xml中引入该父容器 
    <com.example.nft.myapplication.ViewGroupTest2
        xmlns:viewGroupTest2 = "http://schemas.android.com/apk/res/com.example.nft.myapplication"
        android:layout_width="wrap_content"
        android:layout_height="800dp"
        android:id="@+id/viewGroup2"
        android:layout_marginTop="15dp"
        viewGroupTest2:layout_orientation = "horital"
        >
        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:textStyle="bold"
            android:text="1"
            android:textSize="24dp"
            viewGroupTest2:layout_bg="#FF4444"/>
        <TextView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:textStyle="bold"
            android:text="2"
            android:textSize="24dp"
            viewGroupTest2:layout_bg="#bb6cc0"/>
        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:textStyle="bold"
            android:text="3"
            android:textSize="24dp"
            viewGroupTest2:layout_bg="#66ff00"/>
        <TextView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:textStyle="bold"
            android:text="4"
            android:textSize="24dp"
            viewGroupTest2:layout_bg="#6f60f0"/>
    </com.example.nft.myapplication.ViewGroupTest2>

log 输出:
generateLayoutParams attrs ;
MyLayoutParams attrs ;
checkLayoutParams params true
总结 :
在xml中引入这个ViewGroupTest2 布局,会调用public 的generateLayoutParams(atters)方法来给子view生成自定义的布局参数MyLayoutParam.

在使用Python来安装geopandas包时,由于geopandas依赖于几个其他的Python库(如GDAL, Fiona, Pyproj, Shapely等),因此安装过程可能需要一些额外的步骤。以下是一个基本的安装指南,适用于大多数用户: 使用pip安装 确保Python和pip已安装: 首先,确保你的计算机上已安装了Python和pip。pip是Python的包管理工具,用于安装和管理Python包。 安装依赖库: 由于geopandas依赖于GDAL, Fiona, Pyproj, Shapely等库,你可能需要先安装这些库。通常,你可以通过pip直接安装这些库,但有时候可能需要从其他源下载预编译的二进制包(wheel文件),特别是GDAL和Fiona,因为它们可能包含一些系统级的依赖。 bash pip install GDAL Fiona Pyproj Shapely 注意:在某些系统上,直接使用pip安装GDAL和Fiona可能会遇到问题,因为它们需要编译一些C/C++代码。如果遇到问题,你可以考虑使用conda(一个Python包、依赖和环境管理器)来安装这些库,或者从Unofficial Windows Binaries for Python Extension Packages这样的网站下载预编译的wheel文件。 安装geopandas: 在安装了所有依赖库之后,你可以使用pip来安装geopandas。 bash pip install geopandas 使用conda安装 如果你正在使用conda作为你的Python包管理器,那么安装geopandas和它的依赖可能会更简单一些。 创建一个新的conda环境(可选,但推荐): bash conda create -n geoenv python=3.x anaconda conda activate geoenv 其中3.x是你希望使用的Python版本。 安装geopandas: 使用conda-forge频道来安装geopandas,因为它提供了许多地理空间相关的包。 bash conda install -c conda-forge geopandas 这条命令会自动安装geopandas及其所有依赖。 注意事项 如果你在安装过程中遇到任何问题,比如编译错误或依赖问题,请检查你的Python版本和pip/conda的版本是否是最新的,或者尝试在不同的环境中安装。 某些库(如GDAL)可能需要额外的系统级依赖,如地理空间库(如PROJ和GEOS)。这些依赖可能需要单独安装,具体取决于你的操作系统。 如果你在Windows上遇到问题,并且pip安装失败,尝试从Unofficial Windows Binaries for Python Extension Packages网站下载相应的wheel文件,并使用pip进行安装。 脚本示例 虽然你的问题主要是关于如何安装geopandas,但如果你想要一个Python脚本来重命名文件夹下的文件,在原始名字前面加上字符串"geopandas",以下是一个简单的示例: python import os # 指定文件夹路径 folder_path = 'path/to/your/folder' # 遍历文件夹中的文件 for filename in os.listdir(folder_path): # 构造原始文件路径 old_file_path = os.path.join(folder_path, filename) # 构造新文件名 new_filename = 'geopandas_' + filename # 构造新文件路径 new_file_path = os.path.join(folder_path, new_filename) # 重命名文件 os.rename(old_file_path, new_file_path) print(f'Renamed "{filename}" to "{new_filename}"') 请确保将'path/to/your/folder'替换为你想要重命名文件的实际文件夹路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值