超级扩展的textview(让我们开启自定义之旅吧

本文介绍如何自定义一个类似朋友圈内容查看全部的TextView,通过继承LinearLayout,实现展开和折叠效果。讲解了自定义属性、测量方法、动画应用,并强调在onFinishInflate和onMeasure中的操作。文章提供了核心代码解析,并鼓励读者实践提升自定义控件技能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

超级扩展的textview(让我们开启自定义之旅吧)

自定义已经家喻户晓了,自定义的实现有好几种,继承现有的控件,几种控件组合,继承view,其中继承view算是最难的啦,建议开始大家先继承现有的控件,其实自定义你完全理解了可以说so easy,但是对于刚起步的同学,感觉难是因为你们没去尝试,给自己定个小目标,每周写个自定义,自定义需要自己慢慢积累的,着急是来不了的,后期你会发现自定义,也就是位置的计算,这就考验大家的高中数学知识了,和大学里面的学的矩阵之类的。没好好学习是不是现在感觉后悔啦。。。。。。开玩笑一切可以弥补的啦.


现在看下效果图,类似朋友圈内容的查看全部。
展开前的
这里写图片描述
展开后的
这里写图片描述

喜欢或者需要的朋友可以继续往下面看———–

在项目res/values/下新建attrs.xml(当然其它名称也是可以的),在其中声明相关属性如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SuperExpandableTextViewAttr">
        <!-- 默认情况下需要显示的最大行数 -->
        <attr name="maxExpandLines" format="integer"></attr>
        <!-- 一定距离下,动画的时候 -->
        <attr name="duration" format="integer"></attr>
    </declare-styleable>
</resources>

其中,attar的格式即单位有:dimension(尺寸)、boolean(布尔)、color(颜色)、enum(枚举)、flag(位或)、float(浮点)、fraction(百分比)、integer(整型)、reference(资源引用)、string(字符串)。
1.首先需要我们自定义下属性,但是属性在xml 运用的时候一定加入xmlns:app=”http://schemas.android.com/apk/res-auto要不然识别不了我们的属性,
2然后需要在我们自定义view 里面实现这个构造

 private void init(Context context, AttributeSet attrs) {
        setOrientation(VERTICAL);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SuperExpandableTextViewAttr);
        maxExpandLines = array.getInteger(R.styleable.SuperExpandableTextViewAttr_maxExpandLines, 3);
        duration = array.getInteger(R.styleable.SuperExpandableTextViewAttr_duration, 500);
        array.recycle();
    }

注意:TypedArray对象是一个共享资源,使用后必须调用recycle()回收。目的是为了缓存,不需要每次调用TypedArray的时候都重新分配内存,方便了其它地方的复用。
3.此次我们自定义在LinearLayout 的基础上定义的
public class SuperEpandableTextView extends LinearLayout
4.我们需要重写onFinishInflate 此方法, 在这这个方法里面进行找控件的操作,为什么在这里面进行呢,这个方法的作用是//当加载完成xml后,就会执行那个方法。

  @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        id_source_textview = (TextView) findViewById(R.id.id_source_textview);
        id_expand_textview = (TextView) findViewById(R.id.id_expand_textview);
        id_expand_textview.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                SuperAnimation animation;

                isCollapsed=!isCollapsed;
                if (isCollapsed) {
                    id_expand_textview.setText("查看更多");
                    if (listener!=null) {
                        listener.onExpandStateChanged(true);
                    }
                    animation=new SuperAnimation(getHeight(), collapsedHeight);
                }
                else {
                    id_expand_textview.setText("收起");
                    if (listener!=null) {
                        listener.onExpandStateChanged(false);
                    }
                    animation=new SuperAnimation(getHeight(), realTextViewHeigt+lastHeight);
                }
                //只是将view移动到了目标位置,但是view绑定的点击事件还在原来位置,导致点击时会先闪一下
                animation.setFillAfter(true);
                animation.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {
                        isAnimate=true;
                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {
                        clearAnimation();
                        isAnimate=false;
                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {

                    }
                });
                clearAnimation();
                startAnimation(animation);

            }
        });

5.我们还需要重写测量的方法onMeasure ,在此处进行对控件进行操作,显示隐藏等

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //如果隐藏控件或者textview的值没有发生改变,那么不进行测量
        if (getVisibility()==GONE || !isChange) {
            return;
        }
        isChange=false;

        //初始化默认状态,即正常显示文本
        id_expand_textview.setVisibility(GONE);
        id_source_textview.setMaxLines(Integer.MAX_VALUE);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //如果本身没有达到收起展开的限定要求,则不进行处理
        if (id_source_textview.getLineCount()<=maxExpandLines) {
            return;
        }

        //初始化高度赋值,为后续动画事件准备数据
        realTextViewHeigt=getRealTextViewHeight(id_source_textview);

        //如果处于收缩状态,则设置最多显示行数
        if (isCollapsed) {
            id_source_textview.setLines(maxExpandLines);
        }
        id_expand_textview.setVisibility(VISIBLE);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (isCollapsed) {
            id_source_textview.post(new Runnable() {
                @Override
                public void run() {
                    lastHeight=getHeight()-id_source_textview.getHeight();
                    collapsedHeight=getMeasuredHeight();
                }
            });
        }
    }

解释下上面代码两个地方,post(new Runnable() {}为什么要post出去,这样才能布局绘制完成,测量的值才是正确,getMeasuredHeight()是实际View的大小,与屏幕无关,而getHeight的大小此时则是屏幕的大小

6大家看见了里面我用到了Animation这是为了增强用户的体验,Animation的实现很简单这里用到了估值器, 解释下, 比如view 移动的距离是10,需要的时间5秒 估值器会计算每一秒需要移动的距离。

/**
     * 此处用到了估值器
     */
    public int getValue(int startValue, int endValue, float Time) {
        int value = (int) ((endValue - startValue) * Time + startValue);
        return value;

    }

    private class SuperAnimation extends Animation {
        int startValue = 0;
        int endValue = 0;

        public SuperAnimation(int startValue, int endValue) {
            setDuration(duration);
            this.startValue = startValue;
            this.endValue = endValue;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            int height = getValue(startValue, endValue, interpolatedTime);
            id_source_textview.setMaxHeight(height - lastHeight);
            SuperEpandableTextView.this.getLayoutParams().height = height;
            SuperEpandableTextView.this.requestLayout();
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    }

核心代码解释完了,需要完整代码我可以上传github。
参考文献:http://www.shorr.cn/2016/11/25/Android自定义View(一):基础篇/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值