最近在做类似朋友圈功能,其中有个细节是当文本内容多行时显示全文与收起,微信的文本内容是最多显示6行,我的例子是显示3行,这个可以根据自己的需求来写,之前写过一个可伸缩扩展的TextView带图标,和这个类似,但是有点区别,微信这个体验还是不错的,于是自己写了一个自定义的TextView实现了类似功能,用的是androidx的兼容包,如果小伙伴们不清楚androidx兼容包该咋写,可参考官网地址:迁移到 AndroidX | Android 开发者 | Android Developers.
1.微信的效果截图如下:
2.实现效果截图如下:
2.1 ExpandTextView的代码如下:
/*** @作者: njb* @时间: /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() {@Overridepublic 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() {@Overridepublic 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="/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@+id/contentText"android:layout_width="match_parent"android:layout_height="wrap_content"android:textColor="@color/color_232323"android:textSize="14sp"android:text=""/><TextViewandroid: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* @时间: /7/22 10:53* @描述: 仿微信朋友圈文本显示全文与收起*/public class MainActivity extends AppCompatActivity {private RecyclerView recyclerView;private CircleAdapter circleAdapter;private String content = "茫茫的长白大山,浩瀚的原始森林,大山脚下,原始森林环抱中散落着几十户人家的" +"一个小山村,茅草房,对面炕,烟筒立在屋后边。在村东头有一个独立的房子,那就是青年点," +"窗前有一道小溪流过。学子在这里吃饭,由这里出发每天随社员去地里干活。干的活要么上山伐" +"树,抬树,要么砍柳树毛子开荒种地。在山里,可听那吆呵声:“顺山倒了!”放树谨防回头棒!" +"树上的枯枝打到别的树上再蹦回来,这回头棒打人最厉害。";private List<String> strings;@Overrideprotected 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* @时间: /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@Overridepublic 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;}@Overridepublic void onBindViewHolder(@NonNull CircleViewHolder holder, int position) {holder.expandTextView.setText(list.get(position));}@Overridepublic 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* @时间: /7/22 11:07* @描述:*/public class App extends Application {//全局Contextprivate static Context sContext;@Overridepublic void onCreate() {super.onCreate();sContext = getApplicationContext();}public static Context getContext() {return sContext;}}
2.7CircleMovementMethod的代码如下:
/*** @作者: njb* @时间: /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){//移除点击时设置的背景spanbuffer.removeSpan(mBgSpan);}}else{}Selection.removeSelection(buffer);widget.setBackgroundResource(R.color.transparent);}else if(action == MotionEvent.ACTION_MOVE){//这种情况不用做处理}else{if(mBgSpan != null){//移除点击时设置的背景spanbuffer.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* @时间: /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@Overridepublic void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {super.getItemOffsets(outRect, view, parent, state);//outRect.bottom、left,right,top设置为int值,设置每一项的paddingoutRect.bottom =dividerHeight ;}//画图@Overridepublic 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 29buildToolsVersion "29.0.0"defaultConfig {applicationId "com.example.expandtextview"minSdkVersion 15targetSdkVersion 29versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles 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'}
下一篇会给出仿微信点赞评论弹框遇到的问题及解决方法,最后附上项目地址:
ExpandTextView: 实现仿微信朋友圈列表多类型布局,图片点击放大、保存,包含点赞、评论、消息提醒、视频播放等功能/jackning_admin/ExpandTextView
写得不好,还望大家多多指教,如有问题,及时沟通更正.