【问题标题】:How to make textview continue below another textview if multiple lines如果多行,如何使文本视图在另一个文本视图下方继续
【发布时间】:2019-09-04 06:19:50
【问题描述】:

我用两个文本视图的项目填充 RecyclerView,一个是标签(左文本视图),另一个包含内容(右文本视图)。当内容文本变长时,它将创建多行。现在,如果我们有更多的一行我希望文本在标签下方继续,这可能吗?如果不是,如果内容文本视图中有多行,我如何使文本视图垂直?

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">

  <TextView
    android:id="@+id/label"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:ellipsize="marquee"
    android:textColor="#667889"

    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintEnd_toStartOf="@+id/content"

    tools:text="Eros donec ac odio tempor" />

  <TextView
    android:id="@+id/content"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginStart="8dp"
    app:layout_constraintStart_toEndOf="@+id/label"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    tools:text="Content - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." />

</android.support.constraint.ConstraintLayout>

【问题讨论】:

  • 你的预期输出是什么?
  • 文字应该在下面继续,类似于link
  • 请向我们展示您的布局代码。
  • @Neptulon 你有幸实现这一目标吗?

标签: android android-layout


【解决方案1】:

你要使用的属性是

android:ems=""
android:maxLines=""

ems 表示字体大小,最大行数表示您希望在文本视图中显示多少行。 这些是您要在 XML 中使用的属性。

【讨论】:

    【解决方案2】:

    您可以使用单个 textView 实现此目的,只需使用 StringBuilder 并将文本附加到构建器 abs 将该字符串构建器设置为文本视图

    StringBuilder builder = new StringBuilder();
    builder.append("Your Text1 );
    builder.append("Your Text2 );
    

    然后将其设置为 textView

    textView.setText(builder);
    

    【讨论】:

      【解决方案3】:

      您可以执行以下操作:

      这里的想法是采用约束布局的布局 包含两个 TextView:

      • tv_fixed(从我不关心它的文字的角度来看是固定的)
      • tv_variable(从需要对其文本进行进一步处理的角度来看变量)

      注意:我们将正在处理的文本称为Ourtext

      并在 tv_fixed 和 tv_variable 下方动态添加另一个 TextView (tv_variable_plus)。 TextView tv_variable_plus 将帮助 tv_variable 显示 Ourtext 以提供所需的效果。

      注意: 使用参考 id (tv_fixed 和 tv_variable ids)动态添加障碍,而 tv_variable_plus 实际上是在此障碍下方添加的。 这个屏障的目的是确保 tv_variable_plus 文本不会与其他 TextViews 文本混合。

      OurText 在 TextView tv_variable 和添加的 TextView tv_variable_plus 之间如何划分?

      这里的想法是(当 Ourtext 使用提供的setText(String text1, String text) 方法更改时):

      • 遍历Ourtext的每个字符,得到每个字符的宽度cCharWidth(以像素为单位),递增cTotalCharWidth(计算OurText中所有字符的宽度) ,然后检查cTotalCharWidth 是否大于TextView tv_variable 宽度(-padding),如果大于我们可以认为我们到达了新行(使用cLineCounter 计算行数)。

      • 下一步,我们需要检查计数的行数 cLineCounter 乘以 TextView tv_variable 行高 int cLineHeight = tv_variable.getLineHeight(); 是否大于所有 TextView tv_fixed 行 高度int tAllLineHeight = tv_fixed.getLineCount() * tv_fixed.getLineHeight();,如果是这样的话,我们可以认为当前字符索引 (i) 是 Ourtext 文本索引,TextView tv_variable 文本在该索引处结束并且 tv_variable_plus 文本开始。

      2) TextViewInADifferentWay.class

      public class TextViewInADifferentWay {
      
      private final String TAG = TextViewInADifferentWay.class.getSimpleName();
      private Context context;
      private ConstraintLayout cl;
      private TextView tv_fixed = null;
      private TextView tv_variable = null;
      private TextView tv_variable_plus = null;
      private String text = "";
      public static final int CALCULATION_MODE_SINGLE_LINE = 0;
      public static final int CALCULATION_MODE_LINES_TOTAL_HEIGHT = 1;
      private int calculationMode = CALCULATION_MODE_LINES_TOTAL_HEIGHT;
      
      public TextViewInADifferentWay(@NonNull Context context,
                                     @NonNull View layout) {
          this(context, layout, CALCULATION_MODE_LINES_TOTAL_HEIGHT);
      }
      
      public TextViewInADifferentWay(@NonNull Context context,
                                     @NonNull View layout,
                                     int calculationMode) {
      
          this.context = context;
          if(calculationMode == CALCULATION_MODE_SINGLE_LINE ||
                  calculationMode == CALCULATION_MODE_LINES_TOTAL_HEIGHT){
              this.calculationMode = calculationMode;
          }
          setUpLayout(context, layout);
      }
      
      private void setUpLayout(Context context, View layout) {
      
          if (layout instanceof ConstraintLayout) {
              this.cl = (ConstraintLayout) layout;
              if (this.cl.getChildCount() == 2) {
                  View child1 = this.cl.getChildAt(0);
                  View child2 = this.cl.getChildAt(1);
                  if (child1 instanceof TextView
                          && child2 instanceof TextView) {
                      this.tv_fixed = (TextView) child1;
                      this.tv_variable = (TextView) child2;
                      this.text = this.tv_variable.getText().toString();
                  }
              } else {
                  throw new RuntimeException("Must have two children!!");
              }
          }
          if (tv_fixed != null && tv_variable != null) {
      
              /**
               * Add a barrier in the bottom of tv_fixed and tv_variable in order to always write to
               * the bottom of the view with the largest height
               */
              ConstraintLayout.LayoutParams params_ = new ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                      ViewGroup.LayoutParams.WRAP_CONTENT);
      
              Barrier barrier = new Barrier(context);
              int bId = ViewCompat.generateViewId();
              barrier.setId(bId);
              barrier.setType(Barrier.BOTTOM);
              barrier.setReferencedIds(new int[]{tv_fixed.getId(),
                      tv_variable.getId()});
              barrier.setLayoutParams(params_);
      
              this.cl.addView(barrier);
      
              ConstraintSet constraintSet_ = new ConstraintSet();
              constraintSet_.clone(this.cl);
              constraintSet_.connect(bId, ConstraintSet.START, this.cl.getId(), ConstraintSet.START);
              constraintSet_.connect(bId, ConstraintSet.END, this.cl.getId(), ConstraintSet.END);
              constraintSet_.applyTo(this.cl);
      
              /**
               * Add a TextView (tv_variable_plus)
               */
              ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams(0,
                      ViewGroup.LayoutParams.WRAP_CONTENT);
              tv_variable_plus = new TextView(context);
              int tId = ViewCompat.generateViewId();
              tv_variable_plus.setId(tId);
      
              //TODO: Clone as much attribute from base textView (tv_variable)
              tv_variable_plus.setTextSize(convertPixelsToDp(tv_variable.getTextSize(),
                      context));
              tv_variable_plus.setTextColor(tv_variable.getCurrentTextColor());
              tv_variable_plus.setTypeface(null, Typeface.BOLD);
              tv_variable_plus.setIncludeFontPadding(false);
              tv_variable_plus.setLayoutParams(params);
              this.cl.addView(tv_variable_plus);
      
              ConstraintSet constraintSet = new ConstraintSet();
              constraintSet.clone(this.cl);
              constraintSet.connect(tv_variable_plus.getId(), ConstraintSet.TOP, bId, ConstraintSet.BOTTOM);
              constraintSet.connect(tv_variable_plus.getId(), ConstraintSet.START, this.cl.getId(), ConstraintSet.START);
              constraintSet.connect(tv_variable_plus.getId(), ConstraintSet.END, this.cl.getId(), ConstraintSet.END);
              constraintSet.applyTo(this.cl);
      
              //set Text to default text (xml based) in order to re-calculate
              setText(tv_fixed.getText().toString(), text);
      
          } else {
              throw new RuntimeException("Both TextViews must be non null!!");
          }
      }
      
      /**
       * This method converts device specific pixels to density independent pixels.
       *
       * @param px      A value in px (pixels) unit. Which we need to convert into db
       * @param context Context to get resources and device specific display metrics
       * @return A float value to represent dp equivalent to px value
       */
      private static float convertPixelsToDp(float px, Context context) {
          return px / ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT);
      }
      
      public void calculate() {
      
          //TODO: Clone as much attribute from base textView (tv_variable)
          tv_variable_plus.setTextSize(convertPixelsToDp(tv_variable.getTextSize(),
                  context));
          tv_variable_plus.setTextColor(tv_variable.getCurrentTextColor());
          tv_variable_plus.setTypeface(null, Typeface.BOLD);
          tv_variable_plus.setIncludeFontPadding(false);
      
          int cTotalCharWidth = 0;
          int cLineCounter = 1;
          int index = 0;
          for (int i = 0; i < text.length(); i++) {
              int cLineHeight = tv_variable.getLineHeight();
              int tLineHeight = tv_fixed.getLineHeight();
              int tAllLineHeight = tv_fixed.getLineCount() * tLineHeight;
              String c = String.valueOf(text.charAt(i));
              float cCharWidth = getStringWidth(tv_variable, c);
              //Log.i(TAG, c + "-- w: " + cCharWidth);
              cTotalCharWidth += cCharWidth;
              if (cTotalCharWidth >= (tv_variable.getWidth() -
                      tv_variable.getPaddingLeft() - tv_variable.getPaddingRight())) {
                  //Log.i(TAG, "New Line after " + c + " (" + i + ")");
                  if (calculationMode == CALCULATION_MODE_LINES_TOTAL_HEIGHT) {
                      cTotalCharWidth = 0;
                      if (((2 * cLineCounter) * cLineHeight) >= (tAllLineHeight)) {
                          index = i;
                          break;
                      }
                  } else if (calculationMode == CALCULATION_MODE_SINGLE_LINE) {
                      if (cLineCounter == 1) {
                          index = i;
                          break;
                      }
                  }
                  cLineCounter += 1;
              }
          }
          if (index != 0) {
              tv_variable.setText(text.substring(0, index));
              tv_variable_plus.setText(text.substring(index));
              tv_variable_plus.setVisibility(View.VISIBLE);
          } else {
              tv_variable.setText(text);
              tv_variable_plus.setText("");
              tv_variable_plus.setVisibility(View.GONE);
          }
      }
      
      private float getStringWidth(TextView tv, String text) {
          Paint textPaint = tv.getPaint();
          float sWidth = textPaint.measureText(text); // in pixels
          return sWidth;
      }
      
      public TextView getTvFixed() {
          return this.tv_fixed;
      }
      
      public TextView getTvVariable() {
          return this.tv_variable;
      }
      
      public TextView getTvVariablePlus() {
          return this.tv_variable_plus;
      }
      
      public TextViewInADifferentWay setCalculationMode(int calculationMode) {
          if (calculationMode == CALCULATION_MODE_SINGLE_LINE ||
                  calculationMode == CALCULATION_MODE_LINES_TOTAL_HEIGHT) {
              if (calculationMode != this.calculationMode) {
                  this.calculationMode = calculationMode;
                  calculate();
              }
          }
          return TextViewInADifferentWay.this;
      }
      
      public TextViewInADifferentWay setText(String text1, String text) {
          this.text = text;
          tv_fixed.setText(text1);
          tv_variable.setText(text);
          if (tv_variable_plus != null) {
              tv_variable_plus.setText("");
              /**
               * make sure that views are inflated before calculating
               */
              final ViewTreeObserver viewTreeObserver = this.cl.getViewTreeObserver();
              viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                  @Override
                  public boolean onPreDraw() {
                      cl.getViewTreeObserver().removeOnPreDrawListener(this);
                      calculate();
                      return true;
                  }
              });
          }
          return TextViewInADifferentWay.this;
      }
      
      public String getText() {
          return this.text;
      }
      
      }
      

      3) 示例:

      MainActivity.class

      public class MainActivity extends AppCompatActivity {
      
      private final String TAG = MainActivity.class.getSimpleName();
      private RecyclerView rv_before;
      private RecyclerView rv_after;
      private Button b;
      private List<String> one = new ArrayList<>();
      private List<String> two = new ArrayList<>();
      
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
      
          one.add("Hello, how is work?");
          two.add("Hello, everything is fine, thank you for asking.");
          one.add("?");
          two.add("How are you today?");
          one.add("I not feeling well!");
          two.add("Are you sick, you seem extremely tired?");
          one.add("I've got flu");
          two.add("Go home and please stay there until you feel better. " +
                  "You don't want to spread your infection!");
      
          rv_before = (RecyclerView) findViewById(R.id.rv_before);
          rv_before.setHasFixedSize(true);
          LinearLayoutManager linearLayoutManagerB = new LinearLayoutManager(MainActivity.this);
          rv_before.setLayoutManager(linearLayoutManagerB);
      
          rv_after = (RecyclerView) findViewById(R.id.rv_after);
          rv_after.setHasFixedSize(true);
          LinearLayoutManager linearLayoutManagerA = new LinearLayoutManager(MainActivity.this);
          rv_after.setLayoutManager(linearLayoutManagerA);
      
          CustomRecyclerViewAdapter customRecyclerViewAdapterB = new CustomRecyclerViewAdapter(MainActivity.this, true, one, two);
          rv_before.setAdapter(customRecyclerViewAdapterB);
      
          CustomRecyclerViewAdapter customRecyclerViewAdapterA = new CustomRecyclerViewAdapter(MainActivity.this, false, one, two);
          rv_after.setAdapter(customRecyclerViewAdapterA);
      
          b = (Button) findViewById(R.id.b);
          b.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  List<String> fixed = new ArrayList<>();
                  List<String> variable = new ArrayList<>();
                  for (int i = 0; i < one.size(); i++) {
                      int oneI = new Random().nextInt(one.size());
                      fixed.add(one.get(oneI));
                      variable.add(two.get(oneI));
                  }
                  customRecyclerViewAdapterB.changeText(fixed, variable);
                  customRecyclerViewAdapterA.changeText(fixed, variable);
              }
          });
      }
      
      private class CustomRecyclerViewAdapter extends RecyclerView.Adapter<CustomRecyclerViewAdapter.CustomViewHolder> {
      
          private final Context context;
          private List<String> fixed;
          private List<String> variable;
          private final boolean before;
      
          public CustomRecyclerViewAdapter(Context context, boolean before,
                                           List<String> fixed, List<String> variable) {
              super();
              this.context = context;
              this.before = before;
              this.fixed = fixed;
              this.variable = variable;
          }
      
          @NonNull
          @Override
          public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
              View layout = LayoutInflater.from(context).inflate(R.layout.recycler_view_item, parent, false);
              return new CustomViewHolder(layout);
          }
      
          @Override
          public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) {
              if(!before) { // after
                  holder.textViewInADifferentWay.setText(fixed.get(position), variable.get(position));
              }else{ // before
                  TextView tv_fixed = (TextView) holder.text_view_in_a_different_way.findViewById(R.id.tv_fixed);
                  TextView tv_variable = (TextView) holder.text_view_in_a_different_way.findViewById(R.id.tv_variable);
                  tv_fixed.setText(fixed.get(position));
                  tv_variable.setText(variable.get(position));
              }
              holder.tv.setText("Item " + position + " top");
              holder.tv1.setText("Item " + position + " bottom");
          }
      
          @Override
          public int getItemCount() {
              return fixed.size();
          }
      
          public class CustomViewHolder extends RecyclerView.ViewHolder {
      
              private TextViewInADifferentWay textViewInADifferentWay;
              private final View text_view_in_a_different_way;
              private final TextView tv;
              private final TextView tv1;
      
              public CustomViewHolder(@NonNull View itemView) {
                  super(itemView);
                  text_view_in_a_different_way = (View)
                          itemView.findViewById(R.id.text_view_in_a_different_way);
                  if(!before) { // after
                      textViewInADifferentWay = new TextViewInADifferentWay(context,
                              text_view_in_a_different_way);
                  }
                  tv = (TextView) itemView.findViewById(R.id.tv);
                  tv1 = (TextView) itemView.findViewById(R.id.tv1);
              }
          }
      
          public void changeText(List<String> fixed, List<String> variable) {
              this.fixed = fixed;
              this.variable = variable;
              this.notifyDataSetChanged();
          }
      }
      
      }
      

      activity_main.xml:

      <?xml version="1.0" encoding="utf-8"?>
      <RelativeLayout
      android:id="@+id/rl"
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="horizontal"
      android:layout_width="match_parent"
      android:layout_height="match_parent">
      
      <Button
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="Change Data Set"
          android:id="@+id/b"
          android:textAllCaps="false"
          android:layout_alignParentTop="true"
          android:layout_marginTop="10dp">
      </Button>
      
      <LinearLayout
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:layout_below="@id/b"
          android:orientation="vertical"
          android:layout_marginTop="10dp"
          android:weightSum="100">
      
      <androidx.recyclerview.widget.RecyclerView
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:layout_weight="26.5"
          android:id="@+id/rv_before">
      </androidx.recyclerview.widget.RecyclerView>
      
          <TextView
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:layout_weight="47"
              android:text="Divider (before - after)"
              android:gravity="center"
              android:textColor="@android:color/white"
              android:background="@color/colorAccent">
          </TextView>
      
      <androidx.recyclerview.widget.RecyclerView
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:layout_weight="26.5"
          android:id="@+id/rv_after">
      </androidx.recyclerview.widget.RecyclerView>
      
      </LinearLayout>
      
      </RelativeLayout>
      

      recycler_view_item

      <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="vertical"
      android:layout_width="match_parent"
      android:layout_height="wrap_content">
      
      <!-- other stuff -->
      
      <TextView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:textAllCaps="false"
          android:id="@+id/tv"
          android:gravity="center"
          android:textColor="@android:color/white"
          android:background="@color/colorPrimary"
          android:text="Item">
      </TextView>
      
      <!-- other stuff -->
      
      <include
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:id="@+id/text_view_in_a_different_way"
          layout="@layout/text_view_in_a_different_way">
      </include>
      
      <!-- other stuff -->
      
      <TextView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:textAllCaps="false"
          android:id="@+id/tv1"
          android:gravity="center"
          android:textColor="@android:color/white"
          android:background="@color/colorPrimaryDark"
          android:text="Item">
      </TextView>
      
      <!-- other stuff -->
      
      </LinearLayout>
      

      text_view_in_a_different_way.xml

      <?xml version="1.0" encoding="utf-8"?>
      <androidx.constraintlayout.widget.ConstraintLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      android:id="@+id/cl"
      android:padding="10dp"
      android:layout_width="match_parent"
      android:layout_height="wrap_content">
      
      <TextView
          android:id="@+id/tv_fixed"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:textColor="@android:color/white"
          android:textSize="15sp"
          android:textStyle="bold"
          android:ellipsize="marquee"
          android:gravity="center_vertical"
          android:includeFontPadding="false"
          android:background="@color/colorPrimary"
          app:layout_constraintStart_toStartOf="parent"
          app:layout_constraintTop_toTopOf="parent"
          app:layout_constraintEnd_toStartOf="@id/tv_variable"
          android:text="Hello World World Hello" />
      
      <TextView
          android:id="@+id/tv_variable"
          android:layout_width="0dp"
          android:layout_height="wrap_content"
          android:textSize="18sp"
          android:textStyle="bold"
          android:layout_marginStart="8dp"
          android:layout_marginLeft="8dp"
          android:gravity="center_vertical"
          android:includeFontPadding="false"
          android:textColor="@color/colorAccent"
          app:layout_constraintTop_toTopOf="parent"
          app:layout_constraintEnd_toEndOf="parent"
          app:layout_constraintStart_toEndOf="@id/tv_fixed"
          android:text="Hello World Hello World Hello World" />
      
      </androidx.constraintlayout.widget.ConstraintLayout>
      

      4) 输出

      5) GitHub

      TextViewInADifferentWay

      【讨论】:

        猜你喜欢
        • 2013-02-16
        • 1970-01-01
        • 2014-06-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多