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

来源:互联网 发布:医院数据分析 编辑:程序博客网 时间:2024/05/16 10:06

话不多说,直接上效果图

这里写图片描述

由于录屏的原因,画面有点卡顿,实际效果很流畅,滚动速度和方向等属性已经封装好,可以直接设置。因为最近一个项目中要反复使用到跑马灯效果,所以直接把这个自定义View封装好,方便项目中直接调用。

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

1、首先新建一个模块吧!
这里写图片描述

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

3、选择 Module dependency]
选择 Module dependency

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

5、接着开始写代码,新建一个类ScrollingText继承View
首先直接上所有代码,然后慢慢分析吧!

public class ScrollingText extends View {    //开始绘制文本的X轴坐标    private int startDrawX=0;    //画笔    private Paint drawPaint;    //文本的宽度和高度    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)
这个系列比较的详细,个人觉得很不错,强烈推荐!

原创粉丝点击