android 跑马灯效果 自定义View

android 跑马灯效果 自定义View

**摘要:**android自带的TextView可以实现跑马灯效果,但是有很多的局限性;比如需要设置ellipsize=”marquee”,获取 focusable=”true”,设置singleLine=”true”,控件里的内容需要超过控件本身的长度,无法控制滚动速度和滚动暂停和继续滚动功能,各种限制导致用起来特别不顺手,几乎无法使用到生产环境中,在此背景下,需要自定义View实现跑马灯效果。

使用主要方法:自定义View重写onDraw方法,通过canvas.drawText()方法来显示文字,利用Handler不断地绘制文字,并控制文字开始绘制的X轴的位置,来实现连续滚动的效果。

实现步骤:

1:自定义view的class:

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

public class MarqueenView extends View {

    /*
     * 1、自定义View的属性 2、在View的构造方法中获得我们自定义的属性 [ 3、重写onMesure ] 4、重写onDraw
     */

    /*
     * 文本
     */
    private String mTitleText;

    /**
     * 文本的颜色
     */
    private int mTitleTextColor;

    /**
     * 文本的大小
     */
    private int mTitleTextSize;

    /**
     * 绘制时控制文本绘制的范围
     */
    private Rect mBound, usualBound;

    // 画笔
    private Paint mPaint;

    private int spead = 15;

    private int length = 3;

    private int currentLength;

    @SuppressLint("HandlerLeak")
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(android.os.Message msg) {
            if (mBound.width() <= getWidth()) {
                if (currentLength >= 120 + (getWidth() / length) * length) {
                    currentLength = length;
                } else {
                    currentLength = currentLength + length;
                }
            } else {
                if (currentLength >= (mBound.width() + 120)) {
                    currentLength = length;
                } else {
                    currentLength = currentLength + length;
                }
            }

            invalidate();
            handler.sendEmptyMessageDelayed(0, spead);

        };
    };

    public MarqueenView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        currentLength = length;
    }

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

    public MarqueenView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);

        int n = a.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = a.getIndex(i);

            switch (attr) {
            case R.styleable.CustomTitleView_titleText:
                mTitleText = a.getString(attr);
                break;
            case R.styleable.CustomTitleView_titleTextColor:
                mTitleTextColor = a.getColor(attr, Color.BLACK);
                break;
            case R.styleable.CustomTitleView_titleTextSize:
                mTitleTextSize = a.getDimensionPixelSize(attr,
                        (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
                break;
            }

        }
        a.recycle();

        mPaint = new Paint();
        mPaint.setTextSize(mTitleTextSize);

        mBound = new Rect();
        usualBound = new Rect();
        mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
        mPaint.getTextBounds("1234567890QqYy你好", 0, 16, usualBound);
    }


    /*
     * EXACTLY:一般是设置了明确的值或者是MATCH_PARENT AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
     * UNSPECIFIED:表示子布局想要多大就多大,很少使用
     */

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int width;
        int height;
        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            mPaint.setTextSize(mTitleTextSize);
            mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
            float textWidth = mBound.width();// 字体宽度

            // 控件padding
            int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
            width = desired;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            mPaint.setTextSize(mTitleTextSize);
            mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
            float textHeight = mBound.height();
            int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
            height = desired;
        }
        // setMeasuredDimension((width / length) * length, height);
        setMeasuredDimension(width, height + usualBound.bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {


        mPaint.setColor(mTitleTextColor);
        if (mBound.width() <= getWidth()) {


            if (currentLength < mBound.width()) {
                canvas.drawText(mTitleText, getPaddingLeft() - currentLength, getHeight() - usualBound.bottom, mPaint);
            }

            if (currentLength >= 120) {
                canvas.drawText(mTitleText, getWidth() - currentLength + 120, getHeight() - usualBound.bottom, mPaint);
            }

        } else {


            if (currentLength < mBound.width()) {
                canvas.drawText(mTitleText, getPaddingLeft() - currentLength, getHeight() - usualBound.bottom, mPaint);
            }

            if (currentLength >= mBound.width() - getWidth() + 120) {
                canvas.drawText(mTitleText, 120 - currentLength + mBound.width(), getHeight() - usualBound.bottom, mPaint);
            }

        }

    }

    public static int getFontHeight(Integer textSize) {
        Paint paint = new Paint();
        if (textSize != null) {
            paint.setTextSize(textSize);
        }
        FontMetrics fm = paint.getFontMetrics();
        return (int) (fm.descent - fm.ascent);
    }

    public void startScroll() {
        handler.removeMessages(0);
        handler.sendEmptyMessage(0);
    }

    public void stopScroll() {
        handler.removeMessages(0);
        currentLength = 0;
        invalidate();
    }

    public void setScrollLength(int length) {
        this.length = length;
        currentLength = length;
    }

    public void setSpead(int spead) {
        this.spead = spead;
    }

    public void setText(String msg) {
        mTitleText = msg;
        mBound = new Rect();
        mPaint.getTextBounds(msg, 0, mTitleText.length(), mBound);
    }


}

2:在values的attrs.xml文件中需要加入自定义的属性,字体大小、颜色、和初始内容,这里有待改进

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

    <attr name="titleText" format="string" />
    <attr name="titleTextColor" format="color" />
    <attr name="titleTextSize" format="dimension" />

    <declare-styleable name="CustomTitleView">
        <attr name="titleText" />
        <attr name="titleTextColor" />
        <attr name="titleTextSize" />
    </declare-styleable>
</resources>

3:在layout的布局文件中引用此控件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res/***这里写上自己的包名***"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

   <com.example.MarqueenView
    android:id="@+id/main_marquee_view"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    custom:titleText="  "
    custom:titleTextColor="#666666"
    custom:titleTextSize="14sp"/>
 </LinearLayout>

4:上面准备好后,就可以在activity中设置跑马灯并开始滚动了

mv = (MarqueenView) findViewById(R.id.main_marquee_view);
mv.stopScroll();
mv.setText("滚动内容");// 设置显示内容
//mv.setSpead(15);//滚动频率,默认15毫秒一次,数值太小会影响效率
//mv.setScrollLength(3);//默认每次左移3px,数值太大会有停顿感,数值太小滚动会变慢
mv.startScroll();

//记得在Ondestroy中停止滚动,方便回收
@Override
    protected void onDestroy() {
        try {
            mv.stopScroll();
            mv = null;
        } catch (Throwable e) {
            e.printStackTrace();
        }
        super.onDestroy();
    }

下载链接以后再放上来,电脑坏啦^_^

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
俗名:垂直跑马灯;学名:垂直翻页公告使用Gradle:compile 'com.sunfusheng:marqueeview:1.1.0'属性Attribute 属性Description 描述mvAnimDuration一行文字动画执行时间mvInterval两行文字翻页时间间隔mvTextSize文字大小mvTextColor文字颜色mvGravity文字位置:left、center、rightmvSingleLine单行设置xml<com.sunfusheng.marqueeview.MarqueeView     android:id="@ id/marqueeView"     android:layout_width="match_parent"     android:layout_height="30dp"     app:mvAnimDuration="1000"     app:mvInterval="3000"     app:mvTextColor="@color/white"     app:mvTextSize="14sp"     app:mvSingleLine="true"/>设置列表数据MarqueeView marqueeView = (MarqueeView) findViewById(R.id.marqueeView); List<String> info = new ArrayList<>(); info.add("1. 大家好,我是孙福生。"); info.add("2. 欢迎大家关注我哦!"); info.add("3. GitHub帐号:sfsheng0322"); info.add("4. 新浪微博:孙福生微博"); info.add("5. 个人博客:sunfusheng.com"); info.add("6. 微信公众号:孙福生"); marqueeView.startWithList(info);设置字符串数据String notice = "心中有阳光,脚底有力量!心中有阳光,脚底有力量!心中有阳光,脚底有力量!"; marqueeView.startWithText(notice);设置事件监听marqueeView.setOnItemClickListener(new MarqueeView.OnItemClickListener() {     @Override     public void onItemClick(int position, TextView textView) {         Toast.makeText(getApplicationContext(), String.valueOf(marqueeView1.getPosition())   ". "   textView.getText(), Toast.LENGTH_SHORT).show();     } });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值