自定义Viewgroup实现流式布局(2):条件换行的自定义view

只是实例,因为Textview就带换行功能。

上一篇最后说canvas.darwText可以来绘制view,实现类似textview的显示,但是超出控件长度就会看不到,并不会换行;至于一段文字计算他在给定长度上如何换行等等很麻烦,这里只举例:如果文字一行可以容纳则一行显示,如果一行不能显示则每行只显示7个字符。

注:这里只处理wrap和match的情况,如果给指定宽度还需要去判断宽度能不能放下7个字符的问题。

这里只说自定义view的情况,自定义viewgroup的先放下:

(1).onMeasure必须做处理,当写wrap的时候必须手动计算宽高:
当一行可以容纳时,宽就是文字的宽,高就是文字的高;
//因为默认写wrap时候相当于match,所以上面测量的width其实就是match也就是屏幕的宽度,实际可以写字的区域是减去左右pading的区域
int canUseWidth = widthSize - getPaddingLeft() - getPaddingRight();
当一行不能容纳时,宽就是当前所有行文字宽度最大那行的宽度(同样7个字符,中英文不一样)+两个padding,高就是行数*行高+两个padding。
计算文字所占宽高的方法如下:
paint = new Paint();
paint.setTextSize(SizeUtil.sp2px(getContext(), 20.0f));
paint.setColor(Color.parseColor("#000000"));
paint.setAntiAlias(true);//抗锯齿
paint.setFilterBitmap(true);//位图过滤
;
rect = new Rect();
//得到写这些文字需要占据的方形区域
paint.getTextBounds(text, 0, text.length(), rect);
判断mode,如果是EXACTLY,那set的时候就是MeasureSpec.getSize的尺寸;
如果不是,那就是上面计算出来的宽高。
(2).draw文字
通过onMeasure已经计算出来warp时文字所占的区域,但是文字并没有被绘制上去。
onMeasure的时候已经知道能分几行,然后对文字也进行了分段,现在把这若干段文字分别画上去就可以了。
可能会想到onLayout,但是这是view,不是ViewGroup,所以只能多canvas.draw几次,只是每次draw的时候基线的坐标不同。

实例代码如下:

 

//伪换行的textview
public class MyTextview extends View {

    public void setText(String text) {
        this.text = text;
        initView();
        invalidate();
    }

    public MyTextview(Context context) {
        super(context);
        initView();
    }

    public MyTextview(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initAtters(context, attrs);
        initView();

    }

    public MyTextview(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAtters(context, attrs);
        initView();

    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public MyTextview(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initAtters(context, attrs);
        initView();

    }

    private void initAtters(Context context, @Nullable AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyTextview);
        text = array.getString(R.styleable.MyTextview_texcontent);

    }

    //画笔
    Paint paint;
    //文字所占的区域,用来确定文字的起始坐标
    Rect rect;
    String text = "需要显示的文字";

    private void initView() {
        Log.e("init里面放的数字是", text);
        if (paint == null) {
            paint = new Paint();
            paint.setTextSize(SizeUtil.sp2px(getContext(), 20.0f));
            paint.setColor(Color.parseColor("#000000"));
            paint.setAntiAlias(true);
            paint.setFilterBitmap(true);//位图过滤
            ;
        }
        if (rect == null) {
            rect = new Rect();
        }

        paint.getTextBounds(text, 0, text.length(), rect);
    }

    String TAG = "MyTextview";

    @Override
    protected void onDraw(Canvas canvas) {
        //super.onDraw(canvas);
        Log.e(TAG, "onDraw: ");
        //为了让文字纵向居中,计算正确的Y值
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        //ascent的绝对值=descent+2*(文字纵向中线与基准线的高度差)
        //float baseline = 0;
        //Math.abs(fontMetrics.ascent) =fontMetrics.descent+2*(baseline-height/2);

        if (lineNum > 1) {
            //绘制文字
            int startY = getPaddingTop();
            for (int i = 0; i < rowStrings.size(); i++) {
                Rect rect_T = new Rect();
                //得到写这些文字需要占据的方形区域
                paint.getTextBounds(rowStrings.get(i), 0, rowStrings.get(i).length(), rect_T);
                int height = rect_T.height();
                float y = height / 2 + (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2;
                canvas.drawText(rowStrings.get(i), 0 + getPaddingLeft(), startY + y, paint);
                startY = startY + height;
            }

        } else {
            float y = rect.height() / 2 + (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2;
            canvas.drawText(text, 0 + getPaddingLeft(), y + getPaddingTop(), paint);
        }


    }

    List<String> rowStrings;
    int lineNum;

    //模拟一行可以放下就不换行,如果大于1行每行只容纳五个字符(只是模拟,实际操作还需要判断当前位置一行还能不能放下5个字符)
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //得到写这些文字需要占据的方形区域
        Log.e(TAG, "onMeasure: ");
        rowStrings = new ArrayList<>();
        lineNum = 0;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);   //获取宽的模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec); //获取高的模式
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);   //获取宽的尺寸
        int heightSize = MeasureSpec.getSize(heightMeasureSpec); //获取高的尺寸
        //判断,如果文字的长度大于宽度就换行
        //因为默认写wrap时候相当于match,所以上面测量的width其实就是match也就是屏幕的宽度
        int canUseWidth = widthSize - getPaddingLeft() - getPaddingRight();
        if (rect.width() > canUseWidth) {
//表示一行放不下
            //一行只容纳7个字符
            double rows = text.length() / 7.0;
            lineNum = (int) Math.ceil(rows);
            //一行文字所包含的文字数
            for (int i = 0; i < lineNum; i++) {
                if (i == lineNum - 1) {
                    rowStrings.add(text.substring(i * 7, text.length()));
                } else {
                    rowStrings.add(text.substring(i * 7, (i + 1) * 7));
                }

            }
        } else {
            lineNum = 1;
        }


        int width;
        int height;

        if (widthMode == MeasureSpec.EXACTLY) {
            //如果match_parent或者具体的值,直接赋值
            width = widthSize;
        } else {
            //如果是wrap_content,我们要得到控件需要多大的尺寸
            float textWidth = rect.width();   //文本的宽度
            //控件的宽度就是文本的宽度加上两边的内边距。内边距就是padding值,在构造方法执行完就被赋值
            //根据行数来计算宽高
            if (lineNum > 1) {
                //计算分割的文字里面的最大宽度
                width = 0;
                for (String s : rowStrings) {
                    Rect rect_T = new Rect();
                    //得到写这些文字需要占据的方形区域
                    paint.getTextBounds(s, 0, s.length(), rect_T);
                    int width_T = rect_T.width();
                    if (width_T > width) {
                        width = width_T;
                    }
                }
                width = (int) (getPaddingLeft() + width + getPaddingRight());

            } else {
                width = (int) (getPaddingLeft() + textWidth + getPaddingRight());
            }


        }

        //高度跟宽度处理方式一样
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            float textHeight = rect.height();
            if (lineNum > 1) {
                //计算分割的文字里面的最大宽度
                height = 0;
                for (String s : rowStrings) {
                    Rect rect_T = new Rect();
                    //得到写这些文字需要占据的方形区域
                    paint.getTextBounds(s, 0, s.length(), rect_T);
                    int height_T = rect_T.height();
                    height = height + height_T;
                }
                height = (int) (getPaddingTop() + height + getPaddingBottom());
            } else {
                height = (int) (getPaddingTop() + textHeight + getPaddingBottom());
            }
            Log.e("计算出来textview高度", height + "");
        }
        //保存测量宽度和测量高度
        setMeasuredDimension(width, height);

    }

}


 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值