Android仿微信朋友圈1自定义TextView(文本内容多行显示全文与收起)

         最近在做类似朋友圈功能,其中有个细节是当文本内容多行时显示全文与收起,微信的文本内容是最多显示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'
}

下一篇会给出仿微信点赞评论弹框遇到的问题及解决方法, 最后附上项目地址:

ExpandTextView: 实现仿微信朋友圈列表多类型布局,图片点击放大、保存,包含点赞、评论、消息提醒、视频播放等功能https://gitee.com/jackning_admin/ExpandTextView

写得不好,还望大家多多指教,如有问题,及时沟通更正.

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值