可伸缩的TextView
来源:互联网 发布:北京学java最好的学校 编辑:程序博客网 时间:2024/04/29 21:41
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/collapse private 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); } @Override public void setOrientation(int orientation){ /* if(LinearLayout.HORIZONTAL == orientation){ throw new IllegalArgumentException("ExpandableTextView only supports Vertical Orientation."); }*/ super.setOrientation(orientation); } @Override public 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 progress mAnimating = 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() { @Override public void onAnimationStart(Animation animation) { applyAlphaAnimation(mTv, mAnimAlphaStart); } @Override public void onAnimationEnd(Animation animation) { // clear animation here to avoid repeated applyTransformation() calls clearAnimation(); // clear the animation flag mAnimating = false; // notify the listener if (mListener != null) { mListener.onExpandStateChanged(mTv, !mCollapsed); } } @Override public void onAnimationRepeat(Animation animation) { } }); clearAnimation(); startAnimation(animation); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mAnimating; } @Override protected void onFinishInflate() { super.onFinishInflate(); findViews(); } @Override protected 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 needed mButton.setVisibility(View.GONE); mTv.setMaxLines(Integer.MAX_VALUE); // Measure super.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 lines mTextHeightWithMaxLines = 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 setup super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mCollapsed) { // Gets the margin between the TextView's bottom and the ViewGroup's bottom mTv.post(new Runnable() { @Override public void run() { mMarginBetweenTxtAndBottom = getHeight() - mTv.getHeight(); Log.e("onMeasure","mTv height = "+ mTv.getHeight() + " mMarginBetweenTxtAndBottom = " + mMarginBetweenTxtAndBottom ); } }); // Saves the collapsed height of this ViewGroup mCollapsedHeight = 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(); } @Nullable public 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 instant alphaAnimation.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); } @Override protected 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(); } @Override public void initialize( int width, int height, int parentWidth, int parentHeight ) { super.initialize(width, height, parentWidth, parentHeight); } @Override public 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的方向为竖直方向
这样水平方向和竖直方向都支持
@Override public 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
0 0
- 可伸缩的TextView
- 可伸缩的TextView
- android ExpandableTextView可伸缩的TextView
- TextView文字过长,可伸缩
- Android自定义View系列之可伸缩的TextView
- Android自定义View系列之可伸缩的TextView
- 部分伸缩的TextView
- 可伸缩的电子商务解决方案
- 可伸缩TextView和TextView显示错位问题
- 可伸缩的电子商务解决方案 2
- 可伸缩的搜索框
- Android ----可伸缩的控件
- 67. 可伸缩的 Comet
- 可伸缩展示的ListView
- 可拖动可伸缩的div
- 自定义控件,可以伸缩的textview
- android之仿微博Textview的伸缩效果
- 最简单的TextView伸缩效果
- Android 九宫格解锁Demo--Android 进阶之路
- 常用GIT bash 命令一览
- 2016.8.18 C组总结
- Android中facebook与google的第三方接入
- doGet与doPost的区别
- 可伸缩的TextView
- 关于今天装oracle之后用工具链接时的问题
- Python的数据库--MySQL结果集
- 用application loader上传ipa包遇到的问题
- Jenkins学习总结(5)——免费DevOps开源工具简介
- java的设计模式之二
- 《JAVA相关文章索引(1)》
- bzoj3238: [Ahoi2013]差异
- Introduction to LabKey and R Integration