您的位置:首页 > 其它

自定义垂直滚动切换TextView

2016-03-03 00:41 288 查看
很多app的首页都会有一个用于显示热点消息的banner,通过垂直切换文本的方式动态展示消息。垂直切换的方式可以有效利用空间显示更多的内容,动态的效果也更能吸引用户的注意力。



实现这个效果,我能想到的方式大概有两种:

1、继承一个LineLayout,在里面添加两个TextView,通过动画实现TextView的移动、显示、隐藏。

2、继承TextView,手动去绘制文字,然后动态的改变文字的绘制,以实现切换的动效。

相比之下,第一种方式要简单一些,而且方法1不只可以添加TextVIew,还可以添加两个ViewGroup,然后构建更加复杂的布局,如添加图片等。为了体现自定义控件的特点,这里使用第二种方式来实现这个功能。具体看下代码的实现:

private static final int DEFAULT_SWITCH_DURATION = 500;
private static final int DEFAULT_IDLE_DURATION = 2000;
private Context mContext;

private List<String> lists;//会循环显示的文本内容
private int contentSize;
private String outStr;//当前滑出的文本内容
private String inStr;//当前滑入的文本内容
private float textBaseY;//文本显示的baseline
private int currentIndex = 0;//当前显示到第几个文本

private int switchDuaration = DEFAULT_SWITCH_DURATION;//切换时间
private int idleDuaration = DEFAULT_IDLE_DURATION;//间隔时间
private int switchOrientation = 0;

private float currentAnimatedValue = 0.0f;
private ValueAnimator animator;

private int verticalOffset = 0;
private int mWidth;
private int mHeight;
private int paddingLeft = 0;
private int paddingBottom = 0;
private int paddingTop = 0;

private Paint mPaint;

//回调接口,用来通知调用者控件当前的状态
public VerticalSwitchTextViewCbInterface cbInterface;

public VerticalSwitchTextView(Context context) {
this(context, null);
}

public VerticalSwitchTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public VerticalSwitchTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.VerticalSwitchTextView);
try {
switchDuaration = array.getInt(R.styleable.VerticalSwitchTextView_switchDuaration, DEFAULT_SWITCH_DURATION);
idleDuaration = array.getInt(R.styleable.VerticalSwitchTextView_idleDuaration, DEFAULT_IDLE_DURATION);
switchOrientation = array.getInt(R.styleable.VerticalSwitchTextView_switchOrientation, 0);
} finally {
array.recycle();
}
init();
}


首先定义一些常量和变量,并实现了三个构造方法。

常量的含义可以直接看代码的标注,构造方法中只有一个参数的方法是在代码里使用new生成View的时候调用的,两个参数的构造方法是在xml中定义View的时候调用,三个参数的构造方法不常用,当有自定义style的时候会用到。构造方法中对自定义属性进行了解析,后面会用到。

<resources>
<declare-styleable name="VerticalSwitchTextView">
<attr name="switchDuaration" format="integer"/>
<attr name="idleDuaration" format="integer"/>
<attr name="switchOrientation">
<enum name="up"  value="0"/>
<enum name="down"  value="1"/>
</attr>
</declare-styleable>
</resources>


上面是自定义属性的内容,分别对应于切换时长、切换间隔和切换方向。

private void init() {
setOnClickListener(this);

mPaint = getPaint();
mPaint.setColor(getCurrentTextColor());

animator = ValueAnimator.ofFloat(0f, 1f).setDuration(switchDuaration);
animator.setStartDelay(idleDuaration);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentAnimatedValue = (float) animation.getAnimatedValue();
if (currentAnimatedValue < 1.0f) {
invalidate();
}
}
});
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {

}

@Override
public void onAnimationEnd(Animator animation) {
currentIndex = (++currentIndex) % contentSize;
if (cbInterface != null) {
cbInterface.showNext(currentIndex);
}
outStr = lists.get(currentIndex);
inStr = lists.get((currentIndex + 1) % contentSize);

animator.setStartDelay(idleDuaration);
animator.start();
}

@Override
public void onAnimationCancel(Animator animation) {

}

@Override
public void onAnimationRepeat(Animator animation) {

}
});
}


init()方法中定义了一个属性动画,通过属性值的更新控制绘制的进度,在AnimatorUpdateListener的onAnimationUpdate()方法中,不断调用invalidate(),从而触发View的onDraw()方法回调。在AnimatorListener的onAnimationEnd()方法中,对要显示的内容进行更新,同时延时一定间隔再循环执行动画。

/**
* 设置循环显示的文本内容
*
* @param content 内容list
*/
public void setTextContent(List<String> content) {
lists = content;
if (lists == null || lists.size() == 0) {
return;
}
contentSize = lists.size();

outStr = lists.get(0);
if (contentSize > 1) {
inStr = lists.get(1);
} else {
inStr = lists.get(0);
}

if (contentSize > 0) {
animator.start();
}
}


setTextContent()方法用于动态设置切换的文本列表,考虑到大部分应用场景下内容都是动态设置的,这里没有自定义属性提供xml文件的静态设置,如果有需求可以另行优化。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);

Rect bounds = new Rect();
if (contentSize <= 0) {
return;
}
String text = lists.get(0);
mPaint.getTextBounds(text, 0, text.length(), bounds);
int textHeight = bounds.height();
Log.d("viclee", "onMeasure height is " + mHeight);

paddingLeft = getPaddingLeft();
paddingBottom = getPaddingBottom();
paddingTop = getPaddingTop();
mHeight = textHeight + paddingBottom + paddingTop;

Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
//计算文字高度
float fontHeight = fontMetrics.bottom - fontMetrics.top;
//计算文字的baseline
textBaseY = mHeight - (mHeight - fontHeight) / 2 - fontMetrics.bottom;

setMeasuredDimension(mWidth, mHeight);
}


onMeasure()方法中主要做了两件事:根据文本的高度、padding设置View的高度和确定文本内容绘制的baseline。具体baseline的原理和计算方法请自行google。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (contentSize <= 0) {
return;
}

//直接使用mHeight控制文本绘制,会因为text的baseline的问题不能居中显示
verticalOffset = Math.round(2 * textBaseY * (0.5f - currentAnimatedValue));
Log.d("viclee", "verticalOffset is " + verticalOffset);
if (switchOrientation == 0) {//向上滚动切换
if (verticalOffset > 0) {
canvas.drawText(outStr, paddingLeft, verticalOffset, mPaint);
} else {
canvas.drawText(inStr, paddingLeft, 2 * textBaseY + verticalOffset, mPaint);
}
} else {
if (verticalOffset > 0) {//向下滚动切换
canvas.drawText(outStr, paddingLeft,  2 * textBaseY - verticalOffset, mPaint);
} else {
canvas.drawText(inStr, paddingLeft, -verticalOffset, mPaint);
}
}
}


onDraw()中调用Canvas的drawText()方法绘制文本,根据我们设置的滚动方向的不同,绘制的y坐标的计算方式有所差别,向上滚动时,y值变小,向下滚动时,y值变大。需要注意的是,在调用drawText()的时候,x坐标为设置的左边距paddingLeft,y坐标不是控件的高度mHeight,也不能是字体的高度textHeight,只能是text的baseline,这个大家可以自己验证一下。

//回调接口,用来通知调用者控件当前的状态,index表示开始显示哪一个文本内容
public interface VerticalSwitchTextViewCbInterface {
void showNext(int index);
void onItemClick(int index);
}

public void setCbInterface(VerticalSwitchTextViewCbInterface cb) {
cbInterface = cb;
}


最后提供了一个接口,通过回调通知调用者View的状态更新(切换到新的内容或者当前内容被点击等)。如果需要其他功能,可以根据自定义View的原理自行扩展。

完整代码下载

效果图如下



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: