热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

Android实现文本排版

这篇文章主要介绍了Android实现文本排版,对多行文本进行排版布局,每一行的内容又分为两部分,左边为标题,右边为描述,左边内容长度不确定,右边的内容需要对齐,需要的朋友可以参考下

  在项目中有一个小功能需要实现,就是对多行文本进行排版布局,每一行的内容又分为两部分,左边为标题,右边为描述,左边内容长度不确定,右边的内容需要对齐,如有换行也需要对齐右边的文本。

一、效果图

       

可以看到内容分成了两部分,左边的颜色与右边不一致,右边的描述文案统一对齐。

二、实现方案

       以上功能,由于输入内容输入行数不确定,并且左边的文案长度也不确定,因此不能直接在布局中实现,基于此这里主要实现了以下6种方式

方案1

       采用自定义控件的方式,继承TextView,重新onDraw函数,实现如下:

/**
 * 计算出左边最长的显示字符串maxLeftWidth,之后draw每一行字符,右边的描述从maxLeftWidth开始draw
 * 当一行显示不完全时,折行并且空出maxLeftWidth的空格长度
 */
public class TypographyView1 extends TextView {

  private Paint leftPaint = new Paint();
  private Paint rightPaint = new Paint();
  private int fullWidth;
  private float textSize;
  private JSONArray array;
  private int middlePadding = 0;
  float maxLeftWidth = 0;
  int itemSize = 0;

  public TypographyView1(Context context) {
    super(context);
    init();
  }

  public TypographyView1(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  }

  public TypographyView1(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
  }

  private void init() {
    textSize = getResources().getDimensionPixelSize(R.dimen.text_size_13);
    leftPaint.setAntiAlias(true);
    leftPaint.setTextSize(textSize);
    leftPaint.setColor(getResources().getColor(R.color.color_black_999999));
    rightPaint.setAntiAlias(true);
    rightPaint.setTextSize(textSize);
    rightPaint.setColor(getResources().getColor(R.color.color_black));
    middlePadding = getResources().getDimensionPixelSize(R.dimen.padding_value);
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    fullWidth = getWidth();// 整个textView的宽度
  }

  public void setText(JSONArray array) {
    this.array = array;
    if (array != null) {
      try {
        int size = itemSize = array.length();
        for (int i = 0; i  maxLeftWidth) {
            maxLeftWidth = curWidth;
          }
        }
        maxLeftWidth = maxLeftWidth + middlePadding;
        invalidate();
      } catch (Exception e) {

      }
    }
  }

  boolean setHeight = false;

  @Override
  protected void onDraw(Canvas canvas) {
    if (array == null) {
      return;
    }
    int lineCount = 0;
    try {
      JSONArray item;
      float offsetY;
      for (int i = 0; i  fullWidth - maxLeftWidth) {// 一行显示不完
          char[] textCharArray = value.toCharArray();
          float charWidth;
          float drawWidth = maxLeftWidth;
          for (int j = 0; j 

       添加了setText(JSONArray array)作为数据输入,并且在这里面测量了左边title的最大宽度,之后调用invalidate触发重绘,在onSizeChanged获取整个控件的宽度,重绘会调用onDraw函数,这里不需要调用super函数,TextView的onDraw函数做了非常多的操作,解析传入的数据,分别一行一行调用canvas来进行drawText操作,当绘制描述时,先计算宽度,如果超过剩余控件说明需要换行,最后调用setHeight设置高度,这个加一个判断条件,因为会触发requestLayout()进行重新布局和invalidate()进行重绘,如果不加判断会一直重绘。

方案2

       方式2与方式1差不多,不同为所有计算都在onDraw函数中:

/**
 * 该方式与方式1很类似,只是所有的计算都放在了onDraw方法中。
 */
public class TypographyView2 extends TextView {

  private Paint paint1 = new Paint();
  private Paint paint2 = new Paint();
  private int middlePadding = 0;
  int width;
  private float textSize;
  private JSONArray array;

  public TypographyView2(Context context) {
    super(context);
    init();
  }

  public TypographyView2(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  }

  public TypographyView2(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
  }

  private void init() {
    textSize = getResources().getDimensionPixelSize(R.dimen.text_size_13);
    paint1.setAntiAlias(true);
    paint1.setTextSize(textSize);
    paint1.setColor(getResources().getColor(R.color.color_black_999999));
    paint2.setAntiAlias(true);
    paint2.setTextSize(textSize);
    paint2.setColor(getResources().getColor(R.color.color_black));
    middlePadding = getResources().getDimensionPixelSize(R.dimen.padding_value);
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    width = getWidth();// 整个textView的宽度
  }

  public void setText(JSONArray array) {
    this.array = array;
    if (array != null) {
      invalidate();
    }
  }

  boolean setHeight = false;

  @Override
  protected void onDraw(Canvas canvas) {
    // super.onDraw(canvas);
    int lineCount = 0;
    int size = array.length();
    float maxLeftWidth = 0;
    float drawWidth = 0;
    try {
      for (int i = 0; i  maxLeftWidth) {
          maxLeftWidth = v;
        }
      }
      maxLeftWidth = maxLeftWidth + middlePadding;
      for (int i = 0; i 

       该方案的实现是不太好的,方案1也是在此基础上进行调整的,在这里放出来只是为了说明,所有的计算不要全部放在onDraw里面,因为该方法可能会反复调用多次,这样就降低了性能。

方案3

       将数据源拼接成SpannableString,重写onDraw函数,根据内容draw每一个字符:

/**
 * 该方法,是需要显示的内容先拼接成SpannableString,在onDraw方法中获取所有的char字符,一个一个比较
 * 当为分号是,表示为key与value的分隔符。
 */
public class TypographyView3 extends TextView {

  private Paint leftPaint = new Paint();
  private Paint rightPaint = new Paint();
  int width;
  private String text;
  private float textSize;
  float maxLeftWidth = 0;
  private int middlePadding = 0;

  public TypographyView3(Context context) {
    super(context);
    init();
  }

  public TypographyView3(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  }

  public TypographyView3(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
  }

  private void init() {
    textSize = getResources().getDimensionPixelSize(R.dimen.text_size_13);
    textSize = getResources().getDimensionPixelSize(R.dimen.text_size_13);
    leftPaint.setAntiAlias(true);
    leftPaint.setTextSize(textSize);
    leftPaint.setColor(getResources().getColor(R.color.color_black_999999));
    rightPaint.setAntiAlias(true);
    rightPaint.setTextSize(textSize);
    rightPaint.setColor(getResources().getColor(R.color.color_black));
    middlePadding = getResources().getDimensionPixelSize(R.dimen.padding_value);
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    width = getWidth();// 整个textView的宽度
  }

  public void setText(JSONArray data) {
    if (data == null) {
      return;
    }
    try {
      int size = data.length();
      for (int i = 0; i  maxLeftWidth) {
          maxLeftWidth = v;
        }
      }
      maxLeftWidth += middlePadding;
      SpannableStringBuilder ssb = new SpannableStringBuilder();
      for (int i = 0; i = 2) ? item.getString(1) : "";
      if (TextUtils.isEmpty(key) && TextUtils.isEmpty(value)) {
        return;
      }
      if (breakLine) {// 换行
        ssb.append("\r\n");
        ssb.append("\r\n");
      }
      SpannableString span = new SpannableString(key);
      //      span.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorAccent)), 0, key
      // .length(),
      //          Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
      ssb.append(span);
      ssb.append(value);

    } catch (JSONException e) {
      e.printStackTrace();
    }
  }

  @Override
  protected void onDraw(Canvas canvas) {
    // super.onDraw(canvas);
    int lineCount = 0;
    text = this.getText().toString();
    if (text == null)
      return;
    char[] textCharArray = text.toCharArray();
    // 已绘的宽度
    float drawWidth = 0;
    float charWidth;
    Paint paint = leftPaint;
    for (int i = 0; i  1 && textCharArray[i - 1] == ':') {
        drawWidth = maxLeftWidth;
        paint = rightPaint;
      }
      canvas.drawText(textCharArray, i, 1, drawWidth, (lineCount + 1) * textSize, paint);
      drawWidth += charWidth;
    }
    //may be need set height
    //setHeight((lineCount + 1) * (int) textSize + 5);
  }
}

       这里先计算左边title的最大宽度,同时将所有的数据拼接成一个SpannableStringBuilder,调用setText函数会触发重绘,在onDraw函数中进行处理,由于未重新super函数,因此SpannableString的setSpan函数失效,该方案主要根据分隔符来进行分割,因此分隔符需要唯一。

方案4

       采用GridLayout方式实现,但是原始控件有展示问题,因此对此进行了修改:

public class Typography4Activity extends BaseActivity {

  public static void start(Context context) {
    Intent intent = new Intent();
    intent.setClass(context, Typography4Activity.class);
    context.startActivity(intent);
  }

  private LinearLayout root;
  private Paint leftPaint = new Paint();
  private float textSize;
  private float maxLeftWidth;
  private int middlePadding = 0;
  private float maxRightWidth;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    root = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.activity_typography4, null);
    setContentView(root);
    initPaint();
    findViews();
    loadData();
  }

  private void initPaint() {
    textSize = getResources().getDimensionPixelSize(R.dimen.text_size_13);
    leftPaint.setAntiAlias(true);
    leftPaint.setTextSize(textSize);
    leftPaint.setColor(getResources().getColor(R.color.color_black_999999));
    middlePadding = getResources().getDimensionPixelSize(R.dimen.padding_value);
  }

  private void findViews() {

  }

  private void loadData() {
    addGridLayout(DataSource.getArray());
    TextView view = new TextView(this);
    view.setText("修改后的实现");
    view.setGravity(Gravity.CENTER);
    view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 160));
    root.addView(view);
    addModifyGridLayout(DataSource.getArray());
  }

  private void addGridLayout(JSONArray data) {
    try {
      GridLayout layout = createGridLayout();
      int size = data.length();
      for (int i = 0; i = 2) ? item.getString(1) : "";
        GridLayout.Spec row = GridLayout.spec(i);
        GridLayout.Spec col1 = GridLayout.spec(0);
        GridLayout.Spec col2 = GridLayout.spec(1);
        GridLayout.LayoutParams params = new GridLayout.LayoutParams(row, col1);

        TextView title = getLeftTextView(key);
        layout.addView(title, params);

        params = new GridLayout.LayoutParams(row, col2);
        TextView desc = getRightTextView(value);
        layout.addView(desc, params);
      }
      root.addView(layout);
    } catch (Exception e) {

    }
  }

  @NonNull
  private TextView getRightTextView(String value) {
    TextView desc = new TextView(this);
    desc.setTextSize(13);
    desc.setTextColor(getResources().getColor(R.color.black));
    desc.setText(value);
    return desc;
  }

  @NonNull
  private TextView getLeftTextView(String key) {
    TextView title = new TextView(this);
    title.setText(key);
    title.setPadding(0, middlePadding, middlePadding, 0);
    title.setTextColor(getResources().getColor(R.color.color_black_999999));
    title.setTextSize(13);
    return title;
  }

  private void addModifyGridLayout(JSONArray data) {
    try {
      calculateLeftMaxWidth(data);
      GridLayout layout = createGridLayout();
      int size = data.length();
      for (int i = 0; i = 2) ? item.getString(1) : "";
        GridLayout.Spec col2 = GridLayout.spec(1);
        params = new GridLayout.LayoutParams(row, col2);

        TextView desc = getRightTextView(value);
        params.width = (int) maxRightWidth;
        params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
        layout.addView(desc, params);
      }
      root.addView(layout);
    } catch (Exception e) {

    }
  }

  private void calculateLeftMaxWidth(JSONArray data) {
    try {
      DisplayUtil.init(this);// 这个可以在应用程序起来的时候init
      int size = data.length();
      for (int i = 0; i  maxLeftWidth) {
          maxLeftWidth = curWidth;
        }
      }
      maxLeftWidth = maxLeftWidth + middlePadding;
      maxRightWidth = DisplayUtil.screenWidth - DisplayUtil.dp2px(this, 32 + 10) - maxLeftWidth;
    } catch (Exception e) {

    }
  }

  private GridLayout createGridLayout() {
    GridLayout layout = new GridLayout(this);
    layout.setColumnCount(2);
    //layout.setRowCount(5);
    layout.setOrientation(GridLayout.HORIZONTAL);
    return layout;
  }
}

       如果直接创建一个GridLayout,里面添加每一项,如果描述过长都导致显示不全,这个是系统的一个bug,计算的宽度有问题,因此需要对此方案进行更改。
       更改方式为先计算左边占用的最大宽度,在添加右边的项时,设置布局参数控制最大的长度。

方案5

       采用每一行一个布局,手动一行一行进行添加:

public class Typography5Activity extends BaseActivity {

  public static void start(Context context) {
    Intent intent = new Intent();
    intent.setClass(context, Typography5Activity.class);
    context.startActivity(intent);
  }


  private LinearLayout root;
  private Paint leftPaint = new Paint();
  private float textSize;
  private float maxLeftWidth;
  private int middlePadding = 0;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    root = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.activity_typography5, null);
    setContentView(root);
    initPaint();
    loadData();
  }

  private void initPaint() {
    textSize = getResources().getDimensionPixelSize(R.dimen.text_size_13);
    leftPaint.setAntiAlias(true);
    leftPaint.setTextSize(textSize);
    leftPaint.setColor(getResources().getColor(R.color.color_black_999999));
    middlePadding = getResources().getDimensionPixelSize(R.dimen.padding_value);
  }

  private void loadData() {
    JSONArray array = DataSource.getArray();
    calculateLeftMaxWidth(array);
    if (array != null) {
      try {
        int size = array.length();
        for (int i = 0; i  maxLeftWidth) {
          maxLeftWidth = curWidth;
        }
      }
      maxLeftWidth = maxLeftWidth + middlePadding;
    } catch (Exception e) {

    }
  }

  private void addItem(String key, String value) {
    LinearLayout layout = getItemLayout();
    TextView left = (TextView) layout.findViewById(R.id.left);
    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
        ViewGroup.LayoutParams.WRAP_CONTENT);

    params.width = (int) maxLeftWidth;
    left.setLayoutParams(params);
    left.setText(key);

    TextView right = (TextView) layout.findViewById(R.id.right);
    right.setText(value);

    root.addView(layout);
  }

  private LinearLayout getItemLayout() {
    LinearLayout layout = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.compose_item_layout, null);
    return layout;
  }
}

       改方案也需要先计算左边的最大占用宽度,来设置右边占用的大小,每一项的布局如下:

<&#63;xml version="1.0" encoding="utf-8"&#63;>


  

  




       每一行有两个TextView,左边宽度为自适应,右边占据剩下左右的位置,在计算出左边最大宽度后,重新设置左边每一个TextView占用的宽度。

方案6

       方式与1差不多,但是不在继承TextView,而是直接继承View:

public class TypographyView4 extends View {

  private Paint leftPaint = new Paint();
  private Paint rightPaint = new Paint();
  private int fullWidth;
  private float textSize;
  private JSONArray array;
  private int middlePadding = 0;
  float maxLeftWidth = 0;
  int itemSize = 0;

  public TypographyView4(Context context) {
    super(context);
    init();
  }

  public TypographyView4(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  }

  public TypographyView4(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
  }

  private void init() {
    textSize = getResources().getDimensionPixelSize(R.dimen.text_size_13);
    leftPaint.setAntiAlias(true);
    leftPaint.setTextSize(textSize);
    leftPaint.setColor(getResources().getColor(R.color.color_black_999999));
    rightPaint.setAntiAlias(true);
    rightPaint.setTextSize(textSize);
    rightPaint.setColor(getResources().getColor(R.color.color_black));
    middlePadding = getResources().getDimensionPixelSize(R.dimen.padding_value);
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    fullWidth = getWidth();// 整个textView的宽度
  }

  public void setText(JSONArray array) {
    this.array = array;
    if (array != null) {
      try {
        int size = itemSize = array.length();
        for (int i = 0; i  maxLeftWidth) {
            maxLeftWidth = curWidth;
          }
        }
        maxLeftWidth = maxLeftWidth + middlePadding;
        invalidate();
      } catch (Exception e) {

      }
    }
  }

  @Override
  protected void onDraw(Canvas canvas) {
    if (array == null) {
      return;
    }
    int lineCount = 0;
    try {
      JSONArray item;
      float offsetY;
      for (int i = 0; i  fullWidth - maxLeftWidth) {// 一行显示不完
          char[] textCharArray = value.toCharArray();
          float charWidth;
          float drawWidth = maxLeftWidth;
          for (int j = 0; j 

       该方案主要继承自View,不再继承TextView,由于在在上述方案中不在调用super,因此TextView已经退化为一个View,因此直接继承View。

总结

       因为左边的宽度不确定,因此所有的方案都进行了同样的一个操作,就是测量了左边显示的最大宽度,后续的操作再根据该宽度进行调整。上述的方案中1,2,3,6都只需用一个View来进行显示,4,5都需要多个View进行显示。

 完整的代码可以在查看链接上进行查看。

以上就是本文的全部内容,希望对大家的学习有所帮助。


推荐阅读
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 使用正则表达式爬取36Kr网站首页新闻的操作步骤和代码示例
    本文介绍了使用正则表达式来爬取36Kr网站首页所有新闻的操作步骤和代码示例。通过访问网站、查找关键词、编写代码等步骤,可以获取到网站首页的新闻数据。代码示例使用Python编写,并使用正则表达式来提取所需的数据。详细的操作步骤和代码示例可以参考本文内容。 ... [详细]
  • 本文是关于自学Android的笔记,包括查看类的源码的方法,活动注册的必要性以及布局练习的重要性。通过学习本文,读者可以了解到在自学Android过程中的一些关键点和注意事项。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
author-avatar
笑看天下
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有