Android 自定义View 不规则的排布效果

自定义View继承ViewGroup,那么必须重新onLayout方法,确定每一个子元素的位置信息,重写onMeasure方法是测量每个子元素的大小,否则也是无法显示的,
getLayoutParams 是获取布局文件中配置的高度,只有设置具体的高度才能获取到,如果设置成wrap_content 或者是match_parent是获取不到的(这里是在onMeasure方法中进行获取的话是获取不到的)
之前对自定义的measure和layout理解的不是很深刻,最近项目不是很忙,就学习学习这块,写一个Demo,效果图如下:

这个自定义控件思路是:
1、确定哪些元素属于第一行,哪些元素属于第二行。。将所有的行对象存储在lineList里面去
2、根据lineList里面的内容来计算出MyFlowLayout的高度
3、将行对象里面的view对象,进行位置确定,让他们从左到右一字排开
4、细节的修改
onMeasure会被执行多次
别忘了最后一行
每一行的右边需要对齐,需要进行行里面每一个View对象的重新测量
考虑padding的情况
当一行中元素高度不一致的情况之下,这一行的高度应该以最高的元素为主,其他的元素居中显示
自定义控件代码如下:

public class MyFlow extends ViewGroup {
    public MyFlow(Context context) {
        this(context,null);
    }

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

    public MyFlow(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    private int mUsedWidth = 0;
    private Line mCurrentLine;
    private ArrayList<Line> lineList = new ArrayList<>();
    //水平间距
    private int mHorizontalSpacing = UiUtils.dip2px(10);
    //垂直间距
    private int mVerticalSpacing = UiUtils.dip2px(10);
    @SuppressLint("DrawAllocation")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        reset();
        
        //获取控件的宽度
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        widthSize = widthSize - getPaddingLeft() - getPaddingRight();
        //这种方式有可能是为0
        System.out.println("widthSize :" + widthSize);
        int count = getChildCount();
        mCurrentLine = new Line();
        for (int i = 0; i < count; i++) {
            View childView = getChildAt(i);
            //测量给予约束,宽度最多只有屏幕的宽度
            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
            childView.measure(childWidthMeasureSpec,0);
            //获取childView的宽度
            int childWidth = childView.getMeasuredWidth();
            mUsedWidth = mUsedWidth + childWidth;
            if (mUsedWidth < widthSize){
                //表示在同一行
                mCurrentLine.addLineView(childView);
                mUsedWidth = mUsedWidth + mHorizontalSpacing;
            }else {
                //表示需要换行
                newLine();
                mCurrentLine.addLineView(childView);
                mUsedWidth = mUsedWidth + childWidth + mHorizontalSpacing;
            }
        }
        //添加最后一个元素
        if (!lineList.contains(mCurrentLine)){
            lineList.add(mCurrentLine);
        }
        System.out.println("lineList.size() :" + lineList.size());
        int totalHeight = 0;
        for (int i = 0; i < lineList.size(); i++) {
            Line line = lineList.get(i);
            totalHeight = totalHeight + line.lineHeight + mVerticalSpacing;
        }
        //减去最后一行的高度间隔
        totalHeight = totalHeight - mVerticalSpacing;

        totalHeight = totalHeight + getPaddingTop() + getPaddingBottom();
        //确定控件的高度
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(totalHeight, MeasureSpec.EXACTLY);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private void reset() {
        lineList.clear();
        mUsedWidth = 0;
    }

    private void newLine() {
        //把装
        lineList.add(mCurrentLine);
        //重新创建装载这行view的对象
        mCurrentLine = new Line();
        //把已经用的mUsedWidth置位0
        mUsedWidth = 0;
    }
    class Line{
        public int lineHeight;
        private ArrayList<View> lineViews = new ArrayList<>();
        public void addLineView(View childView){
            if (!lineViews.contains(childView)){
                lineViews.add(childView);
                if (lineHeight < childView.getMeasuredHeight()){
                    lineHeight = childView.getMeasuredHeight();
                }
            }
        }
        public void layout(int left,int top){
            for (int i = 0; i < lineViews.size(); i++) {
                View childView = lineViews.get(i);
                System.out.println("measuredWidth :" + childView.getMeasuredWidth());
                childView.layout(left,top,left + childView.getMeasuredWidth(),top + childView.getMeasuredHeight());
                left = left + childView.getMeasuredWidth() + mHorizontalSpacing;

//                int topOffset = lineHeight/2 - childView.getMeasuredHeight()/2;
//                System.out.println("topOffset :" + topOffset);
//                childView.layout(left,top+topOffset,left + childView.getMeasuredWidth(),top+topOffset+childView.getMeasuredHeight());
//                left = left + childView.getMeasuredWidth() + mHorizontalSpacing;
            }
        }
        //这个是均等份从新测量
        public void reMeasure(){
            int surplusWidth = 0;//剩余部分宽度
            int totalWidth = 0;//总共所占宽度
            int equalWidth = 0;//均等份宽度
            for (int i = 0; i < lineViews.size(); i++) {
                View childView = lineViews.get(i);
                totalWidth = totalWidth + childView.getMeasuredWidth() + mHorizontalSpacing;
            }
            totalWidth = totalWidth - mHorizontalSpacing;
            surplusWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - totalWidth;
            equalWidth = surplusWidth / lineViews.size();

            for (int i = 0; i < lineViews.size(); i++) {
                View childView = lineViews.get(i);
                int measuredWidth = childView.getMeasuredWidth();
                int widthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth + equalWidth, MeasureSpec.EXACTLY);
                int measuredHeight = childView.getMeasuredHeight();
                int measuredHeightSpec = MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.AT_MOST);
                childView.measure(widthMeasureSpec,measuredHeightSpec);
            }
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int top = getPaddingTop();
        int left = getPaddingLeft();
        for (int i = 0; i < lineList.size(); i++) {
            Line line = lineList.get(i);
            line.reMeasure();//在摆放之前从新测量
            line.layout(left,top);//确定摆放的位置
            top = top + line.lineHeight + mVerticalSpacing;
        }
    }
}

所用到的工具类代码:

public class UiUtils {

    //获取全局Context对象
    public static Context getContext() {
        return MyApplication.instance.context;
    }
    public static int getColor(int resId) {
        return getContext().getResources().getColor(resId);
    }

    //产生随机的颜色值  90~230
    public static int getRandomColor() {
        Random random= new Random();
        int red =  90 + random.nextInt(141);;
        int green= 90 + random.nextInt(141);;
        int blue=  90 + random.nextInt(141);;
        int color = Color.rgb(red, green, blue);
        return color;
    }
    //获取颜色的状态选择器
    public static ColorStateList getColorStateList(int resId) {
        return getContext().getResources().getColorStateList(resId);
    }

    public static int getDimen(int resId) {
        return getContext().getResources().getDimensionPixelSize(resId);
    }

    //dip2px
    public static int dip2px(int dip) {
        //屏幕密度
        float density = getContext().getResources().getDisplayMetrics().density;
        return (int) (dip * density + 0.5f);
    }

    //px2dip
    public static int px2dip(int px) {
        //屏幕密度
        float density = getContext().getResources().getDisplayMetrics().density;
        return (int) (px/density + 0.5f);
    }
    //代码中创建shape标签对应的对象
    public static GradientDrawable getShape(int radius,int color) {
        GradientDrawable gradientDrawable = new GradientDrawable();
        gradientDrawable.setShape(GradientDrawable.RECTANGLE);
        gradientDrawable.setCornerRadius(radius);
        gradientDrawable.setColor(color);
        return gradientDrawable;
    }
    //代码中获取一个状态选择器  对应的类StateListDrawable
    public static StateListDrawable getSelector(Drawable pressedDrawable,Drawable normalDrawable) {
        StateListDrawable stateListDrawable = new StateListDrawable();
        stateListDrawable.addState(new int[]{android.R.attr.state_pressed},pressedDrawable);
        stateListDrawable.addState(new int[]{},normalDrawable);
        return stateListDrawable;
    }
    public static int getScreenWidth() {
        DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
        return displayMetrics.widthPixels;
    }

}

MainActivity中代码:

public class CustomActivity extends AppCompatActivity {
    private ArrayList<String> arrayList = new ArrayList<>();
    private ScrollView scrollView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_custom);
        MyFlow myFlowLayout = new MyFlow(this);
        scrollView = findViewById(R.id.scrollView);
        myFlowLayout.setPadding(UiUtils.dip2px(5),UiUtils.dip2px(5),UiUtils.dip2px(5),UiUtils.dip2px(5));
        arrayList.add("QQ");
        arrayList.add("腾讯");
        arrayList.add("爱奇艺");
        arrayList.add("Boss直聘");
        arrayList.add("智联招聘");
        arrayList.add("火狐浏览器");
        arrayList.add("百度网盘");
        arrayList.add("微信");
        arrayList.add("支付宝");
        arrayList.add("虾米音乐-听见不同");
        arrayList.add("蚂蚁财富");
        arrayList.add("鲁大师评测");
        arrayList.add("盛宏必思恩公司");
        arrayList.add("QQ飞车");
        arrayList.add("UC浏览器");
        arrayList.add("爱奇艺视频");
        arrayList.add("火狐");
        arrayList.add("Google地图");
        arrayList.add("百度地图");
        arrayList.add("盛宏电气");
        arrayList.add("美图秀秀");
        arrayList.add("拉钩");
        arrayList.add("TCL");
        arrayList.add("诚迈科技");
        arrayList.add("钉钉");
        arrayList.add("腾讯视频");
        MyFlow myFlow = new MyFlow(this);
        for (int i = 0; i < arrayList.size(); i++) {
            TextView textView = new TextView(this);
            ;
            //press
            UiUtils.getShape(UiUtils.dip2px(5),UiUtils.getRandomColor());
            //nomal
            UiUtils.getShape(UiUtils.dip2px(5),Color.rgb(55,55,55));
            StateListDrawable selector = UiUtils.getSelector(UiUtils.getShape(UiUtils.dip2px(5), Color.rgb(55, 55, 55)),UiUtils.getShape(UiUtils.dip2px(5), UiUtils.getRandomColor()));
            textView.setTag(arrayList.get(i));
            textView.setOnClickListener(listener);
            textView.setBackgroundDrawable(selector);
            textView.setTextColor(Color.WHITE);
            textView.setText(arrayList.get(i));
            textView.setTextSize(UiUtils.dip2px(10));
            textView.setGravity(Gravity.CENTER);
            textView.setPadding(UiUtils.dip2px(5),UiUtils.dip2px(5),UiUtils.dip2px(5),UiUtils.dip2px(5));
            myFlowLayout.addView(textView);
        }
        scrollView.addView(myFlowLayout);

    }
    private View.OnClickListener listener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            String msg = (String) v.getTag();
            UiUtils.toast(msg);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值