Android自定义ViewGroup实现选择面板

 更新时间:2022年07月22日 09:36:27   作者:猪飞啦~  
ViewGroup是上面提到的所有的父控件的父类;但ViewGroup是一个抽象类,它里面有一个抽象方法onLayout,这个方法的作用就是摆放它所有的子控件(安排位置),因为是抽象类,不能直接new对象,所以我们在布局文件中不能直接使用 ViewGroup

背景

在做社交类平台开发的小伙伴都躲不开选择社交个性标签的业务需求,那么实现这个UI效果我想大伙第一时间想到的必定是RecycleView或GridView,其实这两者都可以实现需求,但我们的标签长度是不固定的,有可能是4个字符也有可能是10个字符,这时使用这两者就很能实现根据每个标签的宽度来自适应换行显示,那么这时就离不开自定义ViewGroup

效果

至于我这里的效果为什么不根据字体的数量进行自适应宽度的问题,是因为我这边的产品要求每行显示四个且宽高一致,所以我在每个item外面加了一层RelativeLayout,需要自适应宽度的朋友可以在创建item时不要在item外面多加一层

思路

1,我们先把每一行的标签看作一个对象

2,在onMeasure()方法中获取ViewGroup宽度,减去padding值便是ViewGroup的可用宽度

3,获取所有的子View进行遍历,创建一个对象来存储每一行的标签view,每次添加一个标签view先判断剩余空间能否存放得下这个标签view,如果不能则换行

4,在onLayout()方法中进行布局,循环子view并调用其layout()方法进行布局,每布局一个子view就计算出下一个子view的x坐标,y坐标

完整代码

/**
 * create by lijianhui
 * on 2022-7-6
 * <p>
 * description: 自定义标签选择面板
 */
public class TagSelectView extends ViewGroup implements View.OnClickListener {
    private int mMaxWidth;
    private int mHorizontalSpace = DensityUtil.dp2px(5);
    private int mVerticalSpace = DensityUtil.dp2px(10);
    private List<RowTag> mRows = new ArrayList<>();
    private TagClickCallback mTagClickCallback;
    private int mTitleHeight;
    private boolean mUpdateTabState = true;
    public TagSelectView(@NonNull Context context) {
        super(context);
    }
    public TagSelectView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
    public TagSelectView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    /**
     * 测量宽高
     */
    @SuppressLint("DrawAllocation")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mRows.clear();
        // 获取总宽度
        int width = MeasureSpec.getSize(widthMeasureSpec);
        mMaxWidth = width - getPaddingStart() - getPaddingEnd();
        //测量子view
        int childCount = this.getChildCount();
        RowTag currentLine = null;
        for (int i = mTitleHeight > 0 ? 1 : 0; i < childCount; i++) {
            View childView = getChildAt(i);
            childView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
            childView.setOnClickListener(this);
            if (currentLine == null) {
                currentLine = new RowTag(mMaxWidth, mHorizontalSpace);
                currentLine.addTagView(childView);
                mRows.add(currentLine);
            } else {
                if (currentLine.canAddView(childView)) {
                    currentLine.addTagView(childView);
                } else {
                    currentLine = new RowTag(mMaxWidth, mHorizontalSpace);
                    currentLine.addTagView(childView);
                    mRows.add(currentLine);
                }
            }
        }
        //测量自己
        int height = getPaddingTop() + getPaddingBottom();
        for (int i = 0; i < mRows.size(); i++) {
            height += mRows.get(i).mHeight;
        }
        height += (mRows.size() - 1) * mVerticalSpace;
        height += mTitleHeight;
        setMeasuredDimension(width, height);
    }
    /**
     * 布局子view
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        left = getPaddingStart();
        top = getPaddingTop() + mTitleHeight;
        for (int i = 0; i < mRows.size(); i++) {
            // 获取行
            RowTag line = mRows.get(i);
            // 管理
            line.layoutView(top, left);
            // 更新高度
            top += line.mHeight;
            if (i != mRows.size() - 1) {
                top += mVerticalSpace;
            }
        }
    }
    /**
     * 添加标题
     *
     * @param view 标题
     */
    public void addTitleView(View view) {
        view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        mTitleHeight = view.getMeasuredHeight() + DensityUtil.dp2px(15);
        view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
        addView(view);
    }
    /**
     * 标签被点击
     *
     * @param v 点击的标签
     */
    @Override
    public void onClick(View v) {
        if (mUpdateTabState) {
            v.setSelected(!v.isSelected());
        }
        if (mTagClickCallback != null) {
            mTagClickCallback.tagClick(v);
        }
    }
    /**
     * 设置标签点击回调接口
     *
     * @param tagClickCallback 点击回调接口
     */
    public void setTagClickCallback(TagClickCallback tagClickCallback) {
        mTagClickCallback = tagClickCallback;
    }
    /**
     * 设置点击表情是否改变状态
     *
     * @param updateTabState true:改变
     */
    public void setUpdateTabState(boolean updateTabState) {
        this.mUpdateTabState = updateTabState;
    }
    /**
     * 设置水平间距
     *
     * @param horizontalSpace 间距
     */
    public void setHorizontalSpace(int horizontalSpace) {
        this.mHorizontalSpace = horizontalSpace;
    }
    /**
     * 标签点击回调接口
     */
    public interface TagClickCallback {
        void tagClick(View view);
    }
    /**
     * 每一行的数据
     */
    private static class RowTag {
        private final int mMaxWidth;
        private final int mHorizontalSpace;
        private final List<View> mTagViews;
        private int mUsedWidth;
        private int mHeight;
        public RowTag(int maxWidth, int horizontalSpace) {
            this.mMaxWidth = maxWidth;
            this.mHorizontalSpace = horizontalSpace;
            this.mTagViews = new ArrayList<>();
        }
        /**
         * 添加标签
         *
         * @param view 标签view
         */
        public void addTagView(View view) {
            int childWidth = view.getMeasuredWidth();
            int childHeight = view.getMeasuredHeight();
            if (mTagViews.size() == 0) {
                if (childWidth > mMaxWidth) {
                    mUsedWidth = mMaxWidth;
                } else {
                    mUsedWidth = childWidth + mHorizontalSpace;
                }
                mHeight = childHeight;
            } else {
                mUsedWidth += childWidth + mHorizontalSpace;
                mHeight = Math.max(childHeight, mHeight);
            }
            mTagViews.add(view);
        }
        /**
         * 判断是否可添加
         *
         * @param view 要添加的标签view
         * @return 如果剩余宽度可以装下要添加的标签view返回true
         */
        public boolean canAddView(View view) {
            if (mTagViews.size() == 0) {
                return true;
            }
            return view.getMeasuredWidth() <= (mMaxWidth - mUsedWidth - mHorizontalSpace);
        }
        /**
         * 布局标签
         *
         * @param t 头坐标
         * @param l 左坐标
         */
        public void layoutView(int t, int l) {
            int avg = 0;
            if (mTagViews.size() > 1) {
                avg = (mMaxWidth - mUsedWidth) / (mTagViews.size() - 1);
            }
            for (View view : mTagViews) {
                // 获取宽高 如需填充空余空间:measuredWidth = view.getMeasuredWidth() + avg
                int measuredWidth = view.getMeasuredWidth();
                int measuredHeight = view.getMeasuredHeight();
                // 重新测量
                view.measure(MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY));
                // 重新获取宽度值
                measuredWidth = view.getMeasuredWidth();
                int top = t;
                int left = l;
                int right = measuredWidth + left;
                int bottom = measuredHeight + top;
                // 指定位置
                view.layout(left, top, right, bottom);
                // 更新坐标
                l += measuredWidth + mHorizontalSpace;
            }
        }
    }
}

使用

    /**
     * 构建标签
     *
     * @param tagName 标签名称
     */
    private void buildTagView(String tagName, boolean social) {
        RelativeLayout relativeLayout = new RelativeLayout(getContext());
        SuperTextView superTextView = new SuperTextView(getContext());
        superTextView.setGravity(Gravity.CENTER);
        superTextView.setText(tagName.length() > 5 ? tagName.substring(0, 5) : tagName);
        superTextView.setSolid(social ? Color.parseColor("#A68CFF") : Color.TRANSPARENT);
        if (!social) {
            superTextView.setStrokeColor(Color.parseColor("#EDEDED"));
            superTextView.setStrokeWidth(DensityUtil.dp2px(1));
        }
        superTextView.setTextColor(social ? Color.WHITE : Color.parseColor("#727272"));
        superTextView.setTextSize(11);
        superTextView.setCorner(DensityUtil.dp2px(14));
        superTextView.setOnClickListener(this);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(DensityUtil.dp2px(70), DensityUtil.dp2px(28));
        relativeLayout.addView(superTextView, params);
        mBinding.tagSelectView.addView(relativeLayout);
    }

到此这篇关于Android自定义ViewGroup实现选择面板的文章就介绍到这了,更多相关Android ViewGroup内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Android开发必备知识 为什么说Kotlin值得一试

    Android开发必备知识 为什么说Kotlin值得一试

    为什么说值得一试,这篇文章主要为大家详细介绍了Android开发必备知识,Kotlin的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • Android学习之BottomSheetDialog组件的使用

    Android学习之BottomSheetDialog组件的使用

    BottomSheetDialog是底部操作控件,可在屏幕底部创建一个支持滑动关闭视图。本文将通过示例详细讲解它的使用,感兴趣的小伙伴可以了解一下
    2022-06-06
  • SQLiteStudio优雅调试Android手机数据库Sqlite(推荐)

    SQLiteStudio优雅调试Android手机数据库Sqlite(推荐)

    这篇文章主要介绍了SQLiteStudio优雅调试Android手机数据库Sqlite的相关资料,需要的朋友可以参考下
    2017-11-11
  • Android 无障碍服务 performAction 调用过程分析

    Android 无障碍服务 performAction 调用过程分析

    这篇文章主要介绍了Android 无障碍服务 performAction 调用过程分析,无障碍服务可以模拟一些用户操作,无障碍可以处理的对象,通过类 AccessibilityNodeInfo 表示,通过无障碍服务,可以通过它的performAction方法来触发一些action
    2022-06-06
  • Android图片描边效果的实现

    Android图片描边效果的实现

    这篇文章主要给大家介绍了Android图片描边效果的实现,在添加图片描边的时候我们用到的是图片蒙版技术,文中通过代码示例给大家介绍的非常详细,需要的朋友可以参考下
    2023-12-12
  • 详解Android开发录音和播放音频的步骤(动态获取权限)

    详解Android开发录音和播放音频的步骤(动态获取权限)

    这篇文章主要介绍了详解Android开发录音和播放音频的步骤(动态获取权限),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • Android编程之内存溢出解决方案(OOM)实例总结

    Android编程之内存溢出解决方案(OOM)实例总结

    这篇文章主要介绍了Android编程之内存溢出解决方案(OOM),结合实例实例总结分析了Android编程过程中常见的内存溢出情况与对应的解决方法,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-11-11
  • Android4.4 WebAPI实现拍照上传功能

    Android4.4 WebAPI实现拍照上传功能

    这篇文章主要介绍了Android4.4 WebAPI实现拍照上传功能,本文给出4.4版本后拍照上传的具体实现方法,感兴趣的小伙伴们可以参考一下
    2016-07-07
  • Android中卡顿优化布局详细介绍

    Android中卡顿优化布局详细介绍

    大家好,本篇文章主要讲的是Android中卡顿优化布局详细介绍,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01
  • android原生实现多线程断点续传功能

    android原生实现多线程断点续传功能

    这篇文章主要为大家详细介绍了android原生实现多线程断点续传功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07

最新评论