在写博客之前,告诉大家一个消息,我开通了自己的微信公众账号,如果你喜欢我的文章,希望关注我的微信公众号,我会定期与大家分享最新的博客文章,以及移动互联网最新动态。
我的微信公众号:yuanzeyao_android
二维码:
经常访问我博客的同学应该注意到了我的博客大部分是关于分析Android系统原理的,很少涉及应用层的知识,但是想要开发一个用户欢迎的App,没有一个炫酷的UI肯定是不行的,就好比一个人想成为武林高手仅仅修炼内功是不行的,必须内功和外功同时修炼才能成为真正的武林高手。很多人在开发Android的App过程中有这样的感觉,我不用学习Android系统原理照样可以进行Android开发,而且可以做出非常炫酷的效果,确实是这样的 ,因为对于一款App,它的大部分需求无非就是请求数据然后展示数据,至于一些炫酷的UI控件网上有很多开源的,所以我们不用懂Android系统的原理同样可以进行App开发,相信你自己也发现了,长期做这样的工作,你的技术是永远得不到提升的,当你使用别人开源的炫酷的控件时,你并不知道别人是怎么实现的,即使你有兴趣看别人的实现方法,如果你不懂Android系统原理,估计你也看不懂别人为什么要这么做。所以从今天起的半年时间,我会将学习重点放在应用层的开发,主要是自定义View和炫酷的动画的学习上。
该自定义View具备如下特性:
下面我们开始代码实现,正如上面所说,此View支持自定义属性,所以我们先要定义attrs.xml文件,如何编写attrs.xml文件网上有很多教程,我这里就不在说了。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ExpandTextView">
<attr name="textcolor" format="color"></attr>
<attr name="textsize" format="dimension"></attr>
<attr name="icon" format="reference"></attr>
<attr name="lines" format="integer"></attr>
<attr name="text" format="string"></attr>
</declare-styleable>
</resources>
这几个属性的意义如下:
text :显示的内容
如果需要,你可以自己定义更多的属性。下面进入代码部分
/** * com.gavin.expandable.textview.ExpandTextView * @author yuanzeyao <br/> * create at 2015年10月10日 下午5:41:12 */
public class ExpandTextView extends LinearLayout implements OnClickListener{
public static final String TAG="ExpandTextView";
public static final int DEFAULT_TEXT_COLOR=0XFF000000;
public static final int DEFAULT_LINE_NUM=3;
public static final int DEFAULT_TEXT_SIZE=12;
public static final int DEFAULT_MARGIN_TOP=10;
private TextView mTextView;
private ImageView mImageView;
/**TextView字体的颜色*/
private int textColor;
/**TextView字体的大小*/
private float textSize;
/**TextView默认显示行数*/
private int maxLine;
/**TextView的文本内容*/
private String text;
/**ImageView使用的图片*/
private Drawable mIcon;
/**TextView所有的内容暂用的行数*/
private int contentLine=0;
/**是否展开*/
private boolean isExpand=false;
public ExpandTextView(Context context) {
super(context);
init(null,0);
}
public ExpandTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs,0);
}
public ExpandTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs,defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ExpandTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs,defStyleAttr);
}
private void init(AttributeSet attrs,int defStyleAttr){
setOrientation(VERTICAL);
setGravity(Gravity.CENTER_HORIZONTAL);
TypedArray array=this.getContext().obtainStyledAttributes(attrs, R.styleable.ExpandTextView,defStyleAttr,0);
textColor=array.getColor(R.styleable.ExpandTextView_textcolor,DEFAULT_TEXT_COLOR);
textSize=array.getDimensionPixelOffset(R.styleable.ExpandTextView_textsize,dp2px(DEFAULT_TEXT_SIZE));
maxLine=array.getInt(R.styleable.ExpandTextView_lines,DEFAULT_LINE_NUM);
mIcon=array.getDrawable(R.styleable.ExpandTextView_icon);
text=array.getString(R.styleable.ExpandTextView_text);
if(mIcon==null){
mIcon=this.getContext().getResources().getDrawable(R.drawable.text_expand);
}
array.recycle();
initViewAttribute();
}
private void initViewAttribute(){
mTextView=new TextView(this.getContext());
//设置属性
mTextView.setText(text);
mTextView.setTextColor(textColor);
mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
int textHeight=mTextView.getLineHeight()*maxLine;
LayoutParams mParams_txt=new LayoutParams(LayoutParams.MATCH_PARENT,textHeight);
addView(mTextView,mParams_txt);
mImageView=new ImageView(this.getContext());
mImageView.setImageDrawable(mIcon);
LayoutParams mParams_img=new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT);
mParams_img.topMargin=dp2px(DEFAULT_MARGIN_TOP);
addView(mImageView,mParams_img);
mImageView.setOnClickListener(this);
this.setOnClickListener(this);
this.post(new Runnable() {
@Override
public void run() {
contentLine=mTextView.getLineCount();
if(contentLine<=maxLine){
mImageView.setVisibility(View.GONE);
LayoutParams mParam=(LayoutParams) mTextView.getLayoutParams();
mParam.height=LayoutParams.WRAP_CONTENT;
mTextView.setLayoutParams(mParam);
ExpandTextView.this.setOnClickListener(null);
}else{
//默认是非展开模式,那么设置最大行为maxLine
mTextView.setMaxLines(maxLine);
mTextView.setEllipsize(TruncateAt.END);
mImageView.setVisibility(View.VISIBLE);
}
}
});
}
/** * dp单位和px单位的转化 * @param dp * @return */
private int dp2px(int dp){
return (int)(this.getResources().getDisplayMetrics().density*dp+0.5);
}
@Override
public void onClick(View v) {
if(v==mImageView|| v==this){
flexibleHeight();
}
}
/** * 对TextView进行伸缩处理 */
private void flexibleHeight() {
isExpand=!isExpand;
int textHeight=0;
float startDegree=0.0f;
float endDegree=180.0f;
if(isExpand){
//如果是展开模式,那么取消最大行为maxLine的限制
textHeight=contentLine*mTextView.getLineHeight();
mTextView.setMaxLines(contentLine);
}else{
textHeight=mTextView.getLineHeight()*maxLine;
endDegree=0.0f;
startDegree=180.0f;
}
final LayoutParams mParam=(LayoutParams) mTextView.getLayoutParams();
//TextView的平移动画
ValueAnimator animator_textView= ValueAnimator.ofInt(mTextView.getHeight(),textHeight);
animator_textView.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mParam.height=(Integer)animation.getAnimatedValue();
mTextView.setLayoutParams(mParam);
}
});
//imageView的旋转动画
ObjectAnimator animator_img=ObjectAnimator.ofFloat(mImageView, "rotation", startDegree, endDegree);
AnimatorSet mAnimatorSets=new AnimatorSet();
mAnimatorSets.setDuration(500);
mAnimatorSets.play(animator_img).with(animator_textView);
mAnimatorSets.start();
mAnimatorSets.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
//动画结束之后,如果是非展开模式,则设置最大行数为maxLine
if(!isExpand){
mTextView.setMaxLines(maxLine);
}
}
});
}
}
其实代码并不复杂,这里就是继承LinearLayout
并在里面放置一个普通的TextView
和ImageView
,并在构造函数中对自定义的属性进行解析然后分别赋值给TextView
和ImageView
的相应属性。
这里有一个细节需要注意,调用TextView
的getLineCount()
方法获取TextView的行数是放置在View的post方法中执行,为什么要这么做呢,因为如果在该View的构造方法中直接调用getLineCount()
方法,必然返回0,因为这个时候View
的 measure,layout,draw过程都没有执行,必然无法获取行数。
由于没有显示完的内容需要使用省略号表示,所以这里需要对TextView
进行一些属性的设置:
mTextView.setMaxLines(maxLine);
mTextView.setEllipsize(TruncateAt.END);
写到这里,估计有些同学就要纳闷了,这里都设置最大行数的属性了,为何还要通过getLineCount()
方法获取TextView
的内容 的行数?你能注意到这里,我不得不给你一个赞…,我现在给你解释一下我为什么这么做,为了让TextView
将为显示完的内容变为省略号,不得不设置上面连个属性,但是设置了两个属性之后,我就无法知道TextView
展开后的高度是多少,所以我是先调用了getLineCount()
之后,再设置上面属性的。然后在用户点击的时候将最大行数设置成contentLine
并通过动画不断设置TextView
的高度,从maxLine
行的高度变为contentLine
行的高度。
最后不得不说一下这里使用到的动画了,这里我使用的是属性动画,属性动画是Android 3.0之后引入的一种动画,较之前Android提供的两种动画,属性动画功能强大得多,网上也有很多关于属性动画的教程,不熟悉的同学可以在网上找找资料,想要学会自己定义炫酷的View,掌握各种动画的使用时必不可少的。
代码下载地址