最近在项目中有一个详情展示页需要求是在描述文本过多时只展示其中的5行并且在末尾可点击展开和收起,理了一下思路以后就开始着手造轮子了。
思路分析:
~描述文本和(展开收起)颜色不同且有叠加效果并非同一个View所以需要两个TextView
~使用ViewGroup将两个View包裹,宽度和高度均使用内部child1的宽高来填充
~将可被点击的child2放置到ViewGroup的右下角位置
~通过TextView中getLayout的getLineEnd方法获取指定行所展示的字数
~计算外部设置进来的text内容是否超过默认展示行数,已超过则将其一分为二
~根据点击事件的状态动态设置child1的文本内容以达到展开和收起的效果
下面看代码
在values下创建attr.xml属性文件定义如下自定义属性
<declare-styleable name="CollapsibleViewGroup">
<attr name="android:textSize"/>
<attr name="android:textColor"/>
<attr name="android:text"/>
<!-- 收起和展开的文字颜色 -->
<attr name="toggleTextColor" format="color"/>
<!-- 达到展开的行数-->
<attr name="visibleLine" format="integer"/>
</declare-styleable>
创建CollapsibleViewGroup类继承自FrameLayout
public class CollapsibleViewGroup extends FrameLayout implements View.OnClickListener {
public CollapsibleViewGroup(@NonNull Context context) {
super(context);
}
public CollapsibleViewGroup(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CollapsibleViewGroup);
int textColor = array.getColor(R.styleable.CollapsibleViewGroup_android_textColor, Color.BLACK);
int toggleColor = array.getColor(R.styleable.CollapsibleViewGroup_toggleTextColor, Color.RED);
float textSize = array.getDimension(R.styleable.CollapsibleViewGroup_android_textSize, 12);
visibleLine = array.getInteger(R.styleable.CollapsibleViewGroup_visibleLine, visibleLine);
String contentText = array.getString(R.styleable.CollapsibleViewGroup_android_text);
array.recycle();
// 初始化可扩展的文本视图
mCollapsibleTextView = new CollapsibleTextView(context);
mCollapsibleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
mCollapsibleTextView.setTextColor(textColor);
if (contentText != null){
this.text = contentText;
mCollapsibleTextView.setText(text);
}
// 初始化右下角的展开按钮
mMoreTextView = new TextView(context);
mMoreTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
mMoreTextView.setTextColor(toggleColor);
mMoreTextView.setText(toggleTipText[0]);
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.END | Gravity.BOTTOM;
mMoreTextView.setPadding(40,3,0,0);
mMoreTextView.setLayoutParams(layoutParams);
mMoreTextView.setOnClickListener(this);
mMoreTextView.setBackgroundResource(R.drawable.more_bg);
// 添加到当前容器内
addView(mCollapsibleTextView);
addView(mMoreTextView);
}
private CollapsibleTextView mCollapsibleTextView;
private TextView mMoreTextView;
private String[] toggleTipText = {"更多","收起"};
private String text ;
private String minText;
private boolean firstChanged = true;
private boolean extend;
private int visibleLine = 3;
CollapsibleViewGroup类中在构造中初始化了两个TextView并将其添加成child,其中的minText和text分别对应着文字最少显示数量和总得文字数量,visibleLine默认为3代表着所设置的文字数量超过3行后可进行展开。
/**
* 检查当前扩展视图的文本内容是否大于3行,并进行更新
*/
private void updateTextView(){
if (TextUtils.isEmpty(mCollapsibleTextView.getText())){
JLog.d("mCollapsibleTextView content is empty");
mMoreTextView.setVisibility(View.GONE);
return;
}
int lineCount = mCollapsibleTextView.getLineCount();
if (lineCount > visibleLine){
int lineChart = mCollapsibleTextView.getLineChart(visibleLine - 1);
// minText = mCollapsibleTextView.getText().subSequence(0, lineChart - 6).toString() + "...";
minText = mCollapsibleTextView.getText().subSequence(0, lineChart).toString();
mCollapsibleTextView.setText(minText);
mMoreTextView.setVisibility(View.VISIBLE);
}else {
mMoreTextView.setVisibility(View.GONE);
}
}
updateTextView方法是真正将当前文本进行加工的地方,先是获取出总行数 在和指定显示行数进行比较,最后获取visible行数所需的字数并设置给TextView
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed && firstChanged){
firstChanged = false;
updateTextView();
}
}
@Override
public void onClick(View view) {
// 根据扩展标识显示不同文本内容
mCollapsibleTextView.setText(extend ? minText : text);
mMoreTextView.setText(extend ? toggleTipText[0]:toggleTipText[1]);
extend = !extend;
}
/**
* 设置新的文本内容
* @param text
*/
public void setText(@NonNull String text) {
this.text = text;
mCollapsibleTextView.setText(text);
updateTextView();
}
private int getLineChart(int line){
return mMoreTextView.getLayout().getLineEnd(line);
}
其次的firstChanged是为了满足在xml中使用时直接设置的text,后续如果在代码中设置文本是走setText
最后贴一下整个类的源码
public class CollapsibleViewGroup extends FrameLayout implements View.OnClickListener {
public CollapsibleViewGroup(@NonNull Context context) {
super(context);
}
public CollapsibleViewGroup(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CollapsibleViewGroup);
int textColor = array.getColor(R.styleable.CollapsibleViewGroup_android_textColor, Color.BLACK);
int toggleColor = array.getColor(R.styleable.CollapsibleViewGroup_toggleTextColor, Color.RED);
float textSize = array.getDimension(R.styleable.CollapsibleViewGroup_android_textSize, 12);
visibleLine = array.getInteger(R.styleable.CollapsibleViewGroup_visibleLine, visibleLine);
String contentText = array.getString(R.styleable.CollapsibleViewGroup_android_text);
array.recycle();
// 初始化可扩展的文本视图
mCollapsibleTextView = new CollapsibleTextView(context);
mCollapsibleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
mCollapsibleTextView.setTextColor(textColor);
if (contentText != null){
this.text = contentText;
mCollapsibleTextView.setText(text);
}
// 初始化右下角的展开按钮
mMoreTextView = new TextView(context);
mMoreTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
mMoreTextView.setTextColor(toggleColor);
mMoreTextView.setText(toggleTipText[0]);
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.END | Gravity.BOTTOM;
mMoreTextView.setPadding(40,3,0,0);
mMoreTextView.setLayoutParams(layoutParams);
mMoreTextView.setOnClickListener(this);
mMoreTextView.setBackgroundResource(R.drawable.more_bg);
// 添加到当前容器内
addView(mCollapsibleTextView);
addView(mMoreTextView);
}
private CollapsibleTextView mCollapsibleTextView;
private TextView mMoreTextView;
private String[] toggleTipText = {"更多","收起"};
private String text ;
private String minText;
private boolean firstChanged = true;
private boolean extend;
private int visibleLine = 3;
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed && firstChanged){
firstChanged = false;
updateTextView();
}
}
/**
* 检查当前扩展视图的文本内容是否大于3行,并进行更新
*/
private void updateTextView(){
if (TextUtils.isEmpty(mCollapsibleTextView.getText())){
mMoreTextView.setVisibility(View.GONE);
return;
}
int lineCount = mCollapsibleTextView.getLineCount();
if (lineCount > visibleLine){
int lineChart = mCollapsibleTextView.getLineChart(visibleLine - 1);
minText = mCollapsibleTextView.getText().subSequence(0, lineChart).toString();
mCollapsibleTextView.setText(minText);
mMoreTextView.setVisibility(View.VISIBLE);
}else {
mMoreTextView.setVisibility(View.GONE);
}
}
@Override
public void onClick(View view) {
// 根据扩展标识显示不同文本内容
mCollapsibleTextView.setText(extend ? minText : text);
mMoreTextView.setText(extend ? toggleTipText[0]:toggleTipText[1]);
extend = !extend;
}
/**
* 设置新的文本内容
* @param text
*/
public void setText(@NonNull String text) {
this.text = text;
mCollapsibleTextView.setText(text);
updateTextView();
}
private int getLineChart(int line){
return mMoreTextView.getLayout().getLineEnd(line);
}
}
CollapsibleTextView的源码,之所以定义这个类是因为直接使用TextView的getLayout().getLineEnd(line)方法会得不到layout而包空指针,但是在类内部调用getLayout则百发百中
public class CollapsibleTextView extends AppCompatTextView {
public CollapsibleTextView(Context context) {
super(context);
}
public CollapsibleTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public int getLineChart(int line){
return getLayout().getLineEnd(line);
}
}
XML布局中使用
<com.xx.xx.widget.CollapsibleViewGroup
app:visibleLine="5"
android:layout_marginTop="@dimen/m_dp10"
android:id="@+id/cvgWords"
android:textColor="@color/color_333333"
app:toggleTextColor="@color/red_E32314"
android:textSize="@dimen/sp11"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>