最近在做类似朋友圈功能,其中有个细节是当文本内容多行时显示全文与收起,微信的文本内容是最多显示6行,我的例子是显示3行,这个可以根据自己的需求来写,之前写过一个可伸缩扩展的TextView带图标,和这个类似,但是有点区别,微信这个体验还是不错的,于是自己写了一个自定义的TextView实现了类似功能,用的是androidx的兼容包,如果小伙伴们不清楚androidx兼容包该咋写,可参考官网地址:迁移到 AndroidX | Android 开发者 | Android Developers.
1.微信的效果截图如下:
2.实现效果截图如下:
2.1 ExpandTextView的代码如下:
/**
* @作者: njb
* @时间: 2019/7/22 10:53
* @描述: 自定义仿微信朋友圈显示全文与收起的TextView
*/
public class ExpandTextView extends LinearLayout {
public static final int DEFAULT_MAX_LINES = 3;//最大的行数
private TextView contentText;
private TextView textState;
private int showLines;
private ExpandStatusListener expandStatusListener;
private boolean isExpand;
public ExpandTextView(Context context) {
super(context);
initView();
}
public ExpandTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttrs(attrs);
initView();
}
public ExpandTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(attrs);
initView();
}
private void initView() {
setOrientation(LinearLayout.VERTICAL);
LayoutInflater.from(getContext()).inflate(R.layout.layout_expand_text, this);
contentText = (TextView) findViewById(R.id.contentText);
if(showLines > 0){
contentText.setMaxLines(showLines);
}
textState = (TextView) findViewById(R.id.textState);
textState.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
String textStr = textState.getText().toString().trim();
if("全文".equals(textStr)){
contentText.setMaxLines(Integer.MAX_VALUE);
textState.setText("收起");
setExpand(true);
}else{
contentText.setMaxLines(showLines);
textState.setText("全文");
setExpand(false);
}
//通知外部状态已变更
if(expandStatusListener != null){
expandStatusListener.statusChange(isExpand());
}
}
});
}
private void initAttrs(AttributeSet attrs) {
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ExpandTextView, 0, 0);
try {
showLines = typedArray.getInt(R.styleable.ExpandTextView_showLines, DEFAULT_MAX_LINES);
}finally {
typedArray.recycle();
}
}
public void setText(final CharSequence content){
contentText.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
// 避免重复监听
contentText.getViewTreeObserver().removeOnPreDrawListener(this);
int linCount = contentText.getLineCount();
if(linCount > showLines){
if(isExpand){
contentText.setMaxLines(Integer.MAX_VALUE);
textState.setText("收起");
}else{
contentText.setMaxLines(showLines);
textState.setText("全文");
}
textState.setVisibility(View.VISIBLE);
}else{
textState.setVisibility(View.GONE);
}
return true;
}
});
contentText.setText(content);
contentText.setMovementMethod(new CircleMovementMethod(getResources().getColor(R.color.name_selector_color)));
}
public void setExpand(boolean isExpand){
this.isExpand = isExpand;
}
public boolean isExpand(){
return this.isExpand;
}
public void setExpandStatusListener(ExpandStatusListener listener){
this.expandStatusListener = listener;
}
public static interface ExpandStatusListener{
void statusChange(boolean isExpand);
}
}
2.2 attrs.xml的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ExpandTextView">
<attr name="showLines" format="integer"/>
</declare-styleable>
</resources>
2.3 layout_expand_text布局文件的代码如下:
<?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="match_parent">
<TextView
android:id="@+id/contentText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/color_232323"
android:textSize="14sp"
android:text=""
/>
<TextView
android:id="@+id/textState"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="@color/color_8290AF"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:text=""
/>
</LinearLayout>
2.4 MainActivity代码如下:
/**
* @作者: njb
* @时间: 2019/7/22 10:53
* @描述: 仿微信朋友圈文本显示全文与收起
*/
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private CircleAdapter circleAdapter;
private String content = "茫茫的长白大山,浩瀚的原始森林,大山脚下,原始森林环抱中散落着几十户人家的" +
"一个小山村,茅草房,对面炕,烟筒立在屋后边。在村东头有一个独立的房子,那就是青年点," +
"窗前有一道小溪流过。学子在这里吃饭,由这里出发每天随社员去地里干活。干的活要么上山伐" +
"树,抬树,要么砍柳树毛子开荒种地。在山里,可听那吆呵声:“顺山倒了!”放树谨防回头棒!" +
"树上的枯枝打到别的树上再蹦回来,这回头棒打人最厉害。";
private List<String> strings;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
initData();
initAdapter();
}
/**
* 初始化控件
*/
private void initViews() {
recyclerView = findViewById(R.id.recyclerView);
}
/**
* 初始化数据
*/
private void initData() {
strings = new ArrayList<>();
for (int i = 0; i < 14; i++) {
strings.add(content);
}
}
/**
* 设置adapter
*/
private void initAdapter() {
circleAdapter = new CircleAdapter(this, strings);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.addItemDecoration(new SpaceDecoration(this));
recyclerView.setAdapter(circleAdapter);
}
}
2.5 CircleAdapter的代码如下:
/**
* @作者: njb
* @时间: 2019/7/25 10:47
* @描述:
*/
public class CircleAdapter extends RecyclerView.Adapter<CircleAdapter.CircleViewHolder> {
private Context context;
private List<String> list;
private LayoutInflater layoutInflater;
public CircleAdapter(Context context, List<String> list) {
this.context = context;
this.list = list;
this.layoutInflater = LayoutInflater.from(context);
}
@NonNull
@Override
public CircleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_circle,
parent, false);
CircleViewHolder circleViewHolder = new CircleViewHolder(view);
return circleViewHolder;
}
@Override
public void onBindViewHolder(@NonNull CircleViewHolder holder, int position) {
holder.expandTextView.setText(list.get(position));
}
@Override
public int getItemCount() {
return list.size();
}
public class CircleViewHolder extends RecyclerView.ViewHolder {
ExpandTextView expandTextView;
public CircleViewHolder(@NonNull View itemView) {
super(itemView);
expandTextView = itemView.findViewById(R.id.expand_textView);
}
}
}
2.6.Application的代码如下:
/**
* @作者: njb
* @时间: 2019/7/22 11:07
* @描述:
*/
public class App extends Application {
//全局Context
private static Context sContext;
@Override
public void onCreate() {
super.onCreate();
sContext = getApplicationContext();
}
public static Context getContext() {
return sContext;
}
}
2.7 CircleMovementMethod的代码如下:
/**
* @作者: njb
* @时间: 2019/7/22 10:53
* @描述:
*/
public class CircleMovementMethod extends BaseMovementMethod {
public final String TAG = CircleMovementMethod.class.getSimpleName();
private final static int DEFAULT_COLOR_ID = R.color.transparent;
private final static int DEFAULT_CLICKABLEA_COLOR_ID = R.color.default_clickable_color;
/**整个textView的背景色*/
private int textViewBgColor;
/**点击部分文字时部分文字的背景色*/
private int clickableSpanBgClor;
private BackgroundColorSpan mBgSpan;
private ClickableSpan[] mClickLinks;
private boolean isPassToTv = true;
/**
* true:响应textview的点击事件, false:响应设置的clickableSpan事件
*/
public boolean isPassToTv() {
return isPassToTv;
}
private void setPassToTv(boolean isPassToTv){
this.isPassToTv = isPassToTv;
}
public CircleMovementMethod(){
this.textViewBgColor = App.getContext().getResources().getColor(DEFAULT_COLOR_ID);
this.clickableSpanBgClor = App.getContext().getResources().getColor(DEFAULT_CLICKABLEA_COLOR_ID);
}
/**
*
* @param clickableSpanBgClor 点击选中部分时的背景色
*/
public CircleMovementMethod(int clickableSpanBgClor){
this.clickableSpanBgClor = clickableSpanBgClor;
this.textViewBgColor = App.getContext().getResources().getColor(DEFAULT_COLOR_ID);
}
/**
*
* @param clickableSpanBgClor 点击选中部分时的背景色
* @param textViewBgColor 整个textView点击时的背景色
*/
public CircleMovementMethod(int clickableSpanBgClor, int textViewBgColor){
this.textViewBgColor = textViewBgColor;
this.clickableSpanBgClor = clickableSpanBgClor;
}
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
int action = event.getAction();
if(action == MotionEvent.ACTION_DOWN){
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
mClickLinks = buffer.getSpans(off, off, ClickableSpan.class);
if(mClickLinks.length > 0){
// 点击的是Span区域,不要把点击事件传递
setPassToTv(false);
Selection.setSelection(buffer,
buffer.getSpanStart(mClickLinks[0]),
buffer.getSpanEnd(mClickLinks[0]));
//设置点击区域的背景色
mBgSpan = new BackgroundColorSpan(clickableSpanBgClor);
buffer.setSpan(mBgSpan,
buffer.getSpanStart(mClickLinks[0]),
buffer.getSpanEnd(mClickLinks[0]),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}else{
setPassToTv(true);
// textview选中效果
widget.setBackgroundColor(textViewBgColor);
}
}else if(action == MotionEvent.ACTION_UP){
if(mClickLinks.length > 0){
mClickLinks[0].onClick(widget);
if(mBgSpan != null){//移除点击时设置的背景span
buffer.removeSpan(mBgSpan);
}
}else{
}
Selection.removeSelection(buffer);
widget.setBackgroundResource(R.color.transparent);
}else if(action == MotionEvent.ACTION_MOVE){
//这种情况不用做处理
}else{
if(mBgSpan != null){//移除点击时设置的背景span
buffer.removeSpan(mBgSpan);
}
widget.setBackgroundResource(R.color.transparent);
}
return Touch.onTouchEvent(widget, buffer, event);
}
2.8 colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="color_8290AF">#8290AF</color>
<color name="color_232323">#232323</color>
<color name="transparent">#00000000</color>
<color name="default_clickable_color">#cccccc</color>
<color name="name_selector_color">#cccccc</color>
<color name="cd8d8d8">#d8d8d8</color>
<color name="black">#000000</color>
</resources>
2.9 分割线的代码如下:
/**
* @作者: njb
* @时间: 2019/7/25 11:31
* @描述: 自定义分割线
*/
public class SpaceDecoration extends RecyclerView.ItemDecoration {
private Context mContext; //上下文
private int dividerHeight; //分割线的高度
private Paint mPaint; //画笔
//自定义构造方法,在构造方法中初始化一些变量
public SpaceDecoration(Context context){
mContext = context;
dividerHeight = 20; //context.getResources().getDimensionPixelSize(R.dimen.divider_height);
mPaint = new Paint();
mPaint.setColor(context.getResources().getColor(R.color.cd8d8d8)); //设置颜色
}
//设置padding
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
//outRect.bottom、left,right,top设置为int值,设置每一项的padding
outRect.bottom =dividerHeight ;
}
//画图
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
//获取item个数
int childCount = parent.getChildCount();
//左右是固定的
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight() ;
//高度
for (int i = 0; i < childCount - 1; i++) {
View view = parent.getChildAt(i);
float top = view.getBottom();
float bottom = view.getBottom() + dividerHeight;
//画图
c.drawRect(left, top, right, bottom, mPaint);
}
}
}
2.10 build.gradle配置如下:
android {
compileSdkVersion 29
buildToolsVersion "29.0.0"
defaultConfig {
applicationId "com.example.expandtextview"
minSdkVersion 15
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
}
下一篇会给出仿微信点赞评论弹框遇到的问题及解决方法, 最后附上项目地址:
写得不好,还望大家多多指教,如有问题,及时沟通更正.