最近公司有个轮播中奖信息的需求,就是那种跑马灯的效果,产品要求跑马灯不能间断,不能等到一轮播完了再播下一轮。但是textview自带的跑马灯效果很不灵活,既不能改变速度,又是间断的。于是网上找了很多那种自定义的跑马灯的例子,但是很不幸的是,目前还没找到那种不间断的跑马灯,于是我寻思着,用自己的办法来实现跑马灯,最终还是弄出来了,虽然用起来不是很方便,但是功能是实现了。

  主要思想:先自定义一个surfaceView,名为MarqueeTextView,用线程来实现跑马灯效果。然后再在layout文件里面加入两个一模一样的MarqueeTextView,这两个MarqueeTextView重叠在一起,先启动第一个MarqueeTextView,让他开始播,等它的最后一个字播出来的时候,再启动第二个一个MarqueeTextView。第一个MarqueeTextView播完以后就停止,等到第二个MarqueeTextView的最后一个字播出来的时候,再启动第一个MarqueeTextView,如此循环播放,相互启动,就形成了不间断的跑马灯效果。可能说得有点绕,直接上代码,注释写详细点。

自定义的MaqueeTextView:

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.os.Handler;
import android.os.Message;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;


public class MarqueeTextView extends SurfaceView implements SurfaceHolder.Callback{
    public Context mContext;

    private float mTextSize = 100; //字体大小

    private int mTextColor = Color.RED; //字体的颜色

    private int mStartPoint;// 开始滚动的位置  0是从最左面开始    1是从最末尾开始

    private int mDirection;//滚动方向 0 向左滚动   1向右滚动

    private int mSpeed;//滚动速度

    private SurfaceHolder holder;

    private TextPaint mTextPaint;

    private MarqueeViewThread mThread;

    private String margueeString;

    private int textWidth=0,textHeight=0;

    private int ShadowColor=Color.BLACK;

    public int currentX=0;// 当前x的位置

    public int sepX=2;//每一步滚动的距离

    public static boolean isScroling1 = false;//1是否正在滚动
    public static boolean isScroling2 = false;//2是否正在滚动

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

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

    public MarqueeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext=context;
        init(attrs, defStyleAttr);
    }

    private void init(AttributeSet attrs, int defStyleAttr) {

        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MarqueeView, defStyleAttr, 0);
        mTextColor = a.getColor(R.styleable.MarqueeView_textcolor, Color.RED);
        mTextSize = a.getDimension(R.styleable.MarqueeView_textSize, 48);
        mStartPoint=a.getInt(R.styleable.MarqueeView_startPoint,0);
        mDirection=a.getInt(R.styleable.MarqueeView_direction,0);
        mSpeed=a.getInt(R.styleable.MarqueeView_speed,20);
        a.recycle();

        holder = this.getHolder();
        holder.addCallback(this);
        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setTextAlign(Paint.Align.LEFT);
        setZOrderOnTop(true);//使surfaceview放到最顶层
        getHolder().setFormat(PixelFormat.TRANSLUCENT);//使窗口支持透明度
    }

    public void setText(String msg){
        if(!TextUtils.isEmpty(msg)){
            measurementsText(msg);
        }
    }
    protected void measurementsText(String msg) {
        margueeString=msg;
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setColor(mTextColor);
        mTextPaint.density = getResources().getDisplayMetrics().density;
        // mTextPaint.setStrokeWidth(0.5f);
        // mTextPaint.setFakeBoldText(true);
        // 设定阴影(柔边, X 轴位移, Y 轴位移, 阴影颜色)
//        mTextPaint.setShadowLayer(5, 3, 3, ShadowColor);
        textWidth = (int)mTextPaint.measureText(margueeString);
        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        textHeight = (int) fontMetrics.bottom;
        WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        int width = wm.getDefaultDisplay().getWidth();
        if(mStartPoint==0)
            currentX=0;
        else
            currentX=width-getPaddingLeft()-getPaddingRight();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        this.holder=holder;
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if(mThread!=null)
            mThread.isRun = true;
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if(mThread!=null)
            mThread.isRun = false;
    }

    /**
     * 开始滚动
     */
    public  void startScroll(){

        if(mThread!=null&&mThread.isRun)
            return;
        mThread = new MarqueeViewThread(holder);//创建一个绘图线程
        mThread.start();
    }

    /**
     * 停止滚动
     */
    public  void stopScroll(){
        if(mThread!=null){
            mThread.isRun = false;
            mThread.interrupt();
        }
        mThread=null;
    }

    /**
     * 线程
     */
    class MarqueeViewThread extends Thread{

        private SurfaceHolder holder;

        public boolean isRun ;//是否在运行


        public  MarqueeViewThread(SurfaceHolder holder) {
            this.holder =holder;
            isRun = true;
        }

        public void onDraw() {
            try {
                synchronized (holder) {
                    if (TextUtils.isEmpty(margueeString)) {
                        Thread.sleep(1000);//睡眠时间为1秒
                        return;
                    }
                    Canvas canvas = holder.lockCanvas();
                    int paddingLeft = getPaddingLeft();
                    int paddingTop = getPaddingTop();
                    int paddingRight = getPaddingRight();
                    int paddingBottom = getPaddingBottom();

                    int contentWidth = getWidth() - paddingLeft - paddingRight;
                    int contentHeight = getHeight() - paddingTop - paddingBottom;
                    int centeYLine = paddingTop + contentHeight / 2;//中心线
                    if(mDirection==0) {//向左滚动
                        if(currentX<=contentWidth-textWidth){//这里是关键,当
                                                             //currentX<=contentWidth-textWidth时就启动另一个,这个靠接口来完成
                                                             //如果两轮之间间距还是有点大的话,可以在此处加或者减一个合适的距离,来调整间距大小
                            if(mOnMargueeListener!=null){
                                mOnMargueeListener.onRollOver();
                            }
                        }

                        if(currentX <=-textWidth){
                            mHandler.sendEmptyMessage(ROLL_OVER);
                            currentX=contentWidth;
                        }else{
                            currentX-=sepX;
                        }
                    }else {//  向右滚动
                        if(currentX>textWidth){
                            if(mOnMargueeListener!=null){
                                mOnMargueeListener.onRollOver();
                            }
                        }
                        if(currentX>=contentWidth){
                            mHandler.sendEmptyMessage(ROLL_OVER);
                            currentX=-textWidth;
                        }else{
                            currentX+=sepX;
                        }
                    }
                    if(canvas!=null)
                        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);//绘制透明色
                    canvas.drawText(margueeString,currentX, centeYLine+dip2px(getContext(),textHeight)/2,mTextPaint);
                    holder.unlockCanvasAndPost(canvas);//结束锁定画图,并提交改变。
                    int a=textWidth/margueeString.trim().length();
                    int b=a/sepX;
                    int c=mSpeed/b==0?1:mSpeed/b;
                    Thread.sleep(c);//睡眠时间为移动的频率
                }
            }
            catch (Exception e) {
                e.printStackTrace();
            }

        }

        @Override
        public void run() {
            while (isRun) {
                onDraw();
            }

        }

    }

    public static  final  int  ROLL_OVER =100;
    public static final int START = 101;
    Handler mHandler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            switch (msg.what){
                case ROLL_OVER:
                    stopScroll();
                    break;

            }
        }
    };

    /**
     * dip转换为px
     * @param context
     * @param dpValue
     * @return
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    public void reset(){
        int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight();
        if(mStartPoint==0)
            currentX=0;
        else
            currentX=contentWidth;
    }
    /**
     * 滚动回调
     */
    public interface OnMargueeListener{
        void onRollOver();//滚动完毕
    }

    OnMargueeListener mOnMargueeListener;

    public void setOnMargueeListener(OnMargueeListener mOnMargueeListener){
        this.mOnMargueeListener=mOnMargueeListener;
    }
}




资源文件:attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="MarqueeView">
        <attr name="textcolor" format="color" />
        <attr name="textSize" format="dimension" />
        <attr name="isRepeat" format="boolean" />
        <attr name="speed" format="integer"/>
        <attr name="startPoint" format="integer">
            <enum name="start" value="0"/>
            <enum name="end" value="1"/>
        </attr>
        <attr name="direction" format="integer">
            <enum name="left" value="0"/>
            <enum name="right" value="1"/>
        </attr>
    </declare-styleable>
</resources>


layout文件cativity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             xmlns:app="http://schemas.android.com/apk/res-auto"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:gravity="center"
             android:background="#ffffff">

        <com.test.testdemo.MarqueeTextView
            android:id="@+id/annoText1"
            app:textcolor="#ffffff"
            android:background="#000000"
            app:textSize="25sp"
            app:startPoint="end"
            app:direction="left"
            app:speed="30"
            android:layout_width="1425dp"
            android:layout_height="50dp"
            android:layout_marginTop="5dp"
            android:layout_gravity="center_vertical"/>

        <com.test.testdemo.MarqueeTextView
            android:id="@+id/annoText2"
            app:textcolor="#ffffff"
            android:background="#000000"
            app:textSize="25sp"
            android:layout_marginTop="5dp"
            app:startPoint="end"
            app:direction="left"
            app:speed="30"
            android:layout_width="1425dp"
            android:layout_height="50dp"
            android:layout_gravity="center_vertical"/>
</FrameLayout>




Activity中的调用:

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {
    private MarqueeTextView text1,text2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }
    private void init(){
        text1 = (MarqueeTextView)this.findViewById(R.id.annoText1);
        text2 = (MarqueeTextView)this.findViewById(R.id.annoText2);
        text1.setText("12345678hftghgqerrgmitewthenrtywrtfwert");
        text2.setText("12345678hftghgqerrgmitewthenrtywrtfwert");
        text1.startScroll();//先启动一个;
        text1.setOnMargueeListener(new MarqueeTextView.OnMargueeListener() {
            @Override
            public void onRollOver() {
                if(!MarqueeTextView.isScroling2) {
                    MarqueeTextView.isScroling2 = true;
                    text2.startScroll();//等接口被调用之后再启动另一个。
                    MarqueeTextView.isScroling1 = false;
                }

            }
        });
        text2.setOnMargueeListener(new MarqueeTextView.OnMargueeListener() {
            @Override
            public void onRollOver() {
                if(!MarqueeTextView.isScroling1) {
                    MarqueeTextView.isScroling1 = true;
                    text1.startScroll();
                    MarqueeTextView.isScroling2 = false;
                }

            }
        });
    }

    @Override
    protected void onPause() {
        text1.stopScroll();
        text2.stopScroll();
        super.onPause();
    }
}





所有的代码都在这里了,需要的可以自行复制粘贴,应该是可以直接运行的。也可以继续进行优化的。毕竟不是很简洁。