美文网首页
自定义标签流水型布局

自定义标签流水型布局

作者: 风度_dbfe | 来源:发表于2020-01-15 15:07 被阅读0次

    对于自定义view我们平时在项目中使用的很多,但是我们自己写的却很少,因为现在开源的代码越来越多,使得我们的惰性也变大了。

    前段时间朋友找我解决个需求,他们产品需要做一个类似于标签的流水型布局如下图所示:

    流水型布局

    数据:/#Python#/是一种/#入门快#/、/#功能强大#/、/#高效灵活#/的编程语言,学会之后无论是想进入/#数据分析#/、/#人工智能#/、/#网站开发#/、/#网络安全#/、/#集群运维#/这些领域,还是希望掌握第一门编程语言,都可以用 Python 来开启美好未来的无限可能!

    备注:在  "/#"  和  "#/" 中为特殊字符需要进行变小字体、加圆角背景

    正常情况下我们使用Textview,组件内部会给我们做到自动换行,现在对于这个需求 我采用的是自定义viewgroup,每一行使用多个TextView拼接的方式来排版,通过计算字符的方式来进行换行。(方式有很多种,希望大家多给提提宝贵意见)

    一、初始化

    初始化普通字体TextView、特殊字体TextView,我们需要根据他们所对应的的画笔Paint来测量绘画每个字符所需要的长度,我们需要计算每个分隔号好的字符串来做到换行处理。

    /**

    * 初始化

    */

    private void init(Context context) {

    this.context = context;

    //比较普通字体和特殊字体的大小来决定行高(layout排布的时候把小字体的居中显示)

    maxLineHeight =orTextSize >spTextSize ?orTextSize *4 :spTextSize *4;

    //特殊字体

    specialView =new TextView(this.context);

    specialView.setTextSize(spTextSize);

    specialPaint =specialView.getPaint();

    //普通字体

    ordinaryView =new TextView(this.context);

    ordinaryView.setTextSize(orTextSize);

    ordinaryPaint =ordinaryView.getPaint();

    }

    二、拆分字体

    我是采用多次替换的方式来进行分隔普通字体和特殊字体

    /**

    * 拆分字体

    */

    private String[] split(String data) {

    data = data.replaceAll("/#","&");

    data = data.replaceAll("#/","~&");

    String[] split = data.split("&");

    return split;

    }

    /**

    * creat view

    */

        private void startCalc() {

    if (null ==data ||data.length() ==0)return;

    if (isStart)return;

    String[] split = split(data);

    for (int i =0; i < split.length; i++) {

    String str = split[i];

    if (null != str && str.length() >0) {

    calc(str.contains("~"), str);

    }

    }

    invalidate();

            isStart =true;

    }

    /**

    * 计算数据

    */

    private void calc(boolean flag, String data) {

    //特殊字体

    if (flag) {

    data = data.replace("~","");

    addSpecialText(data);

    //            Log.d("TAG-main","特殊字体:"+data);

    //普通字体

            }else {

    addOrdinaryText(data);

    //            Log.d("TAG-main","普通字体:"+data);

            }

    }

    三、计算每行可放置几个TextView

    这里我通过viewgroup的宽度来计算每行可放置几个view,有一点要考虑的如果剩下的宽度不足以放置下一个view 我们就要根据剩余宽度来计算可放置几个字符来拆分下一个字符串,来拆分成两个或者多个(我使用的是递归的方式)

    /**

    * 添加普通字体

    *

    * @param data

    */

    private void addOrdinaryText(String data) {

    float v =ordinaryPaint.measureText(data);

    //如果字符串宽度小于剩余宽度 直接创建一个textview

        if (v <=lastWidth) {

    lastWidth =lastWidth - v;

    creatView(data);

    //如果字符串宽度大于剩余宽度 需要拆分字符串

        }else {

    String str = getOrdinaryStr(data);

    lastWidth =width;

    creatView(str);

    addOrdinaryText(data.substring(str.length(), data.length()));

    }

    }

    /**

    * 获取剩余控件特殊字体可填入的内容

    *

    * @param data

    * @return

    */

    private String getOrdinaryStr(String data) {

    String str ="";

    char[] chars = data.toCharArray();

    for (int i =0; i < chars.length; i++) {

    str += chars[i];

    if (ordinaryPaint.measureText(str) >=lastWidth) {

    if (i <= chars.length) {

    str = data.substring(0, i);

    }

    break;

    }

    }

    return str;

    }

    四、测量、排布

    我们这里测量的目的是要获取当前viewgroup的宽度,以及做一个自适应的高度,排布的话就是layout函数在摆放子view的位置来达到一个流水型布局的目的。(使用的指定viewgroup具体的宽度数值)

    /**

    * 测量

    *

    * @param widthMeasureSpec

    * @param heightMeasureSpec

    */

    @Override

    protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int width = MeasureSpec.getSize(widthMeasureSpec);

    int mode = MeasureSpec.getMode(widthMeasureSpec);

    if (width >0 && mode == MeasureSpec.EXACTLY) {

    this.width = width;

    lastWidth = width;

    startCalc();

    }

    int count = getChildCount();

    int l =0;

    float totalHeight =lineHeight;

    int viweHeight =0;

    int maxLineHeight =0;

    for (int i =0; i < count; i++) {

    View v = getChildAt(i);

    if (v.getVisibility() != View.GONE) {

    measureChild(v, widthMeasureSpec, heightMeasureSpec);

    View childAt = getChildAt(i);

    int viewWidth = childAt.getMeasuredWidth();

    viweHeight = childAt.getMeasuredHeight();

    if (viewWidth > width - l) {

    totalHeight += maxLineHeight +lineHeight;

    l =0;

    maxLineHeight =0;

    }

    if (maxLineHeight ==0) {

    maxLineHeight = viweHeight;

    }

    if (viweHeight > maxLineHeight) {

    maxLineHeight = viweHeight;

    }

    if (width - l >0) {

    l += viewWidth;

    }

    }

    }

    setMeasuredDimension(width, (int) totalHeight + viweHeight);

    }

    /**

    * 排布

    *

    * @param changed

    * @param l

    * @param t

    * @param r

    * @param b

    */

    @Override

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

    int childCount = getChildCount();

    float totalHeight =lineHeight;

    if (childCount ==0) {

    return;

    }

    l =0;

    for (int i =0; i < childCount; i++) {

    View childAt = getChildAt(i);

    int viewWidth = childAt.getMeasuredWidth();

    int viewHeight = childAt.getMeasuredHeight();

    if (viewWidth >width - l) {

    totalHeight +=maxLineHeight +lineHeight;

    l =0;

    maxLineHeight =orTextSize >spTextSize ?orTextSize *4 :spTextSize *4;

    }

    if (viewHeight >maxLineHeight) {

    maxLineHeight = viewHeight;

    t = (int) totalHeight;

    b = (int) totalHeight + viewHeight;

    }else {

    t = (int) totalHeight + ((maxLineHeight - viewHeight) /2);

    b = (int) totalHeight + viewHeight + ((maxLineHeight - viewHeight) /2);

    }

    childAt.layout(l, t, l + viewWidth, b);

    if (width - l >0) {

    l += (viewWidth) ;

    }

    }

    }

    我上面大概的讲解了一下我实现这个自定义viewgroup的思路,完整的代码我放到下面了,各位看官自行理解!!!

    Java代码:

    /**

    * 自定义标签流水型布局

    * create by wxy on 2020/1/13

    */

    public class LabelLayoutextends ViewGroup {

    //特殊字体属性

    //字体大小

        private int spTextSize =10;

    //字体颜色

        private int spTextColor = R.color.white_a_color;

    //普通字体属性

    //字体大小

        private int orTextSize =14;

    //字体颜色

        private int orTextColor = R.color.gray_a_color;

    //行高

        private int lineHeight =10;

    private Contextcontext;

    private float width;

    private float lastWidth;

    //特殊字体textview

        private TextViewspecialView;

    //特殊字体textview paint

        private PaintspecialPaint;

    //普通字体textview

        private TextViewordinaryView;

    //特殊字体textview paint

        private PaintordinaryPaint;

    private int maxLineHeight;

    public LabelLayout(Context context) {

    super(context);

    init(context);

    }

    public LabelLayout(Context context, AttributeSet attrs) {

    super(context, attrs);

    init(context);

    }

    public LabelLayout(Context context, AttributeSet attrs,int defStyleAttr) {

    super(context, attrs, defStyleAttr);

    init(context);

    }

    /**

    * 初始化

    */

        private void init(Context context) {

    this.context = context;

    maxLineHeight =orTextSize >spTextSize ?orTextSize *4 :spTextSize *4;

    //特殊字体

            specialView =new TextView(this.context);

    specialView.setTextSize(spTextSize);

    specialPaint =specialView.getPaint();

    //普通字体

            ordinaryView =new TextView(this.context);

    ordinaryView.setTextSize(orTextSize);

    ordinaryPaint =ordinaryView.getPaint();

    }

    /**

    * 测量

    *

        * @param widthMeasureSpec

        * @param heightMeasureSpec

        */

        @Override

        protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int width = MeasureSpec.getSize(widthMeasureSpec);

    int mode = MeasureSpec.getMode(widthMeasureSpec);

    if (width >0 && mode == MeasureSpec.EXACTLY) {

    this.width = width;

    lastWidth = width;

    startCalc();

    }

    int count = getChildCount();

    int l =0;

    float totalHeight =lineHeight;

    int viweHeight =0;

    int maxLineHeight =0;

    for (int i =0; i < count; i++) {

    View v = getChildAt(i);

    if (v.getVisibility() != View.GONE) {

    measureChild(v, widthMeasureSpec, heightMeasureSpec);

    View childAt = getChildAt(i);

    int viewWidth = childAt.getMeasuredWidth();

    viweHeight = childAt.getMeasuredHeight();

    if (viewWidth > width - l) {

    totalHeight += maxLineHeight +lineHeight;

    l =0;

    maxLineHeight =0;

    }

    if (maxLineHeight ==0) {

    maxLineHeight = viweHeight;

    }

    if (viweHeight > maxLineHeight) {

    maxLineHeight = viweHeight;

    }

    if (width - l >0) {

    l += viewWidth;

    }

    }

    }

    setMeasuredDimension(width, (int) totalHeight + viweHeight);

    }

    /**

    * 排布

    *

        * @param changed

        * @param l

        * @param t

        * @param r

        * @param b

        */

        @Override

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

    int childCount = getChildCount();

    float totalHeight =lineHeight;

    if (childCount ==0) {

    return;

    }

    l =0;

    for (int i =0; i < childCount; i++) {

    View childAt = getChildAt(i);

    int viewWidth = childAt.getMeasuredWidth();

    int viewHeight = childAt.getMeasuredHeight();

    if (viewWidth >width - l) {

    totalHeight +=maxLineHeight +lineHeight;

    l =0;

    maxLineHeight =orTextSize >spTextSize ?orTextSize *4 :spTextSize *4;

    }

    if (viewHeight >maxLineHeight) {

    maxLineHeight = viewHeight;

    t = (int) totalHeight;

    b = (int) totalHeight + viewHeight;

    }else {

    t = (int) totalHeight + ((maxLineHeight - viewHeight) /2);

    b = (int) totalHeight + viewHeight + ((maxLineHeight - viewHeight) /2);

    }

    childAt.layout(l, t, l + viewWidth, b);

    if (width - l >0) {

    l += (viewWidth) ;

    }

    }

    }

    private Stringdata;

    private boolean isStart;

    public void upData(String data) {

    this.data = data;

    }

    /**

    * creat view

    */

        private void startCalc() {

    if (null ==data ||data.length() ==0)return;

    if (isStart)return;

    String[] split = split(data);

    for (int i =0; i < split.length; i++) {

    String str = split[i];

    if (null != str && str.length() >0) {

    calc(str.contains("~"), str);

    }

    }

    invalidate();

    //        requestLayout();

            isStart =true;

    }

    /**

    * 计算数据

    */

        private void calc(boolean flag, String data) {

    //特殊字体

            if (flag) {

    data = data.replace("~","");

    addSpecialText(data);

    //            Log.d("TAG-main","特殊字体:"+data);

    //普通字体

            }else {

    addOrdinaryText(data);

    //            Log.d("TAG-main","普通字体:"+data);

            }

    }

    /**

    * 拆分字体

    */

        private String[] split(String data) {

    data = data.replaceAll("/#","&");

    data = data.replaceAll("#/","~&");

    String[] split = data.split("&");

    return split;

    }

    /**

    * 添加普通字体

    *

        * @param data

        */

        private void addOrdinaryText(String data) {

    float v =ordinaryPaint.measureText(data);

    //如果字符串宽度小于剩余宽度 直接创建一个textview

            if (v <=lastWidth) {

    lastWidth =lastWidth - v;

    creatView(data);

    //如果字符串宽度大于剩余宽度 需要拆分字符串

            }else {

    String str = getOrdinaryStr(data);

    lastWidth =width;

    creatView(str);

    addOrdinaryText(data.substring(str.length(), data.length()));

    }

    }

    /**

    * 添加特殊字体

    *

        * @param data

        */

        private void addSpecialText(String data) {

    float v =specialPaint.measureText(data)+20;

    //如果字符串宽度小于剩余宽度 直接创建一个textview
    if (v <=lastWidth) {

    lastWidth =lastWidth - v;

    creatSpView(data);

    //如果字符串宽度大于剩余宽度 需要拆分字符串

            }else {

    String str = getSpecialStr(data);

    lastWidth =width;

    creatSpView(str);

    addSpecialText(data.substring(str.length(), data.length()));

    }

    }

    /**

    * 获取剩余控件特殊字体可填入的内容

    *

        * @param data

        * @return

        */

        private String getSpecialStr(String data) {

    String str ="";

    char[] chars = data.toCharArray();

    for (int i =0; i < chars.length; i++) {

    str += chars[i];

    if (specialPaint.measureText(str) >=lastWidth) {

    if (i <= chars.length) {

    str = data.substring(0, i);

    }

    break;

    }

    }

    return str;

    }

    /**

    * 获取剩余控件特殊字体可填入的内容

    *

        * @param data

        * @return

        */

        private String getOrdinaryStr(String data) {

    String str ="";

    char[] chars = data.toCharArray();

    for (int i =0; i < chars.length; i++) {

    str += chars[i];

    if (ordinaryPaint.measureText(str) >=lastWidth) {

    if (i <= chars.length) {

    str = data.substring(0, i);

    }

    break;

    }

    }

    return str;

    }

    /**

    * 创建普通字体textview

    *

        * @param data

        */

        private void creatView(String data) {

    if (null == data || data.length() ==0)return;

    int width = (int)ordinaryPaint.measureText(data);

    ordinaryView =new TextView(this.context);

    ordinaryView.setTextSize(orTextSize);

    ordinaryView.setGravity(Gravity.CENTER);

    ordinaryView.setTextColor(getResources().getColor(orTextColor));

    ordinaryView.setWidth(width);

    ordinaryView.setText(data);

    addView(ordinaryView);

    }

    /**

    * 创建特殊字体textview

    *

        * @param data

        */

        private void creatSpView(String data) {

    if (null == data || data.length() ==0)return;

    int width = (int)specialPaint.measureText(data)+18;

    specialView =new TextView(this.context);

    specialView.setTextSize(spTextSize);

    specialView.setGravity(Gravity.CENTER);

    specialView.setTextColor(getResources().getColor(spTextColor));

    specialView.setBackgroundColor(getResources().getColor(R.color.black_d_color));

    specialView.setWidth(width);

    specialView.setHeight(maxLineHeight);

    specialView.setText(data);

    addView(specialView);

    }

    }

    XML代码:

    <com.hyperx.wlworktools.test.LabelLayout

        android:id="@+id/group_view"

        android:layout_width="240dp"

        android:layout_height="match_parent"
    </com.hyperx.wlworktools.test.LabelLayout>

    Activity代码:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_cus_group);
    final LabelLayout group_view = findViewById(R.id.group_view);
    String data ="/#Python#/是一种/#入门快#/、/#功能强大#/、/#高效灵活#/的编程语言,学会之后无论是想进入/#数据分析#/、/#人工智能#/、/#网站开发#/、/#网络安全#/、/#集群运维#/这些领域,还是希望掌握第一门编程语言,都可以用 Python 来开启美好未来的无限可能!";
    group_view.upData(data);
    }

    感觉有用的同学,动动小手指给个赞,码字不易。

    相关文章

      网友评论

          本文标题:自定义标签流水型布局

          本文链接:https://www.haomeiwen.com/subject/jxpaactx.html