安卓流式布局(可换行的标签)

    xiaoxiao2025-06-03  40

    最近,公司的项目中需要展示商品的规格和属性,但是不同的商品属性个数也是不一样的,

    怎么能够让超过一行的属性自动换行呢?这就需要用到我们的流式布局,下面先看看效果图

            这里先声明一下:这个自定义控件是基于http://blog.csdn.net/jdsjlzx/article/details/45042081?ref=myread

    这篇文章更改的,流式是怎么实现的还是请先看完上边这篇文章.

    在将楼主的源码下载下来使用的时候遇到以下几个问题,本文将围绕这几个小问题进行讲解

      首先楼主的这个自定义控件始终默认铺满屏幕的,但是感觉很奇怪,因为在onMeasure这个方法中

              已经根据控件设置的模式测量模式进行过计算了,按道理说不应该是铺满屏幕的!

             而且我设置的高度是wrap_content(自适应)   打了断点试的时候  发现测量出来的距离并不是铺满屏幕,

             而是真是高度  又认认真真的看了一下测量发方法  发现楼主将super.onMeasure(widthMeasureSpec, heightMeasureSpec);放在了

             方法结束的位置  是不是很扯   这句话放在最后的话,就相当于我测量了半天,测量到最后不使用这个

            测量值,而使用其父类(ViewGroup)的测量结果,也就是默认结果 

        解决方案:把这句话移到方法首句或者直接删除这句话

    接着  确实是自适应了  但是只是单纯的换行不行   我们需要点击之后知道我们选中了那个   并且选中的这个背景颜色需要变

        简单地分析下,我们需要做以下几件事:

            1.两个自定义属性  分别是选中和未选中的背景颜色

             2.获取所有控件的的位置

             3.判断点击的点是不是包含在某个子控件中

             4,如果是包含在某个子控件中,设置回调

    下面我们具体去完成我们这几个步骤:

    1.两个自定义属性

         先在values下创建一个attrs的xml文件   分别代表选中和未选中的两个状态

    <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="flowlayout"> <attr name="back_selected" format="reference" /> <attr name="back_un_selected" format="reference" /> </declare-styleable> </resources>      接着  创建两个shape   分别代表选中和未选中时的背景颜色状态

           选中状态

    <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <corners android:radius="1dp" /> <stroke android:width="2dp" android:color="#ff6600" /> <solid android:color="#ffffff" /> </shape>      未选中状态 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <corners android:radius="1dp" /> <stroke android:width="2dp" android:color="#000000" /> <solid android:color="#ffffff" /> </shape>      接着在自定义控件的构造方法中获取这两个自定义属性:

    public FlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); childLocationList = new HashMap<Integer, Rect>(); // 获取自定义属性的值 TypedArray typedArray = context.getTheme().obtainStyledAttributes( attrs, R.styleable.flowlayout, defStyle, 0); int back_selected = typedArray.getResourceId( R.styleable.flowlayout_back_selected, 0);// 选中时的背景资源id int back_unselected = typedArray.getResourceId( R.styleable.flowlayout_back_un_selected, 0);// 未选中时的背景资源id typedArray.recycle(); }     最后在  布局文件中为这两个属性赋值  注意命名空间

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:flayout="http://schemas.android.com/apk/res/com.czm.flowlayout" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="wrap_content" > <com.czm.flowlayout.FlowLayout android:id="@+id/flowlayout" android:layout_width="match_parent" android:layout_height="wrap_content" flayout:back_selected="@drawable/shape_label_selected" flayout:back_un_selected="@drawable/shape_label_unselected" > </com.czm.flowlayout.FlowLayout> </RelativeLayout> 2.获得所有控件的位置

        获得子控件的位置    应该在我们指定了子控件的位置之后再去获取  也就是说在onLayout方法的最后

          我们添加一个方法  getAllChildViewLocation

         我们先分析一下思路 :楼主当时是这样存储所有的子控件的

    <span style="font-size:14px;"> // 存储所有子View private List<List<View>> mAllChildViews = new ArrayList<List<View>>();</span>     先将每一行的控件存在一个List集合中  再将所有的行List再存到List集合中

           这样  我们就可以根据行数获取到指定行中所有的控件  再获取指定行指定第几个的控件

             我们能拿到这个具体的控件  就能拿到控件所在的位置

            将拿到的位置存到一个键值对类型的集合中      键表示在这个流式布局中第几个   值是对应控件的位置  

            至于为什么要存在一个键值对类型的集合中,等会再说

             接着怎么去存  

         看了这个图,应该知道键怎么存了

          值是获取了子控件所在的矩形,因为矩形有个方法contains(int x, int y)   可以判断一个点是否包含在这个矩形中

           创建这个集合最好是在构造方法中创建

    private HashMap<Integer, Rect> childLocationList = new HashMap<Integer, Rect>();     获取并记录子控件的位置记录

    /** * 获取所有的子控件的位置并记录 */ private void getAllChildViewLocation() { int countBefore = 0; for (int i = 0; i < mAllChildViews.size(); i++) { if (i > 0) { countBefore += mAllChildViews.get(i - 1).size(); } for (int j = 0; j < mAllChildViews.get(i).size(); j++) { View view = mAllChildViews.get(i).get(j); Rect rect = new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); childLocationList.put(countBefore + j, rect); } } }3. 判断点击的是哪个

          现在有控件的位置了  我们只要能知道点的位置就够了  下面重写onTouchEvent方法

    /** * 这里我们需要判断子控件是否被点击 点击的是哪个子控件 */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX = (int) event.getX(); downY = (int) event.getY(); downTime = SystemClock.currentThreadTimeMillis(); break; case MotionEvent.ACTION_UP: long upTime = SystemClock.currentThreadTimeMillis(); if (upTime - downTime <= 50) {// 如果手指按下和手指离开键盘的时间小于50毫秒有效 for (int i = 0; i < childLocationList.size(); i++) { if (childLocationList.get(i).contains(downX, downY)) {// 如果子控件所在位置(矩形)包括这个点 if (onLabelSelectedListener != null) { onLabelSelectedListener.onSelected(i);//这个i是什么 就是子控件是第几个 直接把这个传过去 现在知<span style="white-space:pre"> </span>//道这个集合的键做什么用了吧 } // 将记录的上一个控件颜色改成未选中状态 if (lastSelectedPosition != -1) { TextView lastSelectedView = (TextView) getChildAt(lastSelectedPosition); lastSelectedView .setBackgroundResource(back_unselected); } // 将当前选中的控件背景改成选中状态 并记录 TextView childAt = (TextView) getChildAt(i); childAt.setBackgroundResource(back_selected); lastSelectedPosition = i; break; } } } break; default: break; } return true; }    4.设置回调和改变状态 <span style="white-space:pre"> </span>/** * 子控件(标签)选中监听 * * @author HaiPeng * */ public interface OnLabelSelectedListener { void onSelected(int position); } /** * 设置子控件选中时的监听 * * @param onLabelSelectedListener */ public void setOnLabelSelectedListener( OnLabelSelectedListener onLabelSelectedListener) { this.onLabelSelectedListener = onLabelSelectedListener; } /** * 记录上一次点击的是哪个子控件 */ private int lastSelectedPosition = -1; private OnLabelSelectedListener onLabelSelectedListener; 对了在测量之前给子控件设置上没有选中的背景 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 父控件传进来的宽度和高度以及对应的测量模式 int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); int modeHeight = MeasureSpec.getMode(heightMeasureSpec); // 如果当前ViewGroup的宽高为wrap_content的情况 int width = 0;// 自己测量的 宽度 int height = 0;// 自己测量的高度 // 记录每一行的宽度和高度 int lineWidth = 0; int lineHeight = 0; // 获取子view的个数 int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); <span style="color:#FF6666;">child.setBackgroundResource(back_unselected);</span> // 测量子View的宽和高 measureChild(child, widthMeasureSpec, heightMeasureSpec); // 得到LayoutParams MarginLayoutParams lp = (MarginLayoutParams) child .getLayoutParams(); // 子View占据的宽度 int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; // 子View占据的高度 int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; // 换行时候 if (lineWidth + childWidth > sizeWidth) { // 对比得到最大的宽度 width = Math.max(width, lineWidth); // 重置lineWidth lineWidth = childWidth; // 记录行高 height += lineHeight; lineHeight = childHeight; } else {// 不换行情况 // 叠加行宽 lineWidth += childWidth; // 得到最大行高 lineHeight = Math.max(lineHeight, childHeight); } // 处理最后一个子View的情况 if (i == childCount - 1) { width = Math.max(width, lineWidth); height += lineHeight; } } // wrap_content setMeasuredDimension(modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width, modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height); } 最后我们在MainActivity中调用一下 <span style="white-space:pre"> </span>private void initChildViews() { // TODO Auto-generated method stub mFlowLayout = (FlowLayout) findViewById(R.id.flowlayout); MarginLayoutParams lp = new MarginLayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); lp.leftMargin = 5; lp.rightMargin = 5; lp.topMargin = 5; lp.bottomMargin = 5; for (int i = 0; i < mNames.length; i++) { TextView view = new TextView(this); view.setText(mNames[i]); view.setTextColor(Color.BLACK); view.setPadding(5, 5, 5, 5); // view.setBackgroundDrawable(getResources().getDrawable(R.drawable.textview_bg)); mFlowLayout.addView(view, lp); } mFlowLayout.setOnLabelSelectedListener(new OnLabelSelectedListener() { @Override public void onSelected(int position) { Toast.makeText(MainActivity.this, "第" + position + "个被点击了", Toast.LENGTH_SHORT).show(); } }); } 效果图在最上边,大家已经看过了

    点击这里下载源码

    转载请注明原文地址: https://ju.6miu.com/read-1299565.html
    最新回复(0)