android - 如果多行,如何使文本视图在另一个文本视图下方继续
问题描述
我正在用两个文本视图的项目填充 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>
解决方案
您可以执行以下操作:
这里的想法是采用包含两个 TextView 的约束布局的布局:
- tv_fixed(从我不关心它的文本的角度来看是固定的)
- tv_variable(从需要对其文本进行进一步处理的角度来看的变量)
注意:让我们将我们正在处理的文本称为Ourtext
并在 tv_fixed 和 tv_variable 下方动态添加另一个 TextView ( tv_variable_plus )。TextView tv_variable_plus 将帮助 tv_variable以提供所需效果的方式显示Ourtext 。
注意: Barrier 是使用参考 id(tv_fixed 和 tv_variable ids)动态添加的,并且 tv_variable_plus 实际上是在此 Barrier 下方添加的。这个屏障的目的是确保 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 line heightint cLineHeight = tv_variable.getLineHeight();
是否大于所有 TextView tv_fixed lines heightint 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();
}
}
}
活动主.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
推荐阅读
- node.js - 如何使用 NodeJS 在 CSV 中删除列和重命名列
- javascript - 使用 router.push - next.js 后 csrf 令牌无效
- android - Android RecyclerView Width 比 Screen 宽
- css - Bootstrap Dropdown 在单击 Mobile 后关闭折叠菜单(不显示下拉菜单)
- sql - SQL获取有序数据的排名
- html - 允许图像溢出父 div 但需要将内容向下推送
- react-native - 如何在 React Native 中选择正确的 gradle 版本
- monaco-editor - 如何在评论中为“待办事项”创建规则
- python - Python中的动态数据框转换
- ant - Apache ANT:get 不支持嵌套的“header”元素