自定义可调节速度的跑马灯marqueeTextView滚动文字动效,循环周期停留

自定义文字跑马灯效果(滚动文字)

参考大神的笔记,在大神的基础上添加了速度到步长的转换以及周期性悬停的效果

CustomMarqueeTextView.java

package com.oem.view.textview;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader;
import android.os.Handler;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;

public class CustomMarqueeTextView extends AppCompatTextView{
    private static final String TAG = "CustomMarqueeTextView";
    private static final int FPS = 60;//刷新率
    private int DEFAULT_DISTANCE = 20;
    private float DEFAULT_OFFSET = 0;
    private float DEFAULT_SPEED = 100;
    private long DEFAULT_STAYED_MILISECONDS = 1200;
    private final static int DEFAULT_GRAVITY = 8388659;

    private int textWidth;//文字长度
    private float mStep = -((float) (DEFAULT_SPEED / FPS));
    private float mOffsetX = DEFAULT_OFFSET;//偏移量
    private int mRightDistance = DEFAULT_DISTANCE;//文件重复出现间距
    private int mLeftDistance = 0;
    private Rect mTextRect;//文字占位矩阵
    private boolean isPressPause = false;//是否按压暂停
    private boolean isPause = false;
    private boolean isSingle = false;
    private long mStayed = DEFAULT_STAYED_MILISECONDS;  //默认停留1200 ms
    private long mPeriod = 1000/FPS;  //等待period
    private int mCircluateTimes =3; //滚动3次后停留,-1是无限循环
    private int mCircFlag = 0;
    private Handler mHandler = new Handler();
    private Paint mPaint;
    private boolean isRoll = false;
    private String mText = "";
    private TextPaint textPaint;
    private float mTextCenterVerticalToBaseLine;
    private final int mGravity;

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

    public CustomMarqueeTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mTextRect = new Rect();
        mGravity = getGravity();
        setDistance(mRightDistance);
        setSingle(false);
    }

    public CustomMarqueeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.textViewStyle);
    }
    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            //Log.d(TAG, getText() + ": running... mStep = " + mStep);
            if (mStep != 0 && textWidth != 0) {
                if (!isPause) {
                    if (mStayed != 0) {
                        if (mCircFlag == 0) { //首次停留
                            mCircFlag++;
                            mHandler.removeCallbacks(mRunnable);
                            mHandler.postDelayed(mRunnable, mStayed);
                        } else if (mCircFlag - 1 == mCircluateTimes) { //循环停留
                            mCircFlag = 1;
                            mHandler.removeCallbacks(mRunnable);
                            mHandler.postDelayed(mRunnable, mStayed);
                        } else {
                            mOffsetX += mStep;
                            int maxW = textWidth + mRightDistance;
                            float cirFlag = Math.abs(mOffsetX);
                            mOffsetX = mOffsetX % (maxW);
                            if (cirFlag >= maxW) {
                                mCircFlag++;
                            }
                            mHandler.postDelayed(this, mPeriod);
                        }
                    }
                    postInvalidate();
                }
            }
        }
    };
    public void setCirculation(long stayed, int circluateTimes) { //停留时长,循环显示次数,逻辑是首先停留一段时长,循环显示指定次数后,再次停留,以此循环
        mStayed = stayed;
        mCircluateTimes = circluateTimes;
    }
    private void refreshGravity(boolean isMarquee) {
        Log.d(TAG, "refreshGravity: " + mGravity);
        if(isMarquee) {
            super.setGravity(DEFAULT_GRAVITY);
        } else {
            super.setGravity(mGravity);
        }
    }
    private void refreshGradient(boolean isMarquee) {
        if (isMarquee) {
            mPaint = new Paint();
            mPaint.setColor(Color.BLACK);
            mPaint.setTextSize(getTextSize());

            // 设置边框虚化效果
            mPaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.NORMAL));

            // 设置渐变背景
            int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
            float[] positions = {0.0f, 0.5f, 1.0f};
            Shader shader = new LinearGradient(0, 0, getWidth(), 0, colors, positions, Shader.TileMode.CLAMP);
            getPaint().setShader(shader);
        } else {
            int[] colors = {Color.WHITE, Color.WHITE, Color.WHITE};
            float[] positions = {0.0f, 0.5f, 1.0f};
            Shader shader = new LinearGradient(0, 0, getWidth(), 0, colors, positions, Shader.TileMode.CLAMP);
            getPaint().setShader(shader);
        }
    }

    private void refreshParameters(boolean isMarquee) {
        if (isMarquee) {
            mRightDistance = DEFAULT_DISTANCE;
            mStep = -((float) (DEFAULT_SPEED / FPS));
            mOffsetX = DEFAULT_OFFSET;
            mCircFlag = 0;
        } else {
            mRightDistance = getWidth() - textWidth;
            mStep = 0;
            mOffsetX = 0;
        }
    }
    private void setMarquee(boolean isMarquee) {
        Log.d(TAG, getText() + ": setMarquee " + isMarquee);
        isRoll = isMarquee;
        refreshParameters(isMarquee);
        refreshGradient(isMarquee);
        refreshGravity(isMarquee);
        postInvalidate();
    }
    private void refreshTextInfo() {
        Log.d(TAG, getText() + ": init text info");
        mText = getText().toString();
        textPaint = getPaint();
        textPaint.setColor(getCurrentTextColor());
        textPaint.getTextBounds(mText, 0, mText.length(), mTextRect);
        mTextCenterVerticalToBaseLine =
                (-textPaint.ascent() + textPaint.descent()) / 2 - textPaint.descent();
        textWidth = mTextRect.width();
        Log.d(TAG, "textWidth = " + textWidth + ", getWidth() = " + getWidth());

        if (textWidth > getWidth()) { //超出显示区域需要显示渐变
            setMarquee(true);
            if (mHandler != null) {
                mHandler.removeCallbacks(mRunnable);
                mHandler.postDelayed(mRunnable, 100);
            }
        } else { //不超出设置不滚动
            setMarquee(false);
        }
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        Log.d(TAG, getText() + ": setText, mCircFlag = " + mCircFlag);
        super.setText(text, type);
    }
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.d(TAG, getText() + ": onLayout");
        if (getText() != null) {
            refreshTextInfo();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //Log.d(TAG, getText() + ": onDraw");
        if (!isRoll) { //短文字
            super.onDraw(canvas);
        } else { //长文字
            int width = getWidth();
            boolean isLeft = mStep < 0;
            float y = (getHeight() >> 1) + mTextCenterVerticalToBaseLine;
            if (isSingle) {
                mRightDistance = width;
                canvas.drawText(mText, mOffsetX + (isLeft ? mRightDistance : -textWidth), y, textPaint);
            } else {
                int w = textWidth + mRightDistance;
                int size = width / w + 1;
                canvas.drawText(mText, mOffsetX + (isLeft ? 0 : width - textWidth), y, textPaint);
                for (int i = 1; i <= size; i++) {
                    int offset = w * i;
                    canvas.drawText(mText, mOffsetX + (isLeft ? offset : width - textWidth - offset), y, textPaint);
                }
            }
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isPressPause && event != null) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    setPause(true);
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    setPause(false);
                    break;
            }
            return true;
        }
        return super.onTouchEvent(event);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mHandler != null) {
            mHandler.removeCallbacks(mRunnable);
        }
    }


    /*
    * @param speed为负数向左位移,为正数向右位移
    * @return
    *
    * */
    public void setSpeed(int speed) {
        float step = (float) ((float) speed / FPS);
        Log.d(TAG, "setspeed = " + speed + ", step = " + step);
        mStep = step;
        postInvalidate();
    }

    public void setStep(float step) {
        mOffsetX = 0;
        mStep = step;
        postInvalidate();
    }

    public float getStep() {
        return mStep;
    }

    public void setDistance(int distance) {
        if (!isSingle){
            mRightDistance = distance;
            mLeftDistance = mRightDistance;
        }
  }

    public void setPressPause(boolean isPressPause) {
        this.isPressPause = isPressPause;
    }

    public void setPause(boolean pause) {
        isPause = pause;
    }

    public void setStart() {
        isPause = false;
    }
    private void setSingle(boolean single) {
        Log.d(TAG, "setSingle = true, mCirFlag = " + mCircFlag);
        mOffsetX = 0;
        isSingle = single;
        if (single) {
            mLeftDistance = mRightDistance;
        } else {
            mRightDistance = mLeftDistance;
        }
        postInvalidate();
    }

    public boolean isPause() {
        return isPause;
    }

    public boolean isSingle() {
        return isSingle;
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

麻辣璐璐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值