Android 跑马灯--谷歌“偷渡”法

遇到问题

一个跑马灯的需求,在网上扒了几个,发现都是写一个线程去循环画需要显示的字。效果来说不是很理想。
偶然下我发现了TextView自己就带了跑马灯的属性,只是谷歌为了方便文本内容超过了TextView的范围实现的。这个效果还是很不错的。
一般情况给TextView设置了 setEllipsize(TextUtils.TruncateAt.MARQUEE);是不跑的。会跑动的条件第一是TextView要显示的内容超出TextView的显示范围,第二是TextView获得了焦点

实现

根据这个两个特性我们可以自己根据TextView改造出一个跑马灯来
我就直接贴代码了:


import android.content.Context;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.support.v7.widget.AppCompatTextView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.ViewTreeObserver;

@SuppressWarnings("unused")
public class MarqueeView extends AppCompatTextView {

    private volatile int spaceCount = 0;
    private String layout_width;

    public MarqueeView(Context context) {
        super(context);
    }

    public MarqueeView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        if (attrs != null) {
            //直接获取XML中设置的宽度
            layout_width = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_width");
            switch (layout_width) {
                case "-2"://宽度设置为wrap_content的情况。这个时候宽度是文本内容的宽度决定的。
                    setWidthByText(getText().toString(),getTextSize());
                    initView();
                    break;
                default:
                    getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                        @Override
                        public void onGlobalLayout() {//系统确认了宽度和高度时回调的
                            if(getViewTreeObserver().isAlive()){
                                getViewTreeObserver().removeOnGlobalLayoutListener(this);
                                int viewWidth= getMeasuredWidth();
                                float spaceWidth = getCharacterWidth(getTextSize());
                                spaceCount = viewWidth / (int)spaceWidth;
                                initView();
                            }
                        }
                    });
                    break;
            }
        }

    }

    public MarqueeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private void initView() {
        setSelected(true);
        setSingleLine(true);
        setEllipsize(TextUtils.TruncateAt.MARQUEE);
        setMarqueeRepeatLimit(-1);
    }

    @Override
    public boolean isFocused() {
        //强制保持TextView是一直存在焦点的,而实际是没有获得焦点的。(俗称骗自己有焦点)
        return true;
    }

    @Override
    public void setFocusable(boolean focusable) {
        super.setFocusable(false);
    }

    @Override
    public void setFocusable(int focusable) {
        super.setFocusable(false);
    }

    @Override
    public void setFocusableInTouchMode(boolean focusableInTouchMode) {
        super.setFocusableInTouchMode(false);
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        if(layout_width != null && layout_width.equals("-2")){
            setWidthByText(text.toString(),getTextSize());
        }
        if(spaceCount != 0) {
            text = getCount(spaceCount - 2 ) + text;
        }
        super.setText(text, type);
    }

    @Override
    public CharSequence getText() {
        return super.getText().toString().trim();
    }


    public void setWidthByText(final String string,final float textSize){
        setWidth((int)getCharacterWidth(string, textSize));
        float spaceWidth = getCharacterWidth(textSize);
        spaceCount = (int) (getCharacterWidth(string, textSize)/spaceWidth);
    }

    public float getCharacterWidth(final float size) {
        Paint paint = new Paint();
        paint.setTextSize(size);
        return paint.measureText(" ");
    }

    public float getCharacterWidth(String string ,float size) {
        Paint paint = new Paint();
        paint.setTextSize(size);
        return paint.measureText(string);
    }

    public String getCount(int count) {
        if (count < 0) return "";
        StringBuilder st = new StringBuilder();
        for (int i = 0; i < count; i++) {
            st.append(" ");
        }
        return st.toString();
    }
}

重点是利用空格去填充TextView,让原本要显示的内容隐藏。而需要多少个空格可以通过

        Paint paint = new Paint();
        paint.setTextSize(size);
        paint.measureText(" ");//返回这个文本的宽度单位是px.

而调用getText()的时候只需要复写一下方法,通过trim()去掉两端的空格。

super.getText().toString().trim();

实现的效果:


效果图,GIF的帧率太低了,所以感觉很卡顿。

好了,基本需求解决了,这个只是一个demo,你们可以根据你的实际需求利用好TextView的这个特性去实现你自己的跑马灯。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 174,159评论 25 709
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明AI阅读 16,026评论 3 119
  • 四维2018阅读 197评论 0 0
  • 什么是春茶? 在我国大部分产茶区,春茶、夏茶和秋茶,一般是以季节变化结合茶树新梢生长的间歇性划分的。通常,春茶是指...
    虔茶之旅阅读 698评论 0 0
  • 看着胡歌和菅纫姿旧情复燃,想起我和你,可我们或许真的错过就是失去,回不去了。我亏欠你太多,没有正式的表白,没有陪你...
    Kyrie渡边君阅读 304评论 0 0