源码如下:
package com.example.bkhu.myapplication;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* Created by bkhu on 17/1/2.
*/
public class cutomeView extends LinearLayout {
private boolean isExprand = false;
private TextView contentView = null;
private TextView expandText = null;
private int mMaxCollapseLine = 3;
private Drawable expandDrable = null;
private Drawable collapseDrawable = null;
public cutomeView(Context context) {
this(context, null);
}
public cutomeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public cutomeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.cutomeView, defStyleAttr, 0);
expandDrable = a.getDrawable(R.styleable.cutomeView_expand_drawable);
collapseDrawable = a.getDrawable(R.styleable.cutomeView_collapse_drawable);
if (expandDrable != null) {
expandDrable.setBounds(0, 0, 48, 24);
}
if (collapseDrawable != null) {
collapseDrawable.setBounds(0, 0, 48, 24);
}
initView(context);
}
public void initView(Context context) {
if (getChildCount() > 0) {
removeAllViews();
}
TextView titleTextView = new TextView(context);
titleTextView.setText("title");
addView(titleTextView);
contentView = new TextView(context);
contentView.setEllipsize(TextUtils.TruncateAt.END);
contentView.setTextSize(15);
contentView.setText("内容内容内容内容内容内容内容内容内容" +
"内容内容内容内容内容内容内内容内容内容内容内容内容内容内容内容内容" +
"容内容内容内容内容内容内容内容内容内容内容内容内" +
"容内容内容内容内容内容内容内容内容内容内容内容内容" +
"内容内容内容内容内容内容内容内容内容内容内容内容内容内容" +
"内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容" +
"内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容");
addView(contentView);
expandText = new TextView(context);
expandText.setEllipsize(TextUtils.TruncateAt.END);
contentView.setTextSize(15);
expandText.setText("点击");
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.RIGHT;
expandText.setLayoutParams(params);
addView(expandText, params);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (expandText != null) {
expandText.setVisibility(View.GONE);
}
if (contentView != null) {
contentView.setMaxLines(Integer.MAX_VALUE);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (contentView != null && !isExprand) {
contentView.setMaxLines(mMaxCollapseLine);
}
if (expandText != null) {
expandText.setVisibility(View.VISIBLE);
if (isExprand) {
expandText.setCompoundDrawables(null, null, collapseDrawable, null);
} else {
expandText.setCompoundDrawables(null, null, expandDrable, null);
}
expandText.setText(getText());
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void setExprand(boolean isexprand) {
isExprand = !isexprand;
requestLayout();
if (mOnChangeCallBack != null) {
mOnChangeCallBack.change(isExprand);
}
}
public boolean getExprand() {
return isExprand;
}
private String getText() {
return isExprand ? "收起" : "展开";
}
public OnChangeCallBack mOnChangeCallBack;
public void setOnChangeCallBack(OnChangeCallBack onChangeCallBack) {
this.mOnChangeCallBack = onChangeCallBack;
}
public interface OnChangeCallBack {
void change(boolean isExprand);
}
}
attrs.xml
使用方式:
View view = inflater.inflate(R.layout.item_view, null);
final cutomeView cutomeView = (cutomeView) view.findViewById(R.id.cotume_view);
cutomeView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cutomeView.setExprand(isExprand);
}
});
cutomeView.setOnChangeCallBack(new cutomeView.OnChangeCallBack() {
@Override
public void change(boolean isexprand) {
isExprand = isexprand;
}
});
注意:
expandDrable = a.getDrawable(R.styleable.cutomeView_expand_drawable);
collapseDrawable = a.getDrawable(R.styleable.cutomeView_collapse_drawable);
if (expandDrable != null) {
expandDrable.setBounds(0, 0, 48, 24);
}
if (collapseDrawable != null) {
collapseDrawable.setBounds(0, 0, 48, 24);
}
drawable 必须设置setBounds 不然现实不出来,只有设置固定大小,就可以现实出来。
后期对折叠空间进行了扩展,现在需求中很多的需求,
1 当显示的内容不大于设定行数,添加设定行数的属性,
2 对于折叠控件的大小样式进行扩展
package com.example.bkhu.myapplication;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.DynamicLayout;
import android.text.Layout;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.example.bkhu.myapplication.R;
/**
* Created by bkhu on 17/1/2.
*/
public class HotelCustomExpandView extends LinearLayout implements View.OnClickListener {
private boolean isExpand = false;
private TextView contentView = null;
private TextView expandText = null;
private int mMaxCollapseLine = 2;
private Drawable expandDrawable = null;
private Drawable collapseDrawable = null;
private int mContentTextStyle;
private int mExpandTextStyle;
private String mExpandTextTitle;
private String mCollapseTextTitle;
private boolean onClick;
public HotelCustomExpandView(Context context) {
this(context, null);
}
public HotelCustomExpandView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HotelCustomExpandView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.hotel_expand_customView, defStyleAttr, 0);
expandDrawable = a.getDrawable(R.styleable.hotel_expand_customView_expand_drawable);
collapseDrawable = a.getDrawable(R.styleable.hotel_expand_customView_collapse_drawable);
mContentTextStyle = a.getResourceId(R.styleable.hotel_expand_customView_content_text_style, 0);
mExpandTextStyle = a.getResourceId(R.styleable.hotel_expand_customView_expand_text_style, 0);
mExpandTextTitle = a.getString(R.styleable.hotel_expand_customView_expand_text_title);
mCollapseTextTitle = a.getString(R.styleable.hotel_expand_customView_collapse_text_title);
if (expandDrawable != null) {
expandDrawable.setBounds(0, 0, 40, 24);
}
if (collapseDrawable != null) {
collapseDrawable.setBounds(0, 0, 40, 24);
}
a.recycle();
initView(context);
}
public void initView(Context context) {
if (getChildCount() > 0) {
removeAllViews();
}
contentView = new TextView(context);
contentView.setEllipsize(TextUtils.TruncateAt.END);
contentView.setTextAppearance(context, mContentTextStyle);
contentView.setOnClickListener(this);
contentView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
if (getOrientation() == LinearLayout.HORIZONTAL) {
LinearLayout.LayoutParams contentViewParams = new LinearLayout.LayoutParams(0,
LayoutParams.WRAP_CONTENT);
contentViewParams.weight = 1;
contentView.setLayoutParams(contentViewParams);
addView(contentView, contentViewParams);
} else {
addView(contentView);
}
expandText = new TextView(context);
expandText.setEllipsize(TextUtils.TruncateAt.END);
expandText.setOnClickListener(this);
expandText.setTextAppearance(context, mExpandTextStyle);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
if (getOrientation() == LinearLayout.HORIZONTAL) {
params.gravity = Gravity.BOTTOM;
} else {
params.gravity = Gravity.RIGHT;
}
expandText.setLayoutParams(params);
addView(expandText, params);
}
public ViewTreeObserver.OnGlobalLayoutListener listener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
contentView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
if (contentView.getLineCount() > mMaxCollapseLine) {
contentView.setMaxLines(2);
} else {
removeView(expandText);
}
contentView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
};
public void setContextText(CharSequence sequence) {
if (contentView != null) {
contentView.setText(sequence);
}
// contentView.post(new Runnable() {
// @Override
// public void run() {
// if (contentView.getLineCount() > mMaxCollapseLine) {
// contentView.setMaxLines(2);
//
// } else {
// removeView(expandText);
// }
//
// }
// });
}
public void setMaxLines(int linesCount) {
mMaxCollapseLine = linesCount;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (expandText != null) {
expandText.setVisibility(View.GONE);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (contentView != null && !isExpand && onClick) {
contentView.setMaxLines(mMaxCollapseLine);
}
if (expandText != null) {
expandText.setVisibility(View.VISIBLE);
if (isExpand) {
expandText.setCompoundDrawables(null, null, collapseDrawable, null);
} else {
expandText.setCompoundDrawables(null, null, expandDrawable, null);
}
expandText.setText(getText());
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void setExpand(boolean isexpand) {
isExpand = isexpand;
if (contentView != null) {
contentView.setMaxLines(isExpand ? Integer.MAX_VALUE : mMaxCollapseLine);
}
invalidate();
requestLayout();
}
private String getText() {
return isExpand ? !TextUtils.isEmpty(mCollapseTextTitle) ? mCollapseTextTitle : "" : !TextUtils.isEmpty(mExpandTextTitle) ? mExpandTextTitle : "";
}
public OnChangeCallBack mOnChangeCallBack;
public void setOnChangeCallBack(OnChangeCallBack onChangeCallBack) {
this.mOnChangeCallBack = onChangeCallBack;
}
@Override
public void onClick(View v) {
onClick = true;
isExpand = !isExpand;
requestLayout();
if (mOnChangeCallBack != null) {
mOnChangeCallBack.change(isExpand);
}
}
public interface OnChangeCallBack {
void change(boolean isExpand);
}
}
在使用的时候,注意对 声明可配置的属性定义
1.reference:参考某一资源ID( 图片资源之类的)
2. color:颜色值
3. boolean:布尔值
4. dimension:尺寸值
5. float:浮点值
6. integer:整型值
7. string:字符串
8. fraction:百分数
9. enum:枚举值
10. flag:位或运算
在使用的过程还是要注意的,不然会报错,
后期对这个控件的改造,使得该控件支持支持默认的展示
package ctrip.android.hotel.view.common.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import ctrip.android.hotel.R;
/**
* Created by bkhu on 17/1/2.
*/
public class HotelCustomExpandView extends LinearLayout implements View.OnClickListener {
private boolean isExpand = true;
private TextView contentView = null;
private TextView expandText = null;
private static int mMaxLineCount = 4;
private static int mDisplayLineCount = 2;
private Drawable expandDrawable = null;
private Drawable collapseDrawable = null;
private int mContentTextStyle;
private int mExpandTextStyle;
private String mExpandTextTitle;
private String mCollapseTextTitle;
private boolean onClick;
private int mTextLineSpace;
private int mDrawableWidth;
private int mDrawableHeight;
public HotelCustomExpandView(Context context) {
this(context, null);
}
public HotelCustomExpandView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HotelCustomExpandView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.hotel_expand_customView, defStyleAttr, 0);
expandDrawable = a.getDrawable(R.styleable.hotel_expand_customView_expand_drawable);
collapseDrawable = a.getDrawable(R.styleable.hotel_expand_customView_collapse_drawable);
mContentTextStyle = a.getResourceId(R.styleable.hotel_expand_customView_expand_content_text_style, 0);
mExpandTextStyle = a.getResourceId(R.styleable.hotel_expand_customView_expand_text_style, 0);
mExpandTextTitle = a.getString(R.styleable.hotel_expand_customView_expand_text_title);
mCollapseTextTitle = a.getString(R.styleable.hotel_expand_customView_collapse_text_title);
mTextLineSpace = (int) a.getDimension(R.styleable.hotel_expand_customView_context_line_space, 0);
mDrawableHeight = (int) a.getDimension(R.styleable.hotel_expand_customView_expand_collapse_drawable_height, 0);
mDrawableWidth = (int) a.getDimension(R.styleable.hotel_expand_customView_expand_collapse_drawable_width, 0);
mMaxLineCount = a.getInt(R.styleable.hotel_expand_customView_max_line_count, 0);
mDisplayLineCount = a.getInt(R.styleable.hotel_expand_customView_display_line_count, 0);
if (expandDrawable != null) {
expandDrawable.setBounds(0, 0, mDrawableWidth, mDrawableHeight);
}
if (collapseDrawable != null) {
collapseDrawable.setBounds(0, 0, mDrawableWidth, mDrawableHeight);
}
a.recycle();
initView(context);
}
public void initView(Context context) {
if (getChildCount() > 0) {
removeAllViews();
}
contentView = new TextView(context);
contentView.setEllipsize(TextUtils.TruncateAt.END);
contentView.setTextAppearance(context, mContentTextStyle);
contentView.setLineSpacing(mTextLineSpace, 1);
contentView.setOnClickListener(this);
if (getOrientation() == LinearLayout.HORIZONTAL) {
LinearLayout.LayoutParams contentViewParams = new LinearLayout.LayoutParams(0,
LayoutParams.WRAP_CONTENT);
contentViewParams.weight = 1;
contentView.setLayoutParams(contentViewParams);
addView(contentView, contentViewParams);
} else {
addView(contentView);
}
expandText = new TextView(context);
expandText.setEllipsize(TextUtils.TruncateAt.END);
expandText.setOnClickListener(this);
expandText.setTextAppearance(context, mExpandTextStyle);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
if (getOrientation() == LinearLayout.HORIZONTAL) {
params.gravity = Gravity.BOTTOM;
} else {
params.gravity = Gravity.RIGHT;
}
expandText.setLayoutParams(params);
addView(expandText);
}
public void setContextText(CharSequence sequence) {
if (contentView != null) {
contentView.setVisibility(View.GONE);
contentView.setText(sequence);
}
}
public void setMaxLines(int linesCount) {
mMaxLineCount = linesCount;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (expandText == null) {
expandText.setVisibility(View.GONE);
}
if (contentView != null) {
contentView.setVisibility(View.VISIBLE);
contentView.setMaxLines(Integer.MAX_VALUE);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (contentView != null && contentView.getLineCount() <= mMaxLineCount) {
contentView.setVisibility(View.VISIBLE);
return;
} else {
if (contentView != null && !isExpand && onClick) {
contentView.setVisibility(View.VISIBLE);
contentView.setMaxLines(Integer.MAX_VALUE);
} else {
contentView.setMaxLines(mDisplayLineCount);
contentView.setVisibility(View.VISIBLE);
}
}
if (expandText != null) {
expandText.setVisibility(View.VISIBLE);
if (!isExpand) {
expandText.setCompoundDrawables(null, null, collapseDrawable, null);
} else {
expandText.setCompoundDrawables(null, null, expandDrawable, null);
}
expandText.setText(getText());
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void setExpand(boolean isexpand) {
isExpand = isexpand;
if (contentView != null) {
contentView.setMaxLines(isExpand ? Integer.MAX_VALUE : mDisplayLineCount);
}
invalidate();
requestLayout();
}
private String getText() {
return isExpand ? !TextUtils.isEmpty(mCollapseTextTitle) ? mCollapseTextTitle : "" : !TextUtils.isEmpty(mExpandTextTitle) ? mExpandTextTitle : "";
}
public OnChangeCallBack mOnChangeCallBack;
public void setOnChangeCallBack(OnChangeCallBack onChangeCallBack) {
this.mOnChangeCallBack = onChangeCallBack;
}
@Override
public void onClick(View v) {
onClick = true;
isExpand = !isExpand;
if (mOnChangeCallBack != null) {
mOnChangeCallBack.change(isExpand);
}
}
public interface OnChangeCallBack {
void change(boolean isExpand);
}
}
其实网上有另一种处理,代替方案
public void setText(String str){
mTextView.setText(str);
mTextView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (!hasGetLineCount) {
hasMore = mTextView.getLineCount() > showLine;
hasGetLineCount=true;
}
mClickToShow.setVisibility(hasMore?VISIBLE:GONE);
mTextView.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
});
}
在onpreDraw 的方法中去获取内容的行数。
ViewTreeObserver.OnPreDrawListener这个接口,那么textview必定实现了这个方法回调,而且必定是有得到StaticLayout或者DynamicLayout,否则我们得到的linecount只能为0.
以上的方案其实都是利用组合空间的方式,还有直接继承TextView 的方式,类似的代码如下:
package ctrip.android.hotel.view.common.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.widget.TextView;
import ctrip.android.hotel.R;
public class ExpandableTextView extends TextView {
private static final String TAG = ExpandableTextView.class.getSimpleName();
private int mMaxCollapseLine = 1;
private boolean mIsExpanded = false;
private boolean mIsDisableExpand;
private Drawable mCollapseButtonDrawable;
private Drawable mExpandButtonDrawable;
public ExpandableTextView(Context context) {
this(context, null);
}
public ExpandableTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ExpandableTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ExpandableTextView, defStyle, 0);
mMaxCollapseLine = a.getInt(R.styleable.ExpandableTextView_max_collapse_line, 1);
mIsExpanded = a.getBoolean(R.styleable.ExpandableTextView_is_expanded, false);
int buttonDrawableWidth = a.getDimensionPixelSize(R.styleable.ExpandableTextView_button_drawable_width, 0);
int buttonDrawableHeight = a.getDimensionPixelSize(R.styleable.ExpandableTextView_button_drawable_height, 0);
mCollapseButtonDrawable = a.getDrawable(R.styleable.ExpandableTextView_collapse_button);
if (mCollapseButtonDrawable != null) {
int width = buttonDrawableWidth > 0 ? buttonDrawableWidth : mCollapseButtonDrawable.getIntrinsicWidth();
int height = buttonDrawableHeight > 0 ? buttonDrawableHeight : mCollapseButtonDrawable.getIntrinsicHeight();
mCollapseButtonDrawable.setBounds(0, 0, width, height);
}
mExpandButtonDrawable = a.getDrawable(R.styleable.ExpandableTextView_expand_button);
if (mExpandButtonDrawable != null) {
int width = buttonDrawableWidth > 0 ? buttonDrawableWidth : mExpandButtonDrawable.getIntrinsicWidth();
int height = buttonDrawableHeight > 0 ? buttonDrawableHeight : mExpandButtonDrawable.getIntrinsicHeight();
mExpandButtonDrawable.setBounds(0, 0, width, height);
}
a.recycle();
}
public void setMaxCollapseLine(int maxCollapseLine) {
mMaxCollapseLine = maxCollapseLine;
}
public void setExpandDisable(boolean isDisable) {
mIsDisableExpand = isDisable;
if (mIsDisableExpand) {
mIsExpanded = false;
setCompoundDrawables(null, null, null, null);
mCollapseButtonDrawable = null;
mExpandButtonDrawable = null;
}
}
public void setExpanded(boolean expanded) {
if (mIsDisableExpand) {
return;
}
if (mIsExpanded != expanded) {
mIsExpanded = expanded;
requestLayout();
invalidate();
}
}
public boolean isExpanded() {
return mIsExpanded;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMaxLines(Integer.MAX_VALUE);
setCompoundDrawables(null, null, null, null);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int lineCount = getLineCount();
if (lineCount > mMaxCollapseLine) {
if (mIsExpanded) {
setCompoundDrawables(null, null, null, mCollapseButtonDrawable);
} else {
setMaxLines(mMaxCollapseLine);
setCompoundDrawables(null, null, null, mExpandButtonDrawable);
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
static class SavedState extends BaseSavedState {
boolean isExpanded;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
isExpanded = (in.readInt() != 0);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(isExpanded ? 1 : 0);
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
@Override
public Parcelable onSaveInstanceState() {
// Force our ancestor class to save its state
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.isExpanded = mIsExpanded;
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mIsExpanded = ss.isExpanded;
}
}
在TextView 自定义控件中 会涉及到
StaticLayout或者DynamicLayout
可以看到,如果有span使用的是dynamiclayout,如果是singleline,则用boringlayout,如果都没有(即result==null),则用的是staticlayout
我们不妨继续看看build里面的方法:
public StaticLayout build() {
StaticLayout result = new StaticLayout(this);
Builder.recycle(this);
return result;
}
而StaticLayout最终调用的是Layout的方法,然而Layout的getLineCount是抽象方法,那么只能是StaticLayout实现这个方法了,经过一直查找,最终发现在out这个私有方法里面有关于mLineCount的计数