Android---自定义ViewGroup

博客介绍了如何自定义一个ViewGroup子类VGTest,实现根据宽度自动换行的文本布局,并添加了点击事件监听功能。在VGTest中,通过onMeasure方法测量子View并布局,利用onLayout方法定位每个子View的位置。同时,提供了设置间距和点击监听的方法,使得布局更具灵活性。
摘要由CSDN通过智能技术生成

效果图:
在这里插入图片描述
ViewGroup子类:

  • 创建过程:
    1,构造方法
    2,添加子View方法
    3,测量(onMeasure),确认父类size
    4,调整子View的布局(onLayout)
/**
 * 创建过程:
 *  1,构造方法
 *  2,添加子View方法
 *  3,测量(onMeasure),确认父类size
 *  4,调整子View的布局(onLayout)
 */

public class VGTest extends ViewGroup {

    //存放传过来的子View数据
    private List<String> mData;         //用于存储所有子View的内容
    private List<List<View>> mLines;    //集合内的每个元素代表每一行。


    public VGTest(Context context) {
        this(context,null);
    }

    public VGTest(Context context, AttributeSet attrs) {
        this(context,attrs,0);
    }

    public VGTest(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mData=new ArrayList<>();
        mLines=new ArrayList<>();
    }

    //添加子View
    public void setView(List<String> data) {
        mData.clear();
        mData.addAll(data);
        //先清空原来的内容
        removeAllViews();
        for (String datum : data) {
            TextView textView=new TextView(getContext());
            textView.setText(datum);
            addView(textView);
        }
    }


    //测量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //得到父类提供的大小
        int parentWidth=MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);

        //获得子View个数
        int childCount = getChildCount();
        if(childCount==0){
            return;
        }

        //先清空
        mLines.clear();
        //添加默认行
        List<View> newLine=new ArrayList<>();
        mLines.add(newLine);

        int width = MeasureSpec.makeMeasureSpec(parentWidth, MeasureSpec.AT_MOST);
        int height = MeasureSpec.makeMeasureSpec(parentHeight, MeasureSpec.AT_MOST);


        for(int i=0;i<childCount;i++){
        //获得子View个体
            View childAt = getChildAt(i);

            //如果这个View在new时设置是不可见的,则直接跳到下一个循环
            if(childAt.getVisibility()!=VISIBLE){
                continue;
            }
            measureChild(childAt,width,height);


            if(newLine.size()==0){
                //可以添加
                newLine.add(childAt);
            }else{
                //判断是否可以添加进该行(计算组合宽度是否超出限定宽度,若超出则需要换行)
                boolean isCanBeAdd = checkChildCanBeAdd(newLine,childAt,parentWidth);
                if(isCanBeAdd){
                    newLine.add(childAt);
                }else {
                    newLine=new ArrayList<>();
                    mLines.add(newLine);
                }

            }
        }
        //根据尺寸j计算所有行高
        View child = getChildAt(0);
        int childHeight = child.getMeasuredHeight();
        int totalChildHeight = childHeight * mLines.size();
        setMeasuredDimension(parentWidth,totalChildHeight);

    }

    //判断是否可以添加进该行
    private boolean checkChildCanBeAdd(List<View> newLine, View childAt, int parentWidth) {

        int totalWidth=0;

        for (View view : newLine) {
            totalWidth+=view.getMeasuredWidth();
        }
        totalWidth+=childAt.getMeasuredWidth();

        //如果超出限制宽度,则不可以再添加(换行)
        //否则可以添加
        return totalWidth<=parentWidth;
    }

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

//        View childAt = getChildAt(0);
//        childAt.layout(0,0,childAt.getMeasuredWidth(),childAt.getMeasuredHeight());
//        View childAt1 = getChildAt(1);
//        childAt1.layout(childAt.getRight(),0,childAt.getLeft()+childAt1.getMeasuredWidth(),childAt.getLeft()+childAt1.getMeasuredHeight());
//        ......
        View fristChild=getChildAt(0);
        int currentLeft=0;
        int currentRight=0;
        int currentTop=0;
        int currentBottom=fristChild.getMeasuredHeight();

        for (List<View> mLine : mLines) {
            for (View child : mLine) {

                int width = child.getMeasuredWidth();
                int height =child.getMeasuredHeight();

                currentRight+=width;
                currentBottom+=height;

                child.layout(currentLeft,currentTop,currentRight,currentBottom);
                currentLeft=currentRight;

            }
            currentTop+=getChildAt(0).getMeasuredHeight();
            currentLeft=0;
            currentRight=0;
            currentBottom+=fristChild.getMeasuredHeight();
        }
    }
}

Main:

public class MainActivity extends AppCompatActivity {


    private final String TAG="MainActivity:";
    
    private VGTest test;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        test=findViewById(R.id.vg_test);
        Log.e(TAG+"----------------------------",test+"");

        List<String> data=new ArrayList<>();

        data.add("asdasd");
        data.add("zxczxc");
        data.add("qweqwe");
        data.add("fghrty");
        data.add("123123");
        data.add("456456");
        data.add("789789");
        data.add("aiopiop");
        data.add("jkljkl");
        data.add("p[]p[]");
        Log.e("TAG",data.size()+"");
        test.setView(data);
    }
}

进阶实践:
效果图:
在这里插入图片描述
ViewGroup子类:

public class VGTest extends ViewGroup  {

    //存放传过来的子View数据
    private List<String> mData;         //用于存储所有子View的内容
    private List<List<View>> mLines;    //集合内的每个元素代表每一行。

    private int leftMargin=15;   //每个子View间的左边间距
    private int toptMargin=5;   //每个子View间的与上面子View的间距
    private OnTextClickListener textListener;

    //-----构造方法---------
    public VGTest(Context context) {
        this(context,null);
    }

    public VGTest(Context context, AttributeSet attrs) {
        this(context,attrs,0);
    }

    public VGTest(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mData=new ArrayList<>();
        mLines=new ArrayList<>();
    }
    //--------------------

    //添加子View
    public void setView(List<String> data) {
        mData.clear();
        mData.addAll(data);
        //先清空原来的内容
        removeAllViews();
        for (String datum : data) {
            final TextView textView= (TextView) LayoutInflater.from(getContext()).inflate(R.layout.text_demo_style,this,false);
            textView.setText(datum);

            final String text=datum;

            //---点击事件监听-----
            textView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(textListener!=null) {
                        textListener.getItemTextContext(v, text);
                    }
                }
            });

            addView(textView);
        }
    }


    //测量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //得到父类提供的大小
        int parentWidth=MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);

        //获得子View个数
        int childCount = getChildCount();
        if(childCount==0){
            return;
        }

        //先清空
        mLines.clear();
        //添加默认行
        List<View> newLine=new ArrayList<>();
        mLines.add(newLine);

        int width = MeasureSpec.makeMeasureSpec(parentWidth, MeasureSpec.AT_MOST);
        int height = MeasureSpec.makeMeasureSpec(parentHeight, MeasureSpec.AT_MOST);


        for(int i=0;i<childCount;i++){
        //获得子View个体
            View childAt = getChildAt(i);

            //如果这个View在new时设置是不可见的,则直接跳到下一个循环
            if(childAt.getVisibility()!=VISIBLE){
                continue;
            }
            measureChild(childAt,width,height);


            if(newLine.size()==0){
                //可以添加
                newLine.add(childAt);
            }else{
                //判断是否可以添加进该行(计算组合宽度是否超出限定宽度,若超出则需要换行)
                boolean isCanBeAdd = checkChildCanBeAdd(newLine,childAt,parentWidth);
                if(!isCanBeAdd){
                    newLine=new ArrayList<>();
                    mLines.add(newLine);
                }
                newLine.add(childAt);
            }
        }
        //根据尺寸j计算所有行高
        View child = getChildAt(0);
        int childHeight = child.getMeasuredHeight();
        int totalChildHeight = childHeight * mLines.size()+toptMargin*(mLines.size()+1)+getPaddingBottom()+getPaddingTop();
        setMeasuredDimension(parentWidth,totalChildHeight);

    }

    //判断是否可以添加进该行
    private boolean checkChildCanBeAdd(List<View> newLine, View childAt, int parentWidth) {

        int totalWidth=leftMargin+getPaddingLeft();

        for (View view : newLine) {
            totalWidth+=view.getMeasuredWidth()+leftMargin;
        }
        totalWidth+=childAt.getMeasuredWidth()+leftMargin+getPaddingRight();

        //如果超出限制宽度,则不可以再添加(换行)
        //否则可以添加
        return totalWidth<=parentWidth;
    }


    //布局子View
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        View fristChild=getChildAt(0);
        //用于标记子View的位置
        int currentLeft=leftMargin+getPaddingLeft();
        int currentRight=leftMargin+getPaddingLeft();
        int currentTop=toptMargin+getPaddingTop();
        int currentBottom=fristChild.getMeasuredHeight()+toptMargin+getPaddingTop();

        //循环每一行
        for (List<View> mLine : mLines) {

            //对一行内的View进行布局
            for (View child : mLine) {

                int width = child.getMeasuredWidth();

                currentRight+=width;
                Log.e("TAG-------------",""+currentRight+"----------"+width);
                Log.e("TAG-------------",""+(getMeasuredWidth()-leftMargin));
                if(currentRight >(getMeasuredWidth()-leftMargin-getPaddingRight())){
                    Log.e("TAG-------------","asdasd");
                    currentRight = getMeasuredWidth()-leftMargin-getPaddingRight();
                }
                //为子View排放位置
                child.layout(currentLeft,currentTop,currentRight,currentBottom);

                currentLeft=currentRight+leftMargin;
                currentRight+=leftMargin;
            }

            currentLeft=leftMargin+getPaddingLeft();
            currentRight=leftMargin+getPaddingLeft();
            currentTop+=getChildAt(0).getMeasuredHeight()+toptMargin;
            currentBottom+=fristChild.getMeasuredHeight()+toptMargin;
        }
    }


    //-------设置监听-----
    public void setOnTextListener(OnTextClickListener textListener){
         this.textListener=textListener;
    };
    //-------监听接口-----
    public interface OnTextClickListener{
        void getItemTextContext(View view,String text);
    }
}

R.layout.text_demo_style:
自定义TextView(background,textColor代码就不贴出来了,自己再写一份)

<TextView
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:hint="汉堡包"
    android:gravity="center"
    android:paddingLeft="15dp"
    android:paddingTop="15dp"
    android:paddingBottom="15dp"
    android:paddingRight="15dp"
    android:background="@drawable/text_demo_press"
    android:textColor="@drawable/text_color_text"
    android:singleLine="true"
    android:maxLength="5"
    android:ellipsize="end">

</TextView>

对布局文件加入Padding属性:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#000000"
        tools:context=".MainActivity"
        android:orientation="vertical">


    <com.example.vgdemo.VGTest
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="25dp"
        android:id="@+id/vg_test"/>


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值