您的位置:首页 > 其它

自定义View实现文字跑马灯效果(垂直滚动和水平滚动)

2017-10-22 20:57 2396 查看

话不多说,直接上效果图



由于录屏的原因,画面有点卡顿,实际效果很流畅,滚动速度和方向等属性已经封装好,可以直接设置。

因为最近一个项目中要反复使用到跑马灯效果,所以直接把这个自定义View封装好,方便项目中直接调用。


在这里顺便讲一下怎样去封装自定义控件

1、首先新建一个模块吧!



2、为了使用到新建模块里面的内容,在自己的app模块点鼠标右键选择Open Module Settings把新建的模块添加到app模块里面。



3、选择 Module dependency]



4、 选中你自己新建的模块,点击ok,进行添加。当你在Project Structure里面的Modules下看到了你自己新建的模块,那么模块就添加成功了。

5、接着开始写代码,新建一个类ScrollingText继承View

首先直接上所有代码,然后慢慢分析吧!

public class ScrollingText extends View {
//开始绘制文本的X轴坐标
private int startDrawX=0;
//画笔
private Paint drawPaint;
<
4000
span class="hljs-comment">//文本的宽度和高度
private int TextWidth,TextHeight;
//文本长度是否超过文本框的宽度
private boolean isOutSide;
//要绘制的文本
private String drawText;
//字体的颜色
private int textColor;
//字体的大小
private float textSize;
//滚动的速度
private int scrollingSpeed;
//长文本时,文本显示之间的间距
private String orientation;
//两个长文本之间的空白距离
private int spacing;
//判断是否第首次启动界面
private boolean isJudge=true;
//Y轴初始化
private  int baseY=0;
//存储中间变量
private  int temp=0;

/**
* java文件构造函数
* @param context
*/
public ScrollingText(Context context) {
super(context);
initView();
}

/**
* 初始化View
*/
private void initView() {

Paint p=new Paint();
p.setColor(textColor);
p.setTextSize(sp2px(textSize));
drawPaint=p;
Rect rect=new Rect();

//把一个文本包裹起来的矩形框,来获取文本的宽高
drawPaint.getTextBounds(drawText,0,drawText.length(),rect);
TextWidth=rect.width();
TextHeight=rect.height();
spacing=100;//长文本时中间两段文字中间空白的距离
}

/**
* XML文件构造函数
* @param context
* @param attrs
*/
public ScrollingText(Context context, AttributeSet attrs) {
super(context, attrs);

//从XML文件中读取属性
TypedArray a=context.obtainStyledAttributes(attrs,R.styleable.ScrollingText);
textColor=a.getColor(R.styleable.ScrollingText_textColor, Color.BLACK);
textSize=a.getDimension(R.styleable.ScrollingText_textSize,25);
scrollingSpeed=a.getInteger(R.styleable.ScrollingText_scrollingSpeed,5);
drawText=a.getString(R.styleable.ScrollingText_text);
orientation=a.getString(R.styleable.ScrollingText_orientation);

initView();
}

/**
* 测量View大小
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//测量宽度
int width=getMySize(TextWidth,widthMeasureSpec);
//测量高度
int height=getMySize(TextHeight,heightMeasureSpec);
//保存测量宽度和测量高度
setMeasuredDimension(width,height);
//判断文本宽度是否超过布局宽度
if(TextWidth>=getMeasuredWidth()){
isOutSide =true;
}else {
isOutSide =false;
}
//设置组件初始化的布局方式是水平布局
if (orientation==null || "".equals(orientation)){
orientation="horizontal";
}

}

/**
* 绘制组件
* @param canvas
*/
@Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
if(isJudge){
//绘制文本居中的位置,并把这个值保存下来
temp = (int) ((canvas.getHeight() / 2) - ((drawPaint.descent() + drawPaint.ascent()) / 2));
baseY=temp;
}

//文字水平方向滚动,从右往左
if ("horizontal".equals(orientation)){
//两种情况,一种文字超过屏幕宽度,另一种文字没有超过宽度,针对两种情况做处理
if(isOutSide){
//当绘制的文本长度超过了文字长度时
if(startDrawX<-TextWidth){
startDrawX=spacing;
}

//文本绘制超出的区域,也就是在左边移动时超出的区域
int outSide=startDrawX;

//文本的宽度减去屏幕的宽度,就是文本超出屏幕的长度,当文本绘制超出的区域大于文本超出的宽度时,开始绘制第二条文字,
//这样才能起到无限滚动的效果(由于文字是从右往左,所以outSide是负数)
if(outSide<-(TextWidth-getMeasuredWidth())){

canvas.drawText(drawText,TextWidth+outSide+spacing,baseY,drawPaint);
}
//绘制第一条文本
canvas.drawText(drawText,startDrawX,baseY,drawPaint);

//设置onDraw()刷新的速度
postInvalidateDelayed(scrollingSpeed);

startDrawX-=5;

}else {
//文本未出文本框的宽度

//判断文本是否完全超出区域
if (startDrawX < -TextWidth) {
startDrawX = getMeasuredWidth() - TextWidth;
}
int outSide = startDrawX;

//超出区域往右边绘制超出部分
if (outSide < 0) {
canvas.drawText(drawText, getMeasuredWidth() + outSide, baseY, drawPaint);
}

canvas.drawText(drawText, startDrawX, baseY, drawPaint);
//scrollingSpeed值越大界面更新越慢
postInvalidateDelayed(scrollingSpeed);
//横向滚动时每次X轴-1
startDrawX -= 1;
}
}
//垂直反向滚动
else if("vertical".equals(orientation)){
//计算出文字开始绘制的位置,文字水平居中
int baseX = (int) (canvas.getWidth() / 2 -TextWidth/2);

//在中间这个范围内的时候休眠3秒再开始绘制,这样垂直滚动才会在中间停一下
if (baseY>=temp-10 && baseY<=temp+10){
canvas.drawText(drawText,baseX,temp,drawPaint);
new Thread(){
@Override
public void run() {
try {
Thread.sleep(3000);
handler.sendEmptyMessage(0x111);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();

}else {
//直接绘制
canvas.drawText(drawText,baseX,baseY,drawPaint);
postInvalidateDelayed(scrollingSpeed);
}
//每次Y轴加10
baseY += 10;
//当Y轴完全不可见时,设置baseY=0,从头开始绘制
if(baseY>getMeasuredHeight()+TextHeight){
baseY=0;
}
isJudge=false;
}
}
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x111){
postInvalidateDelayed(scrollingSpeed);
}
}
};

//测量
private int getMySize(int defaultSize, int measureSpec) {
int mySize = defaultSize;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
case MeasureSpec.UNSPECIFIED: {//如果没有指定大小,就设置为默认大小
mySize = defaultSize;
break;
}
case MeasureSpec.AT_MOST: {//如果测量模式是最大取值为size
//WRAP_CONTENT
//我们将大小取最小值,也可以取其他值
mySize = Math.min(size, defaultSize);
break;
}
case MeasureSpec.EXACTLY: {
//如果是固定的大小,那就不要去改变它,MatchParent或者明确的数值
mySize = size;
break;
}
}
return mySize;

}

//sp转px
private int sp2px(float spValue) {
float fontScale = getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}

}


里面注释还算详细,但理解起来可能有点困难,不着急,先用吧!

/**
* XML文件构造函数
* @param context
* @param attrs
*/
public ScrollingText(Context context, AttributeSet attrs) {
super(context, attrs);

//从XML文件中读取属性
TypedArray a=context.obtainStyledAttributes(attrs,R.styleable.ScrollingText);
textColor=a.getColor(R.styleable.ScrollingText_textColor, Color.BLACK);
textSize=a.getDimension(R.styleable.ScrollingText_textSize,25);
scrollingSpeed=a.getInteger(R.styleable.ScrollingText_scrollingSpeed,5);
drawText=a.getString(R.styleable.ScrollingText_text);
orientation=a.getString(R.styleable.ScrollingText_orientation);

initView();
}


这是XML文件中会使用的方法,当你把这个自定义View放到XML文件中时,就会调用这个构造方法,因为是自定义View,要把它封装好,所以也要封装好它的几个属性,方便以后直接在XML文件中设置它的属性。在这个方法中我们要获取它的属性,然后对这些值做处理。


当然这些值是要先定义的,不然报错,显示找不到。

在新建模块的string资源下定义属性



先定义这几个属性



<declare-styleable name="ScrollingText">
<attr name="textColor" format="color"/>
<attr name="textSize" format="dimension"/>
<attr name="scrollingSpeed" format="integer"/>
<attr name="text" format="string"/>
<attr name="orientation" format="string"/>
</declare-styleable>


name名字,format类型。

接着我们就可以在app模块下使用这个自定义View

在你的项目资源文件下直接使用

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.zhengpeng.myviewdemotext.ScrollinnTextActivity">

<com.zhengpeng.uikit.ScrollinnText.ScrollingText
android:layout_width="match_parent"
android:layout_height="180dp"
android:background="#00eeee"
app:text="@string/text"
app:orientation="horizontal"
app:textColor="@color/colorAccent"
app:scrollingSpeed="10" />
</RelativeLayout>


其实主要的核心代码在onDraw( )方法里面

先看这个方法里面水平滚动的代码

//文字水平方向滚动,从右往左
if ("horizontal".equals(orientation)){
//两种情况,一种文字超过屏幕宽度,另一种文字没有超过宽度,针对两种情况做处理
//这是第一种情况,文字超过屏幕宽度
if(isOutSide){
//当绘制的文本长度超过了文字长度时
if(startDrawX<-TextWidth){
startDrawX=spacing;
}

//文本绘制超出的区域,也就是在左边移动时超出的区域
int outSide=startDrawX;

//文本的宽度减去屏幕的宽度,就是文本超出屏幕的长度,当文本绘制超出的区域大于文本超出的宽度时,开始绘制第二条文字,
//这样才能起到无限滚动的效果(由于文字是从右往左,所以outSide是负数)
if(outSide<-(TextWidth-getMeasuredWidth())){

canvas.drawText(drawText,TextWidth+outSide+spacing,baseY,drawPaint);
}
//绘制第一条文本
canvas.drawText(drawText,startDrawX,baseY,drawPaint);

//设置onDraw()刷新的速度
postInvalidateDelayed(scrollingSpeed);

startDrawX-=5;

}else {
//文本未出文本框的宽度

//判断文本是否完全超出区域
if (startDrawX < -TextWidth) {
startDrawX = getMeasuredWidth() - TextWidth;
}
int outSide = startDrawX;

//超出区域往右边绘制超出部分
if (outSide < 0) {
canvas.drawText(drawText, getMeasuredWidth() + outSide, baseY, drawPaint);
}

canvas.drawText(drawText, startDrawX, baseY, drawPaint);
//scrollingSpeed值越大界面更新越慢
postInvalidateDelayed(scrollingSpeed);
//横向滚动时每次X轴-1
startDrawX -= 1;
}
}


水平滚动有两种情况

第一种文本宽度大于屏幕宽度,startDrawX是递减的,这样才能往左边移动,把每次startDrawX的值,赋值给outSide,当判断到outSide大于文本宽度减去屏幕宽度的值之后,也就是文本的最后端已经显示出来了,这是开始绘制第二条文本,绘制的起始点是TextWidth+outSide+spacing这个位置,TextWidth文本宽度时大于屏幕的,outSide是超出的位置,是负值,spacing是与前文本之间的空格。

//当绘制的文本长度超过了文字长度时

if(startDrawX<-TextWidth){

startDrawX=spacing;

}

这时候第一条文字初始化位置是100,spacing值等于100,是设置的空白距离,第二条不再绘制。

第二种是文字长度小于屏幕宽度,首先绘制第一条文字,从startDrawX =0的位置开始绘制,因为我们要实现的效果是从右往左滚动,所以startDrawX 要 -=1,每刷新一次往左边移动一点。往左边移出来的这一些,应该在右边显示出来,这时我们就在右边绘制第二条文本,当第一条文本移动到startDrawX >=文本的宽度,就完全出了屏幕,这时把第一条文本绘制到第二条文本的位置,这是outSide >0所以第二条文本没有绘制,这样启到了一个循环的滚动的效果,理解起来可能有点困难。

看个图吧



最好自己动手去试下,改下参数啥的!运行下,这样有助于理解!

后面还有个垂直滚动的,代码里面有注释,这里不再做说明了,相信理解了水平滚动原理,垂直滚动也不在话下!

对于自定义View不怎么了解的可以看下这个系列的博客


Android自定义View(一、初体验自定义TextView)

这个系列比较的详细,个人觉得很不错,强烈推荐!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: