GIF录制效果不是太好:

原理:
1,获取textview有多少行,根据行数显示箭头图标。
2,获取只有一行时(自己设定的行数),textview的高度,并且记录。
3,点击textview或者图标执行动画,动态改变textview的高度。

ExpandableTextViewHorizontal


package com.ms.square.android.expandabletextview;public class ExpandableTextViewHorizontal extends LinearLayout implements View.OnClickListener {private static final String TAG = ExpandableTextViewHorizontal.class.getSimpleName();/* The default number of lines */private static final int MAX_COLLAPSED_LINES = 8;/* The default animation duration */private static final int DEFAULT_ANIM_DURATION = 300;/* The default alpha value when the animation starts */private static final float DEFAULT_ANIM_ALPHA_START = 0.7f;protected TextView mTv;protected ImageButton mButton; // Button to expand/collapseprivate boolean mRelayout = true;/*** true 折叠,箭头是向下的,false 展开,箭头是向上的<br/>* 同时也影响xml布局界面<br/>* 默认必须为true*/private boolean mCollapsed = true; // Show short version as default.private int mCollapsedHeight;/*** textview整体高度*/private int mTextHeightWithMaxLines;private int mMaxCollapsedLines;private int mMarginBetweenTxtAndBottom;/*** 箭头向下 展开*/private Drawable mExpandDrawable;/*** 箭头向上 折叠*/private Drawable mCollapseDrawable;private int mAnimationDuration;private float mAnimAlphaStart;private boolean mAnimating;/* Listener for callback */private OnExpandStateChangeListener mListener;/* For saving collapsed status when used in ListView */private SparseBooleanArray mCollapsedStatus;private int mPosition;public ExpandableTextViewHorizontal(Context context) {this(context, null);}public ExpandableTextViewHorizontal(Context context, AttributeSet attrs) {super(context, attrs);init(attrs);}@TargetApi(Build.VERSION_CODES.HONEYCOMB)public ExpandableTextViewHorizontal(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init(attrs);}private void init(AttributeSet attrs) {TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableTextView);mMaxCollapsedLines = typedArray.getInt(R.styleable.ExpandableTextView_maxCollapsedLines, MAX_COLLAPSED_LINES);mAnimationDuration = typedArray.getInt(R.styleable.ExpandableTextView_animDuration, DEFAULT_ANIM_DURATION);mAnimAlphaStart = typedArray.getFloat(R.styleable.ExpandableTextView_animAlphaStart, DEFAULT_ANIM_ALPHA_START);mExpandDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_expandDrawable);mCollapseDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_collapseDrawable);if (mExpandDrawable == null) {mExpandDrawable = getDrawable(getContext(), R.drawable.ic_expand_more_black_12dp);}if (mCollapseDrawable == null) {mCollapseDrawable = getDrawable(getContext(), R.drawable.ic_expand_less_black_12dp);}typedArray.recycle();setOrientation(getOrientation());}private void findViews() {mTv = (TextView) getChildAt(0);mTv.setOnClickListener(this);mButton = (ImageButton) getChildAt(1);mButton.setImageDrawable(mCollapsed ? mExpandDrawable : mCollapseDrawable);mButton.setOnClickListener(this);getRootView().setOnClickListener(this);}@Overridepublic void setOrientation(int orientation){/* if(LinearLayout.HORIZONTAL == orientation){throw new IllegalArgumentException("ExpandableTextView only supports Vertical Orientation.");}*/super.setOrientation(orientation);}@Overridepublic void onClick(View view) {startAnimationClick();}private void startAnimationClick() {if (mButton.getVisibility() != View.VISIBLE) {return;}mCollapsed = !mCollapsed;mButton.setImageDrawable(mCollapsed ? mExpandDrawable : mCollapseDrawable);if (mCollapsedStatus != null) {mCollapsedStatus.put(mPosition, mCollapsed);}// mark that the animation is in progressmAnimating = true;Animation animation;if (mCollapsed) {animation = new ExpandCollapseAnimation(this, getHeight(), mCollapsedHeight);} else {animation = new ExpandCollapseAnimation(this, getHeight(), getHeight() +  mTextHeightWithMaxLines - mTv.getHeight());}Log.e("onClick","mCollapsedHeight = "+ mCollapsedHeight + "  hhhhh = "+(getHeight() +  mTextHeightWithMaxLines - mTv.getHeight()));animation.setFillAfter(true);animation.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {applyAlphaAnimation(mTv, mAnimAlphaStart);}@Overridepublic void onAnimationEnd(Animation animation) {// clear animation here to avoid repeated applyTransformation() callsclearAnimation();// clear the animation flagmAnimating = false;// notify the listenerif (mListener != null) {mListener.onExpandStateChanged(mTv, !mCollapsed);}}@Overridepublic void onAnimationRepeat(Animation animation) { }});clearAnimation();startAnimation(animation);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {return mAnimating;}@Overrideprotected void onFinishInflate() {super.onFinishInflate();findViews();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// If no change, measure and return
//        if (!mRelayout || getVisibility() == View.GONE) {if (!mRelayout) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);return;}mRelayout = false;// Setup with optimistic case// i.e. Everything fits. No button neededmButton.setVisibility(View.GONE);mTv.setMaxLines(Integer.MAX_VALUE);// Measuresuper.onMeasure(widthMeasureSpec, heightMeasureSpec);Log.e("onMeasure","mCollapsedHeight = "+ mCollapsedHeight + " mCollapsed = " + mCollapsed + " line count = "+ mTv.getLineCount() + " mCollapsed = "+ mCollapsed);// If the text fits in collapsed mode, we are done.if (mTv.getLineCount() <= mMaxCollapsedLines) {return;}super.onMeasure(widthMeasureSpec, heightMeasureSpec);// Saves the text height w/ max linesmTextHeightWithMaxLines = getRealTextViewHeight(mTv);// Doesn't fit in collapsed mode. Collapse text view as needed. Show// button.if (mCollapsed) {mTv.setMaxLines(mMaxCollapsedLines);}mButton.setVisibility(View.VISIBLE);// Re-measure with new setupsuper.onMeasure(widthMeasureSpec, heightMeasureSpec);if (mCollapsed) {// Gets the margin between the TextView's bottom and the ViewGroup's bottommTv.post(new Runnable() {@Overridepublic void run() {mMarginBetweenTxtAndBottom = getHeight() - mTv.getHeight();Log.e("onMeasure","mTv height  = "+ mTv.getHeight() + " mMarginBetweenTxtAndBottom = " + mMarginBetweenTxtAndBottom );}});// Saves the collapsed height of this ViewGroupmCollapsedHeight = getMeasuredHeight();Log.e("onMeasure","mCollapsedHeight = "+ mCollapsedHeight + " mTextHeightWithMaxLines = " + mTextHeightWithMaxLines );}}public void setOnExpandStateChangeListener(@Nullable OnExpandStateChangeListener listener) {mListener = listener;}public void setText(@Nullable CharSequence text) {mRelayout = true;mTv.setText(text);setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);}public void setText(@Nullable CharSequence text, @NonNull SparseBooleanArray collapsedStatus, int position) {mCollapsedStatus = collapsedStatus;mPosition = position;boolean isCollapsed = collapsedStatus.get(position, true);clearAnimation();mCollapsed = isCollapsed;mButton.setImageDrawable(mCollapsed ? mExpandDrawable : mCollapseDrawable);setText(text);getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;requestLayout();}/*** 其他控件点击的时候,执行缩放或者展开动画*/public void startAnimation() {startAnimationClick();}@Nullablepublic CharSequence getText() {if (mTv == null) {return "";}return mTv.getText();}private static boolean isPostHoneycomb() {return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;}private static boolean isPostLolipop() {return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;}@TargetApi(Build.VERSION_CODES.HONEYCOMB)private static void applyAlphaAnimation(View view, float alpha) {if (isPostHoneycomb()) {view.setAlpha(alpha);} else {AlphaAnimation alphaAnimation = new AlphaAnimation(alpha, alpha);// make it instantalphaAnimation.setDuration(0);alphaAnimation.setFillAfter(true);view.startAnimation(alphaAnimation);}}@TargetApi(Build.VERSION_CODES.LOLLIPOP)private static Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {Resources resources = context.getResources();if (isPostLolipop()) {return resources.getDrawable(resId, context.getTheme());} else {return resources.getDrawable(resId);}}private static int getRealTextViewHeight(@NonNull TextView textView) {int textHeight = textView.getLayout().getLineTop(textView.getLineCount());Log.e("getRealTextViewHeight","textHeight = "+ textHeight + " getHeight = " + textView.getMeasuredHeight());int padding = textView.getCompoundPaddingTop() + textView.getCompoundPaddingBottom();return textHeight + padding;}public boolean isCollapsed() {return mCollapsed;}class ExpandCollapseAnimation extends Animation {private final View mTargetView;private final int mStartHeight;private final int mEndHeight;public ExpandCollapseAnimation(View view, int startHeight, int endHeight) {mTargetView = view;mStartHeight = startHeight;mEndHeight = endHeight;setDuration(mAnimationDuration);}@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {final int newHeight = (int)((mEndHeight - mStartHeight) * interpolatedTime + mStartHeight);Log.e("applyTransformation","newHeight = "+ newHeight + " mMarginBetweenTxtAndBottom = " + mMarginBetweenTxtAndBottom  + " mStartHeight = " + mStartHeight);mTv.setMaxHeight(newHeight - mMarginBetweenTxtAndBottom);if (Float.compare(mAnimAlphaStart, 1.0f) != 0) {applyAlphaAnimation(mTv, mAnimAlphaStart + interpolatedTime * (1.0f - mAnimAlphaStart));}mTargetView.getLayoutParams().height = newHeight;mTargetView.requestLayout();}@Overridepublic void initialize( int width, int height, int parentWidth, int parentHeight ) {super.initialize(width, height, parentWidth, parentHeight);}@Overridepublic boolean willChangeBounds( ) {return true;}}public interface OnExpandStateChangeListener {/*** Called when the expand/collapse animation has been finished** @param textView - TextView being expanded/collapsed* @param isExpanded - true if the TextView has been expanded*/void onExpandStateChanged(TextView textView, boolean isExpanded);}
}

自定义属性attrs

 <declare-styleable name="ExpandableTextView"><attr name="maxCollapsedLines" format="integer"/><attr name="animDuration" format="integer"/><attr name="animAlphaStart" format="float"/><attr name="expandDrawable" format="reference"/><attr name="collapseDrawable" format="reference"/></declare-styleable>

代码修改地方:

1,去掉原固定设置linearlayout的方向为竖直方向
这样水平方向和竖直方向都支持

  @Overridepublic void setOrientation(int orientation){if(LinearLayout.HORIZONTAL == orientation){throw new IllegalArgumentException("ExpandableTextView only supports Vertical Orientation.");}super.setOrientation(orientation);}

2,通过getChildAt来获取对应的控件,
不再使用R.id.expandable_text 和 R.id.expand_collapse

添加getRootView().setOnClickListener(this);

   private void findViews() {mTv = (TextView) getChildAt(0);mTv.setOnClickListener(this);mButton = (ImageButton) getChildAt(1);mButton.setImageDrawable(mCollapsed ? mExpandDrawable : mCollapseDrawable);mButton.setOnClickListener(this);// 添加根布局点击事件getRootView().setOnClickListener(this);}

DEMO下载:http://download.csdn.net/detail/huyuchaoheaven/9607448

参考和感谢:
https://github.com/Manabu-GT/ExpandableTextView

可伸缩的TextView相关推荐

  1. android ExpandableTextView可伸缩的TextView

    类似微信朋友圈里面,当文章太长时会隐藏一部分,当点击展开时就全部显示,这样一种效果. 关于这个自定义ViewGroup: 1.onFinishInflate方法执行的时机 2.自定义的animatio ...

  2. 常用的第三方框架汇总

    Android热门三方库源码面试宝典 工具类合集: https://github.com/Blankj/AndroidUtilCode (32k) https://blankj.com/2016/07 ...

  3. ArrayAdapter requires the resource ID to be a TextView

    这个是使用ListView 的ArrayAdapter 加载自定义布局的时候跟布局没有使用TextView 引起的,处理方法就是跟布局换成TextView 如下 <?xml version=&q ...

  4. Spinner 使用的使用 报错:ArrayAdapter requires the resource ID to be a TextView

    Spinner 使用的使用 报错:ArrayAdapter requires the resource ID to be a TextView 的问题 是修改layout的最外成不是使用TextVie ...

  5. Android Textview 实现版权符号© 的 实现

    版权 @ 这个实现其实很简单就是在TextView 里面 添加  \u00a9 即可 具体效果如下 <TextViewandroid:id="@+id/btn"android ...

  6. Android TextView 去除顶部和底部留白(上下的间距有空白问题处理)

    有时候我们完全按照ui 布局写的代码 看到的效果却和ui的效果有一定的差距 有没有很苦恼的效果,最近帮同事改bug 发现了这个问题,很多地方都是他私自调节的 这个问题不可有啊,还是要按照ui的尺寸来, ...

  7. Android 替换TextView 里面指定的符号

    有时候接口返回的标签类型如下 热血,爱情,经典 中间是,但是ui交互图是 热血/爱情/经典 这个时候我们替换一下就行了,没有必要要求别人去该 使用方法为replace 也是简单的一行代码就可以实现效果 ...

  8. Android TextView设置透明度方法的注意点

    TextView 设置字体透明度 一般自己都是在颜色值前面添加多少透明度即可, 不如设置字体颜色的透明度为50% android:textColor="#50D81B60" 这里 ...

  9. Android TextView 在strings 里面 实现换行

    在TextView 把text 使用快捷键放到strings 里面 \n是无法被放到里面的 如果想实现换行 可以手动在里面添加 \n 例如写了一个小例子, 如果你写了多语言适配 ,那么在你的其他的st ...

最新文章

  1. centos使用镜像源轻松配置golang+vscode的方法
  2. 深度学习3:手动实现L2正则化(L2 Regularization)
  3. TP5 实现微信支付和支付宝支付
  4. 日历记事本java代码_急需日历记事本JAVA源代码
  5. ngrx注入到应用类构造函数里的store变量
  6. onesignal php,PHP FPM源代码反刍品味之五:信号signal处理
  7. Myeclipse学习总结(11)——Eclipse中设置Java/Css/Html/Jsp换行长度
  8. android权限列表
  9. [SQL实战]之查找当前薪水排名第二多的员工编号emp_no、薪水salary、last_name以及first_name,不准使用order by
  10. git commit后,如何撤销commit
  11. pyodbc 连接informix
  12. 什么是分布式定时任务框架?
  13. [Qt笔记]设置VS2015下的应用工程图标
  14. 数据库内外连接、自连接
  15. wannacry作者捉到了吗_WannaCry爆发的根源原来是它?
  16. 工作室培训第一周总结
  17. 一日精通python编程_爱上Python 一日精通Python编程 [Learn Python in One Day and Learn it Well ]...
  18. oracle期初余额录入,用金蝶kis录入数量初始数据的方法
  19. 【智能优化算法】基于蜉蝣算法求解多目标优化问题附matlab代码
  20. python使用手册<5>函数

热门文章

  1. 在Windows上安装TkInter:完整教程
  2. php mysql 模糊查询_PHP如何实现模糊查询(图文代码)
  3. php数组元素倒序输出,PHP数组foreach倒序输出
  4. java 读取远程文件并让浏览器下载
  5. c语言中像y的字符是什么意思,C语言中字符型(char)的简单使用
  6. 10.7 抽象类最佳实践-模板设计模式
  7. 谷粒商城BUG P52新建二级目录中新建三级目录无法显示bug解决
  8. 算法该不该刷?如何高效刷算法?
  9. C/C++业内行情分析学习计划
  10. Camera1.0和Camear2.0的区别